How to fire ErrorBoundary on async method in Blazor - error-handling

I have use ErrorBoundary in MainLayout.razor page but it doesn't work for async methods.
without async method it will fire ErrorBoundary inbuilt component so Can any one have any idea about how to use ErrorBoundary in async method in blazor?
Mainlayout.razor
<ErrorBoundary #ref="errorBoundary">
<ChildContent>
#Body
</ChildContent>
<ErrorContent>
Error Message
</ErrorContent>
</ErrorBoundary>
Game.razor page
#code{
ErrorBoundary errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
public async void OutRange()
{
int[] a = new int[5];
Console.WriteLine(a[6]);
}
}
When this OutRange() function is called then whole behavior is being stoped. Is there any solution for this?

Related

.Net Maui Shell Navigation - Is it possible to pass a Query Parameter and Auto Populate a Page?

I need to auto populate a Page by passing a Shell Navigation Parameter to a ViewModel/Method and call a Service to return a single record from a Web Service. Essentially a drill-through page. My issue is that I need to call the data retrieveal command, "GetFieldPerformanceAsync" (note [ICommand] converts this to "GetFieldPerformanceCommand") from the "To" Page's code-behind from within OnNavigatedTo. This is required since the Shell Navigation Parameter is not set in the ViewModel until the Page is loaded. I'm currently unable to make the Command call from OnNavigatedTo and need advice on how to accomplish this.
Thanks!
Code behind the Page:
public partial class FieldPerformancePage : ContentPage
{
public FieldPerformancePage(FieldPerformanceViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
//works with parameter hard-coded in ViewModel
//viewModel.GetFieldPerformanceCommand.Execute(null);
}
FieldPerformanceViewModel viewModel;
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
//this does not work
viewModel.GetFieldPerformanceCommand.Execute(null);
}
}
ViewModel
namespace TrackMate.ViewModels;
[QueryProperty(nameof(FieldAssignedWbs), nameof(FieldAssignedWbs))]
public partial class FieldPerformanceViewModel : BaseViewModel
{
[ObservableProperty]
FieldAssignedWbs fieldAssignedWbs;
[ObservableProperty]
FieldPerformance fieldPerformance;
FieldPerformanceService fieldPerformanceService;
public FieldPerformanceViewModel(FieldPerformanceService fieldStatusService)
{
Title = "Status";
this.fieldPerformanceService = fieldStatusService;
}
[ICommand]
async Task GetFieldPerformanceAsync()
{
if (IsBusy)
return;
try
{
IsBusy = true;
int wbsId = fieldAssignedWbs.WbsId;
var fieldPerformanceList = await fieldPerformanceService.GetFieldPerformanceList(wbsId);
if (fieldPerformanceList.Count != 0)
FieldPerformance = fieldPerformanceList.First();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
await Shell.Current.DisplayAlert("Error!",
$"Undable to return records: {ex.Message}", "OK");
}
finally
{
IsBusy = false;
}
}
}
I believe I figured it out...
By adding ViewModel Binding within the OnNavigatedTo method in the "DetailsPage" Code Behind, a Command Call can be made to the Page's ViewModel to execute data retrieval method after the Shell Navigation Parameter (object in this scenario) passed from the "Main" Page has been set. Note a null is passed since the Query Parameter is sourced from the ViewModel. If you are new to .Net Maui, as I am, I recommend James Montemagno's video on .Net Maui Shell Navigation.
namespace TrackMate.Views;
public partial class FieldPerformancePage : ContentPage
{
public FieldPerformancePage(FieldPerformanceViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
FieldPerformanceViewModel viewModel = (FieldPerformanceViewModel)BindingContext;
viewModel.GetFieldPerformanceCommand.Execute(null);
base.OnNavigatedTo(args);
}
}
For me it only worked when the BindingContext assignment is before the component initialization and the method call after the base call in OnNavigatedTo
public partial class OccurrencePage : ContentPage
{
public OccurrencePage(OccurrenceViewModel model)
{
BindingContext = model;
InitializeComponent();
}
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
OccurrenceViewModel viewModel = (OccurrenceViewModel)BindingContext;
viewModel.GetFieldsCommand.Execute(null);
}
}
While overriding OnNavigatedTo works fine, there is one more simple technique to run something once your query param is set, given you do not need to run anything asynchronous inside the method: implementing partial method OnFieldAssignedWbsChanged, auto-generated for your convenience by mvvm toolkit
partial void OnFieldAssignedWbsChanged(FieldAssignedWbs value)
{
// run synchronous post query param set actions here
}
Less amount of code and less code-behind and viewModel dependencies, but works fine for non-async operations only.

How to pass argument to Class-based Middleware

I have custom class-based middleware like:
#Service()
export class MyMiddleware implements MiddlewareInterface<Context> {
constructor(private readonly service: Service) {}
async use({ info, context }: ResolverData<Context>, next: NextFn) {
// this.service.doSomeDbLogicHere()
return next();
}
}
#UseMiddleware(MyMiddleware)
#Mutation(() => User)
public async createuser() {}
I wonder how I can pass custom static values to my middleware, but still have other objects injected via DI.
You need to create a function that accepts a static value and return a middleware class.

SignalR context within core3.1 controller - no context.clients

I am trying to call a SignalR Hub Action from a controller.
On my controller I have this:
private readonly IHubContext<TurnHub> _hubContext;
public HomeController(ILogger<HomeController> logger, IHubContext<TurnHub> hubContext)
{
_logger = logger;
_hubContext = hubContext;
_gameService = new GameService(ModelState);
}
public async Task<IActionResult> Test()
{
return View();
}
public async Task<IActionResult> TestMessage()
{
await _hubContext.Clients.All.SendAsync("TurnChanged", 1);
return View();
}
When I break on hub context, I can see nodes for "Clients" and "Groups" but there are no clients or groups under that level. Running the controller action sees no errors, but the message isn't pushed to the client.
On the hub I have this:
public class TurnHub : Hub
{
public async Task EndTurn(int nextUser)
{
await Clients.All.SendAsync("TurnChanged", nextUser);
}
}
And the view has this:
<script>
var connection = new signalR.HubConnectionBuilder().withUrl("/TurnHub").build();
connection.on("TurnChanged", function (nextUser) {
debugger;
alert(nextUser);
});
</script>
I was expecting any browser window that was displaying that view to alert when one of the clients hits that controller action (Called from a button on that view).
What am I doing wrong?
I have the signalr core package installed, the js file from "add client library" #microsoft/signalr. There are no console errors on the browser to say anything is wrong!
Any help greatly appreciated.
In your javascript client you need to start the hub connection. Like this
connection.start();
The start will return a promise so you could do some stuff after the hub has been connected. Also failures in connection can be tracked by catching errors on that promise.

Get Current User in a Blazor component

I'm starting a new site with Blazor and Windows Authentication and need to identify the current user viewing the page/component.
For a Razor Page, the current user name can be accessed with Context.User.Identity.Name, but that doesn't seem to work in a Blazor component. I've tried injecting HttpContext into the component but the Context is null at runtime.
As a bonus, I will eventually want to incorporate this into Startup.cs so I only need to get the username once and can leverage a corporate user class (with EF Core) for my applications. Answers tailored to that use case would also be appreciated.
There are three possibilities for getting the user in a component (a page is a component):
Inject IHttpContextAccessor and from it access HttpContext and then User; need to register IHttpContextAccessor in Startup.ConfigureServices, normally using AddHttpContextAccessor. Edit: according to the Microsoft docs you must not do this for security reasons.
Inject an AuthenticationStateProvider property, call GetAuthenticationStateAsync and get a User from it
Wrap your component in a <CascadingAuthenticationState> component, declare a Task<AuthenticationState> property and call it to get the User (similar to #2)
See more here: https://learn.microsoft.com/en-us/aspnet/core/security/blazor.
For me the solution mentioned in the first answer 2. option worked perfect:
I am using Blazor server side on .Net Core 5.0 .
I injected
#inject AuthenticationStateProvider GetAuthenticationStateAsync
in my Blazor page and added the following in the code section:
protected async override Task OnInitializedAsync()
{
var authstate = await GetAuthenticationStateAsync.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;
}
In my startup.cs, I have the following lines:
services.AddScoped<ApiAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(p =>
p.GetRequiredService<ApiAuthenticationStateProvider>());
For blazor wasm in net 5.0 and above. Here is how I did,
Wrap your <App> component inside <CascadingAuthenticationState> as shown below,
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
...
</Found>
<NotFound>
...
</NotFound>
</Router>
</CascadingAuthenticationState>
Then add Task<AuthenticationState> CascadingParameter inside any component as shown below,
public class AppRootBase : ComponentBase
{
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
}
Now you can access logged in user Identity and Claims inside component as shown below,
protected override async Task OnInitializedAsync()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
Console.WriteLine($"{user.Identity.Name} is authenticated.");
}
}
Here is the reference from Microsoft docs.
I've now been able to get it to work with a general class, as well as a component.
To get access to the HttpContext User; in ConfigureServices, in Startup.cs add
services.AddHttpContextAccessor();
I have a CorporateUserService class for my CorporateUser class. The service class gets a DbContext through constructor injection.
I then created a new CurrentCorporateUserService that inherits from the CorporateUserService. It accepts a DbContext and an IHttpContextAccessor through constructor injection
public class CurrentCorporateUserService : CorporateUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentCorporateUserService(IHttpContextAccessor httpContextAccessor,
MyDbContext context) : base(context)
{
_httpContextAccessor = httpContextAccessor;
}
...
The base service class has a method GetUserByUsername(string username). The Current service class adds an additional method
public CorporateUser GetCurrentUser()
{
return base.GetUserByUsername(_httpContextAccessor.HttpContext.User.Identity.Name.Substring(8));
}
The Current service class is registered in Startup.cs
services.AddScoped<CurrentCorporateUserService>();
Once that is done, I can use the CurrentCorporateUserService in a component with directive injection.
[Inject]
private CurrentCorporateUserService CurrentCorporateUserService { get; set; } =
default!;
or in any class, with constructor injection.
public MyDbContext(DbContextOptions<MyDbContext> options,
CurrentCorporateUserService CurrentCorporateUserService)
: base(options)
{
_currentUser = CurrentCorporateUserService.GetCurrentUser();
}
Making it a project wide service means all my developers do not have to concern themselves with how to get the Current User, they just need to inject the service into their class.
For example, using it in MyDbContext makes the current user available to every save event. In the code below, any class that inherits the BaseReport class will automatically have the report metadata updated when the record is saved.
public override Int32 SaveChanges()
{
var entries = ChangeTracker.Entries().Where(e => e.Entity is BaseReport
&& (e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
((BaseReport)entityEntry.Entity).ModifiedDate = DateTime.Now;
((BaseReport)entityEntry.Entity).ModifiedByUser = _currentUser.Username;
}
return base.SaveChanges();
}
I've had a similar requirement and have been using:
var authstate = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;
I already had an AuthenticationStateProvider in my startup.cs and added it to the constructor of my custom class.
If you create a new project and choose Blazor with Windows Authentication you get a file called Shared\LoginDisplay.razor with the following content:
<AuthorizeView>
Hello, #context.User.Identity.Name!
</AuthorizeView>
Using the <AuthorizeView> we can access #context.User.Identity.Name without any modifications on any page.
In your App.razor, make sure the element encapsulated inside a CascadingAuthenticationState element. This is what is generated by default if you create your Blazor project with authentication support.
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly" PreferExactMatches="#true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
In your component you can use the AuthenticationStateProvider to access the current user like in the following sample:
#page "/"
#layout MainLayout
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject SignInManager<IdentityUser> SignInManager
#code
{
override protected async Task OnInitializedAsync()
{
var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (SignInManager.IsSignedIn(authenticationState.User))
{
//Do something...
}
}
}
The below solution works only if you are running under IIS or IIS Express on Windows. If running under kestrel using 'dotnet run', please follow steps here, https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.0&tabs=visual-studio#kestrel
[startup.cs]
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
[index.razor]
#page "/"
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
<h1>#UserName</h1>
#code {
public string UserName;
protected override async Task OnInitializedAsync()
{
UserName = httpContextAccessor.HttpContext.User.Identity.Name;
}
}
I also had this problem. The best solution I found was to inject both UserManager and AuthenticationStateProvider and then I made these extension functions:
public static async Task<CustomIdentityUser> GetUserFromClaimAsync(this
ClaimsPrincipal claimsPrincipal,
UserManager<CustomIdentityUser> userManager)
{
var id = userManager.GetUserId(claimsPrincipal);
return await userManager.FindByIdAsync(id);
}
public static async Task<CustomIdentityUser> GetCurrentUserAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
{
return await (await provider.GetAuthenticationStateAsync()).User.GetUserFromClaimAsync(UM);
}
public static async Task<string> GetCurrentUserIdAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
{
return UM.GetUserId((await provider.GetAuthenticationStateAsync()).User);
}
This was a painful journey for me chasing a moving target. In my case I only needed the user name for my Blazor component used in a Razor page. My solution required the following:
In the Index.cshtml.cs I added two properties and constructor
public IHttpContextAccessor HttpContextAccessor { get; set; }
public string UserName { get; set; }
public TestModel(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
if (HttpContextAccessor != null) UserName = HttpContextAccessor.HttpContext.User.Identity.Name;
}
Then in the Index.cshtml where I add the component I called it as follows:
<component type="typeof(MyApp.Components.FileMain)" param-UserName="Model.UserName" render-mode="ServerPrerendered" />
In my component I use a code behind file (FileMain.razor.cs using public class FileMainBase : ComponentBase) have the code:
[Parameter]
public string UserName { get; set; } = default!;
and then as a proof of concept I added to the FileMain.razor page
<div class="form-group-sm">
<label class="control-label">User: </label>
#if (UserName != null)
{
<span>#UserName</span>
}
</div>
You should add needed claims to the User after login.
For example I show the FullName on top of site (AuthLinks component) instead of email.
[HttpPost]
public async Task<IActionResult> Login(LoginVM model)
{
var user = await _userManager.FindByNameAsync(model.Email);
if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
return Unauthorized("Email or password is wrong.");
var signingCredentials = GetSigningCredentials();
var claims = GetClaims(user);
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
return Ok(new LoginDto { Token = token, FullName = user.FirstName + " " + user.LastName });
}
#code {
private LoginVM loginVM = new();
[Inject]
public AuthenticationStateProvider _authStateProvider { get; set; }
private async Task SubmitForm()
{
var response = await _http.PostAsJsonAsync("api/accounts/login", loginVM);
var content = await response.Content.ReadAsStringAsync();
var loginDto = JsonSerializer.Deserialize<LoginDto>(content);
await _localStorage.SetItemAsync("authToken", loginDto.Token);
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginDto.Token);
(_authStateProvider as AuthStateProvider).NotifyLogin(loginDto.FullName);
_navigationManager.NavigateTo("/");
}
}
public void NotifyLogin(string fullName)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("FullName", fullName) }, "jwtAuthType"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
<AuthorizeView>
<Authorized>
#context.User.FindFirst("FullName")?.Value
<button class="btn btn-outline-danger mx-4" #onclick="LogOut">LogOut</button>
</Authorized>
<NotAuthorized>
Login
Register
</NotAuthorized>
</AuthorizeView>
GitHub project link:
https://github.com/mammadkoma/Attendance
This worked for me:
Replace:
#context.User.Identity.Name
With:
#context.User.Claims.Where(x => x.Type=="name").Select(x => x.Value).FirstOrDefault()
UPDATE - This answer does not work. Rather than deleting it, I've let it here as information. Please consider the other answers for the question instead.
In ConfigureServices, in Startup.cs, add
services.AddHttpContextAccessor();
In your component class [Note: I use code-behind with null types enabled]
[Inject]
private IHttpContextAccessor HttpContextAccessor { get; set; } = default!;
private string username = default!;
In your component code (code behind), in protected override void OnInitialized()
username = HttpContextAccessor.HttpContext.User.Identity.Name;
username can now be used throughout the component just like any other variable.
However, see my other answer in this question to add get the current user name from a service usable in any class.
This is what works for me on a single page
Add to Startup.cs
services.AddHttpContextAccessor();
On the Razor Component page
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
<div>#GetCurrentUser()</div>
#code{
protected string GetCurrentUser()
{
return httpContextAccessor.HttpContext.User.Identity.Name;
}
}

MVC4 & SignalR Only executing in other windows

I am doing an MVC4 web application and decided using signalr, there is just one thing I don't understand, I'm probably doing something wrong...
I have a page (Home) with an ActionLink that call the HomeController.
In the controller I have this:
HomeController:
public class HomeController : Controller
{
private IHubContext context;
public HomeController()
{
context = GlobalHost.ConnectionManager.GetHubContext<HomeHub>();
}
...
public ActionResult Scan()
{
context.Clients.All.StartedScanning();
}
}
HomeHub:
public class HomeHub : Hub
{
public void StartedScanning()
{
Clients.All.setStatusMessage("Scanning started...");
}
}
Index.cshtml
...
#Html.ActionLink("Scan for media", "scan", null, new
{
#class = "scanActionLink"
})
...
#section scripts
{
<script src="~/Scripts/jquery.signalR-1.1.2.js"></script>
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
$(function () {
var home = $.connection.homeHub;
home.client.setStatusMessage = function (message) {
alert(message);
}
// Start the connection.
$.connection.hub.start().done(function () {
});
})
</script>
}
Actually the code is executing all right, but only in other windows and not on the main. Is that normal?
I hope I've been clear enough.
Thanks.
By clicking your ActionLink, you are reloading the page. SignalR is not yet (re)loaded in the context of the new page, so the notification will not be sent to it by the server. You may want to try the StartedScanning request as an AJAX request rather than navigating away from the current page.