JavaScript interop Error when calling javascript from OnInitializedAsync Blazor - asp.net-core

I am following a sample app from the NDC Oslo which is this app: https://github.com/SteveSandersonMS/presentation-2019-06-NDCOslo/tree/master/demos/MissionControl.
This implements JWT as authentication and authorization. However when I tried to copy the implementation of the code to a Server Side Blazor, I'm getting an error when I try to get the JWT token stored from the local storage described below"
JavaScript interop calls cannot be issued at this time. This is because the component is being
statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed
during the OnAfterRenderAsync lifecycle method.
Here is my blazor code
protected override async Task OnInitializedAsync()
{
var token = await TokenProvider.GetTokenAsync();
Branches = await Http.GetJsonAsync<List<BranchDto>>(
"vip/api/lookup/getbranches",
new AuthenticationHeaderValue("Bearer", token));
}
The error comes from
public async Task<string> GetTokenAsync()
{
//Code Omitted for brevity
//This line of code is equivalent to the IJSRuntime.Invoke<string>("localstorage.getitem","authToken")
//change to use Blazore.LocalStorage.
var token = await _localStorageService.GetItemAsync<string>("authToken");
return token;
}
I tried perform the code on OnAfterRenderAsync(bool firstRender) and the error is gone but the grid which is binded to the API request has no display. The API request must fill the data source for the grid which must be OnInitializedAsync. Any workaround on this?
Update!
I moved the code OnAfterRenderAsync and added the StateHasChanged Method and I got the desired Behavior.
I forgot that the connection for rendering was a signalR connection.

As per “Detect when a Blazor Server app is prerendering”, you can only safely run interop code in the OnAfterRenderAsync lifecycle method.
However, since this runs after the render cycle, you will need to notify your component to re-render using StateHasChanged() once your asynchronous process completes:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var token = await TokenProvider.GetTokenAsync();
Branches = await Http.GetJsonAsync<List<BranchDto>>(
"vip/api/lookup/getbranches",
new AuthenticationHeaderValue("Bearer", token));
StateHasChanged();
}
}

Related

How to call StateHasChanged() / NotifyAuthenticationStateChanged from a class in Blazor WASM

In my project I created a class to handle http POST. The main method in the class first check if a JWT Token is available in local storage, check exp date, decide if a new toekn is needed and if so use the refresh token and finally do the POST.
I inject this class with builder.Services.AddTransient<IMyHttp, MyHttp>();
Now I would like to notify the UI (StateHasChanged() or NotifyAuthenticationStateChanged) in case the refresh token is not valid so to log out the user immediately.
The point is that I do not know how to raise the event from my http class (while from a controller is just a matter of calling this.StateHasChanged()).
As suggested here you are the (pseudo)code:
Index controller call the WebAPI to check weather:
(HttpResponseMessage, IEnumerable<WeatherForecast>) result = await MyHttp.PostPFAsync<IEnumerable<WeatherForecast>>("WeatherForecast/GetWeather", null);
This is MyHttp.PostPFAsync injected with builder.Services.AddTransient<IMyHttp, MyHttp>(); in Program.cs
public async Task<(HttpResponseMessage, T)> PostPFAsync<T>(string requestUri, object data)
{
// I get my JWT Token from localstorage, set up auth headers, create StreamContent content serializing data and then
HttpResponseMessage response = await _http.PostAsync(requestUri, content);
if (response.IsSuccessStatusCode)
{
return (response, "Happiness");
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
// Here I clear tokens from localstorage and I'd also like to logout user and update UI
}
else return (response, default(T));
}
Obviosuly I could logout and update the UI in the Index controller but this would mean to make the check everywhere I call the WebAPI via MyHttp.PostPFAsync while I would like to centralize it as soon as I get a 401 (actually I'll do this if I fail to use the refresh token but keep things simple in this example).

App stops working when calling Http.PostAsync when returning a string

I have a Blazor app that's calling a WebAPI method that returns a string, however when it runs, it just stops. The app remains open but nothing appears to happen.
When i put a breakpoint in the controller it does run.
var ms = new System.IO.MemoryStream();
await file.Data.CopyToAsync(ms);
status = $"Finished loading {file.Size} bytes from {file.Name}";
var content = new MultipartFormDataContent {
{ new ByteArrayContent(ms.GetBuffer()), "\"upload\"", file.Name }
};
string featureImage = await Http.PostAsync("api/Blog/ReturnString", content).Result.Content.ReadAsStringAsync();
Above is the code that calls the WebAPI method. Below is the Controller that returns a very simple string.
[Authorize]
[HttpPost("[action]")]
public async Task<string> ReturnString()
{
return "123456";
}
Apart from the comment by #enet, there're likely two more bugs:
There's a risk of dead lock in the way you get Result by using .Result.Content. You need change the code to await ...
Also, if you're using Blazor Server side, be aware that invoking Http in Blazor ServerSide App is different from the Blazor WebAssembly App. You need create your own HttpClient or inject one and then provide base uri for it.
Hope the below helps:
(if you're using Blazor ServerSide) register HttpClient related services in your startup:
services.AddHttpClient(); // only if you're using Blazor ServerSide
And within your Blazor component/page:
#using System.Net.Http
#inject IHttpClientFactory HttpClientFactory #*if you're using Blazor ServerSide*#
#inject NavigationManager navMgr #*if you're using Blazor ServerSide*#
#code{
var Http = HttpClientFactory.CreateClient(); //if you're using Blazor ServerSide
Http.BaseAddress = new Uri(navMgr.BaseUri); //if you're using Blazor ServerSide
// ... add credentials if you're using Blazor ServerSide
//... your code
// don't block it by access the`.Result` property, await it!
await Http.PostAsync("api/Blog/ReturnString", content).Result.Content.ReadAsStringAsync();
var resp= await Http.PostAsync("api/Blog/ReturnString", content);
var featureImage = await resp.Content.ReadAsStringAsync();
Finally, if above code doesn't address the problem, could you please Press F12 within the browser and show us the error message ?

How to Integrate Testing a Controller, which inside that controller, methods gets token from another site

I'm setting up an API which it gets authenticated to another server through JWT.
one of Controller's methods I want to test, gets the token from an external site.
how can I test this method?
I made a Test server and tried to mimic the website action which providing tokens.i can access this Test Server through Test Methods but I can't get to it from the actual controller.
here is my Test Server setup method
MockedController is the Controller supposed to provide toke and is working fine and I can get tokens from test units.
AuthController is the Controller supposed to test.
var server = new TestServer(
new WebHostBuilder()
.UseEnvironment(TestConstants.EnvironmentName)
.UseStartup<Startup>()
.UseUrls(TestConstants.MockAddress)
.ConfigureTestServices(config =>
{
config.AddOptions();
config.AddMvc()
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2)
.AddApplicationPart(typeof(AuthController).Assembly)
.AddApplicationPart(typeof(MockedTokenController).Assembly)
;
config.AddSingleton<MockedTokenController>();
config.BuildServiceProvider();
}));
server.BaseAddress = new Uri(TestConstants.MockAddress);
server.CreateHandler();
_client = server.CreateClient();
_client.BaseAddress = new Uri(TestConstants.MockAddress);// I tried with and without this line
Here is the test method which is failing
var request = new HttpRequestMessage(HttpMethod.Get, "/Auth/Login");
var response = await _client.SendAsync(request);
var contents = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Here is the AuthController Login Method code
[HttpGet("Login")]
public async Task<IActionResult> LoginAsync(){
var token = await _authService.GetAuthenticationTokenAsync();
return Ok(token);
}
here is the AuthService code which is called from AuthController
public async Task<string> GetAuthenticationTokenAsync(){
HttpClient client = new HttpClient();
var response = await client.SendAsync(request,
HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
var token = await response.Content.ReadAsStringAsync();
return token;
}
extra information.
test method for mocked controller works fine.
it seems the problem is in second step usage of the mocked controller. the first step works fine. I mean I can access the mocked controller from Test unit at first step but when I try to access it through the Main controller(AuthController), I can't get to it
Your code is too tightly coupled to implementation concerns to allow for components to be mocked to isolate it from 3rd party dependencies. Because HttpClient is created manually there is no way to replace its handler to fake the requests.
Consider mocking the auth service itself that is injected into the controller under test.
For example
//...
var token = "{fake token here}";
var authService = Mock.Of<IAuthService>(_ =>
_.GetAuthenticationTokenAsync == Task.FromResult(token)
);
config.AddSingleton<IAuthService>(authService);
//...

How to extend IdentityServer4 workflow to run custom code

I have a basic Identityserver4 implementation based on the quick start sample.
In my startup I have the following:
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
}
public void Configure(IApplicationBuilder app)
{
...
app.UseIdentityServer();
}
I want to extend the IdentityServer4 workflow so that after the access token is generated I can run business logic (based on the claims in the access token) and modify the response send to the calling client. I tried creating a .NET core middleware but it seems the IdentityServer middleware short-circuits the rest of the pipeline (no middleware place after the UseIdentityServer will be executed).
Are they any extension method in Identityserver4 that I can use to always modify the response issued by IdentityServer4? I am using the credentials grant. Essentially I want to run some business logic to modify the response send to the client once IdentityServer4 is done with its workflow
Unfortunately, there is no way to do that.
When you request any IdentityServer endpoint, IdentityServer middleware short-circuits the rest of the pipeline.
You can check source code:
IdentityServerMiddleware class.
I believe it was done for a reason. But if you really need to modify the response, you have at least three options:
Create a fork and remove return operator from
IdentityServerMiddleware Invoke method (be careful to short-circuit the rest of the pipeline adding return into your last middleware).
Create your own IdentityServerMiddleware, IdentityServerApplicationBuilderExtensions implementations and use
them instead of default.
Place your middleware before the UseIdentityServer. Your middleware should look like this:
public ResponseBodyEditorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// get the original body
var body = context.Response.Body;
// replace the original body with a memory stream
var buffer = new MemoryStream();
context.Response.Body = buffer;
// invoke the next middleware from the pipeline
await _next.Invoke(context);
// get the body as a string
var bodyString = Encoding.UTF8.GetString(buffer.GetBuffer());
// make some changes
bodyString = $"The body has been replaced!{Environment.NewLine}Original body:{Environment.NewLine}{bodyString}";
// update the memory stream
var bytes = Encoding.UTF8.GetBytes(bodyString);
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
// replace the memory stream with updated body
buffer.Position = 0;
await buffer.CopyToAsync(body);
context.Response.Body = body;
}

How to set up Relay Modern with a Promise-based Environment? (e.g. Auth0 or another async authentication service?)

In Relay Classic, we would just pass a function to react-relay-network-layer to return the required token in a promise. What's the equivalent in Relay Modern?
Ideally I'd like to display the Loading screen until the Environment promise resolves, and then display the main Component once we have an Environment and the query is fetched.
So if I knew how to swap out QueryRenderer's environment, that would also solve the issue.
The recommended method here is to fetch the authentication token inside of fetchQuery.
The remaining challenge is how to make sure the async authentication function is called only once, even if Relay fetches multiple times while authentication is still ongoing. We did this using a singleton promise; each call to fetchQuery calls the static Promise.resolve() method on the same promise, so that once the authentication call finishes, all fetchQuery calls continue with the desired authentication information.
So fetchQuery gets an auth token (JWT) with:
const authToken = await AuthToken.get();
And AuthToken looks like (TypeScript):
class AuthToken {
private static _accessTokenPromise: Promise<string>;
public static async get() {
if (!this._accessTokenPromise)
this._accessTokenPromise = this.AuthFunction(); // AuthFunction returns a promise
return await Promise.resolve(this._accessTokenPromise);
}
}