I'm tryting to to the following:
[Inject] public HttpClient HttpClient { get; set; } = default!;
public AzureActiveDirectoryUser[] AzureActiveDirectoryUsers { get; set; } = default!;
protected override async Task OnInitializedAsync()
{
var authenticationState = await authenticationStateTask;
if (authenticationState.User?.Identity?.IsAuthenticated == true)
{
AzureActiveDirectoryUsers = await HttpClient.GetFromJsonAsync<AzureActiveDirectoryUser[]>(url); //this is line 52
}
}
But I'm getting the following error:
I don't really understand what it means.
What I'm trying to get is a JSON with usernames and emails (I have access to the URL and can verify that the data is there)
AzureActiveDirectoryUsers is just a class with an id and a mail.
(edit)
I used to have this error. But it just suddenly stopped.
In OnInitializedAsync not everything is initalized on the page.
Move the code in OnAfterRenderAsync and call StateHasChanged if needed.
Related
My GET Request with ID as a parameter does not properly load all my data. My PUT Request successfully alters the data according to Swagger but then the GET Requests show that no change has been made. My GET Request with a parameter says my Player data is null when it shouldn't be. My normal GET Request loads the data properly, except when data within the PUT Request changes something within Player[]. See my code below.
Player Model:
public class Player
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Teams Model:
public class Team
{
public long Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public IEnumerable<Player> Players { get; set; }
}
Generic GET Request (returns POST'ed data but not updated Player data from PUT Request):
[HttpGet]
public async Task<ActionResult<IEnumerable<Team>>> GetTeams()
{
return await _context.Teams.Include(t => t.Players).ToListAsync();
}
GET Request with ID Parameter (doesn't load Player data):
[HttpGet("{id}")]
public async Task<ActionResult<Team>> GetTeam(long id)
{
var team = await _context.Teams.FindAsync(id);
if (team == null)
{
return NotFound();
}
return team;
}
PUT Request (Alters data):
[HttpPut("{id}")]
public async Task<IActionResult> PutTeam(long id, Team team)
{
if (id != team.Id)
{
return BadRequest();
}
_context.Entry(team).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TeamExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
GET Request with ID Parameter response:
{
"id": 1,
"name": "panthers",
"location": "string",
"players": null
}
Essentially, my PUT Requests returns 204 and Swagger says I've altered the data. The changes specifically within the Player list are not altered, whereas anything aside from it like Location will change just fine. Additionally, the GET Request with ID doesn't return Player at all. GET shows Player data, but not changes within my PUT Request.
To GET Players for a specific team by id, the principle is the same, eager load the navigation property:
var team = await _context.Teams.Include(t => t.Players).FindAsync(id);
For the PUT request, I'd say you should have an Update or UpdateAsync method:
_context.Teams.Update(team);
await _context.SaveChangesAsync();
If this, doesn't work or is unavailable you can try and mark the players as modified as well:
_context.Entry(team).State = EntityState.Modified;
foreach(var player in team.Players)
{
_context.Entry(player).State = EntityState.Modified;
}
await _context.SaveChangesAsync();
If it's still doesn't work you can load the entity, that will track the entity, and detect changes:
var teamFromBd = await _context.Teams.Include(t => t.Players).FindAsync(id);
teamFromBd = team; //*
await _context.SaveChangesAsync();
Make sure the Players are indeed binded to the team method parameter.
* The above is a simplification, you should need to assign the team properties one by one (except fot Ids). You can try as is but it probably won't work.
I have a scenario where I need to respond to a request that it's been received and send a response (request?) to another endpoint once internal api calls and logic has completed. The flow looks like this:
External request to my endpoint > endpoint responds to request with accepted > endpoint passes the request on internally > internal logic fetches and handles data from DB > internal logic uses data from DB to send a request back to a different endpoint from the same integration as the first call came from.
I have managed to get it to work using Queued Background Tasks to send the request to the correct internal handler with Mediatr. However in order for it to work I need to add the barer token from the request header to the request object and then use that barer token to validate against the internal API's. I'd like to avoid this since I might run into the issue of the token expiring or not being valid for the internal Api etc.
Request object example:
public class ExampleRequest : IRequest, IRequest<ExampleResponse>
{
public string? Token { get; set; } //Added for functioning version, want to get rid
//of it
public CommonData Data { get; set; }
public string RequestId { get; set; }
public string OperationId { get; set; }
public List<string> ObjectIdentifiers { get; set; }
}
public class CommonData
{
public string MessageId { get; set; }
public DateTime Timestamp { get; set; }
}
Response object example (response to the call):
public class ExampleResponseForCall
{
public CommonData Data { get; set; }
public string ResponseStatus { get; set; } //Will be accepted/acknowledged
}
Example response object (for final response)
public class ExampleResponse
{
public CommonData Data{ get; set; }
public string ResponseStatus { get; set; }
public string ErrorCode { get; set; }
public string ErrorDescription { get; set; }
public string RequestId { get; set; }
public string OperationId { get; set; }
}
My current working version looks something like this:
**Endpoint:**
public IActionResult Post(ExampleRequest request)
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
}
var token = headerValue?.Parameter;
request.Token = token; //I added token as a nullable string on the request obj
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
**Background Task queue:**
public void StartTask(IRequest request)
{
_logger.LogInformation("Task is starting.");
_request = request;
Task.Run(async () => await AddTaskAsync(), _cancellationToken);
}
private async ValueTask AddTaskAsync()
{
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Task {Guid} is starting.", guid);
if (_request == null)
{
_logger.LogWarning("Request for task {Guid} is null.", guid);
return;
}
await _mediator.Send(_request, token);
_logger.LogInformation("Task {Guid} is complete.", guid);
}
I also have Handlers that can handle the request and Clients for sending requests internally and back to the caller. All of that works when awaiting the internal logic to be handled. However when I'm using the background task queue the internal client fails on the when getting the token here
protected async Task<HttpClient> CreateBaseClient()
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", await GetToken());
return client;
}
public async Task<string> GetToken()
{
if (_httpContextAccessor.HttpContext == null)
throw new Exception("No HttpContext available when trying to
get Token.");
_httpContextAccessor.HttpContext.Items.TryGetValue(Constants.AuthenticationSchemeKey,
out var scheme);
if (scheme?.ToString() == Constants.Bearer)
return GetTokenFromRequest();
throw new MissingAccessTokenException("Unknown authentication type");
}
My workaround (that I want to get away from) looks like this:
protected async Task<HttpClient> CreateBaseClient(string version, string token)
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", token); //token from requst.Token
return client;
}
I've tried to pass in the a lot of different things to the Background Task queue (and changing the parameter type to match ofc) but nothing works. I want to have the background task queue generic since I'll be implementing this for other end points as well.
That's a lot of text so TL:DR, I respond to a request but need to use the token from the request for other calls after responding.
We decided to go with the working solution provided in the question itself.
Due to how our infrastructure is set up we won't be able to get a refresh token (as suggested by #GHDevOps and #TheWallrus) since we won't be able to get the login/id and password/secret of the user in a safe and reasonable way.
However, the working solution in the question has some drawback which should be analyzed on a case-to-case basis. We know that the Api sending us the requests will fetch a new (relevant) token approximately 10 minutes before the current (relevant) token expires and use the new token for all coming requests. Since the logic we apply before passing on the request to our backend is very simple (just simple remapping) we should rarely run into issues with the token expiring before the request has been sent, and in the rare cases that is has, we will send that information in the request back to the external Api, giving them a chance to resend the original request. If the external Api isn't fetching a new token before the expiration of the current token that might cause the token to expire before reaching the internal Api more often which might be a good thing to look after if you're implementing a similar solution.
The code changes that I made for this to function are just minor refactoring (see below). Hope this help anyone else running into a similar issue!
//Endpoint
public IActionResult Post(ExampleRequest request)//Before
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
}
var token = headerValue?.Parameter;
request.Token = token; //I added token as a nullable string on the request obj
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
public IActionResult Post(ExampleRequest request)
{
request.Token = GetToken(Request);//Made into a separate function in the inherited class
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
protected string GetToken(HttpRequest request)
{
var authorization = request.Headers[HeaderNames.Authorization];
_ = AuthenticationHeaderValue.TryParse(authorization, out var headerValue);
if (headerValue == null)
{
return "";
}
return string.Equals(headerValue.Scheme, "Bearer", StringComparison.InvariantCultureIgnoreCase) ?
headerValue.Parameter : "";
}
//Client
protected async Task<HttpClient> CreateBaseClient()//before
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", await GetToken());
return client;
}
protected async Task<HttpClient> CreateBaseClient(string token = "")
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", $"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", string.IsNullOrEmpty(token) ?
await GetToken() : token); //We will only send in a token if we are async
return client;
}
I have 4 pages and a service, the purpose of each pages and a service:
First page: animals introduction > when user select animals then start the service
Second page: cities selection
Service (MyService): to get the data from the API and assign the result to the session storage which can be accessed later on from any pages and afterwards stop the service, otherwise loop through the service until it found the result or the service stopped manually
Third page: Summary of what user selected
Fourth page: to stop the service and to get the animals information from the session storage if found (foods liked or disliked by animals selected, animal's age, from where the animal coming from, etc), if not found then call the API directly from this page (but supposedly the data already being assigned to the session storage)
The reason on why I put as a service, is because I don't want user to wait and also user could select multiple animals from the first page and then pass the data to the service and the response from the service could take more than 1 second
Above scenarios already achieved, however when I wants to set the response from the API to the session storage, it didn't response to anything, then when comes to the fourth page, there is no data in session storage which when I manually query the DB, there is a response and also when I put the breakpoint at the line where it will assign to the session storage, it didn't get pass there and the last line after set to session storage never executed
Here is the code that I am using:
First Page
#inject
Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage SessionStorage
#inject MyService MyService
#code {
protected override void OnInitialized()
{
MyService.SessionStorage = SessionStorage;
}
// On user submit will execute below function
private void SetBackgroundService()
{
if (MyService.IsStartService)
return;
MyService.IsStartService = true;
if (!MyService.IsRunning)
MyService.StartAsync(new System.Threading.CancellationToken());
}
}
MyService
public class MyService : IHostedService, IDisposable
{
public bool IsRunning { get; set; }
public bool IsStartService { get; set; }
public Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage SessionStorage { get; set; }
public Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(async () =>
{
if (!IsStartService)
return;
while (!cancellationToken.IsCancellationRequested)
{
if (!IsStartService)
return;
await Task.Delay(2000, cancellationToken);
await DoWorkAsync();
}
}, cancellationToken);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
IsStartService = IsRunning = false;
return Task.CompletedTask;
}
private async Task DoWorkAsync()
{
IsRunning = true;
var Animals = await <API Call>
if (Animals == null)
return;
await SessionStorage.SetAsync("Animals", JsonConvert.SerializeObject(Animals)); // this is where the debug stops
await StopAsync(new CancellationToken()); // this line never executed
}
public void Dispose()
{
}
}
Fourth page
#inject MyService MyService
protected override void OnInitialized()
{
if (MyService.IsRunning)
MyService.StopAsync(new System.Threading.CancellationToken());
}
Startup
public void ConfigureServices(IServiceCollection services)
{
// hide other codes for simplicity
services.AddHostedService<MyService>();
}
Any idea what I am doing wrong?
Thank you very much.
I'm using the latest VS2019Pro with Core 3.1.
It seems like Blazor ServerApp has real-time code running within the #code{} tags in the .razor pages. So instead of using APIs to provide the data, I was thinking it would make a lot of sense to just create classes and methods to return the data.
The only issue I am facing is being able to use User.Identity.Name in a Class. Usually this is provided in the .razor pages and in Controllers without issue, but how can I (if it's even possible) use the same User.Identity.Name property within classes?
You can use the standard .net core auth: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-3.1
Basically you define a:
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
Then you can await it:
private async Task LogUsername()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
_authMessage = $"{user.Identity.Name} is authenticated.";
}
else
{
_authMessage = "The user is NOT authenticated.";
}
}
Edit -----
Something like this??
Base Component:
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
public String Username {get;set;}
protected override async Task OnInitializedAsync(){
Username = (await authenticationStateTask).User.Identity.Name;
}
Then in your other components:
#inherits BaseCompomnent
#Username
IMyRoleManager _myRoleManager;
private AuthenticationStateProvider _authenticationStateProvider;
public MyRepository(IMyRoleManager myRoleManager, AuthenticationStateProvider authentication)
{
_myRoleManager = myRoleManager;
_authenticationStateProvider = authentication;
}
public async Task<bool> AddRoleToUser(string role)
{
var id = await GetUserIdFromAuthenticationStateProviderAsync();
var result = await _myRoleManager.SetRoleForUser(role, id);
return result;
}
And in the startup file the correct entries have to be there for services.xxx (identity services)
I am trying to have some basic configuration from json file to a singleton service inside my client side blazor application at the start up.
Below is my code setup
AppConfig and IAppConfig files
interface IAppConfig
{
string BaseUrl { get; set; }
string Source { get; set; }
}
and
public class AppConfig : IAppConfig
{
public string BaseUrl { get; set; }
public string Source { get; set; }
}
Than a json file by the name of environment.json inside wwwroot as wwwroot/ConfigFiles/environment.json
Than a service to read this file
interface ISharedServices
{
Task<AppConfig> GetConfigurationAsync();
}
and
public class SharedServices : ISharedServices
{
private HttpClient Http { get; set; }
public SharedServices(HttpClient httpClient)
{
Http = httpClient;
}
public async Task<AppConfig> GetConfigurationAsync()
{
return await Http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json");
}
}
Now i am calling it into my component which load first
public class IndexComponent : ComponentBase
{
[Inject]
internal IAppConfig AppConfig { get; set; }
[Inject]
internal ISharedServices sharedServices { get; set; }
protected override async Task OnInitializedAsync()
{
var appconfig = await sharedServices.GetConfigurationAsync();
AppConfig = appconfig;
}
}
All this works fine , but i want to have this configuration ready at the time of application load in browser , so as suggested by "auga from mars" in my other Question i tried below code inside startup.cs at the moment i add IAppConfig as singleton service
services.AddSingleton<IAppConfig, AppConfig>(provider =>
{
var http = provider.GetRequiredService<HttpClient>();
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").GetAwaiter().GetResult();
});
But , buy using this code the blazor app never start up , all it show a blank white page with text Loading.... , not even any error but in every 5 min pop up show - page taking too much time to load with two option of wait and close .
If i change this code a bit from
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").GetAwaiter().GetResult();
to
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").Result;
Than it say - "Maximum call stack size exceed"
How to have configuration ready at startup ??
Update 1:
A little Update
in Basecomponent file , code is
protected override async Task OnInitializedAsync()
{
var appconfig = await sharedServices.GetConfigurationAsync();
AppConfig.BaseUrl = appconfig.BaseUrl;
AppConfig.Source = appconfig.Source;
}
I have to set every property one by one manually , need to get rid of this too