How would you write it in Blazor? #64
-
Have you thought about building a Blazor version of Contoso University? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
This example was easy because it was a pre-existing, full-featured sample from Microsoft. If there are other good full examples to adapt, I wouldn't mind doing it. |
Beta Was this translation helpful? Give feedback.
-
No experience in Blazor here either. But as Blazor is a stateful app framework, I can think of two approaches.
Keeping the state inside blazor components.This is what the sample BlazorServerEFCoreSample does. If you opt to go down this road, it seems you can write queries/commands/handlers in a regular way. One downside of this approach is it leaves too much code/logic inside of a component. So this @page "/add"
@inject IDbContextFactory<ContactContext> DbFactory
@inject NavigationManager Navigation
@inject IPageHelper PageHelper
// Removed the markup for brevity...
@code {
private Contact? Contact { get; set; }
// True when an asynchronous operation is running.
private bool Busy;
// True after successful add.
private bool Success;
// True when an error occurred.
private bool Error;
// The error message
private string ErrorMessage = string.Empty;
// Start with a fresh Contact
protected override Task OnInitializedAsync()
{
Contact = new();
return base.OnInitializedAsync();
}
// Respond to a forms submission.
private async Task ValidationResultAsync(bool success)
{
if (Busy)
return;
if (!success)
{
Success = false;
Error = false;
return;
}
Busy = true;
using var context = DbFactory.CreateDbContext();
// this just attaches
if (Contact is not null)
context.Contacts?.Add(Contact);
try
{
await context.SaveChangesAsync();
Success = true;
Error = false;
// ready for the next
Contact = new();
Busy = false;
}
catch (Exception ex)
{
Success = false;
Error = true;
ErrorMessage = ex.Message;
Busy = false;
}
}
// Back to list.
private void Cancel() => Navigation.NavigateTo($"/{PageHelper.Page}");
} turns into @page "/add"
@inject IMediator Mediator;
// Same code as before...
@code {
// Or replace `Contract?` with `CreateContractCommand?`
// in the same way pages in ContosoUniversityDotNetCore-Pages do it.
private Contact? Contact { get; set; }
// Same code as before...
// Respond to a forms submission.
private async Task ValidationResultAsync(bool success)
{
// Same code as before...
Busy = true;
try
{
await Mediator.Send(new CreateContractCommand { Contract = Contract });
Success = true;
Error = false;
// ready for the next
Contact = new();
Busy = false;
}
catch (Exception ex)
{
Success = false;
Error = true;
ErrorMessage = ex.Message;
Busy = false;
}
}
// Back to list.
private void Cancel() => Navigation.NavigateTo($"/{PageHelper.Page}");
public record CreateContractCommand(Contract Contract) : IRequest<int?>;
public class CreateContractCommandHandler : IRequestHandler<CreateContractCommand, int?>
{
// Most likely, no need to inject `IDbContextFactory<ContactContext>`
// as MediatR creates a new handler instance per request by default.
// But it's up to you to check this actually works as expected.
private readonly ContactContext _contractDbContext;
public CommandHandler(ContactContext contractDbContext) =>
_contractDbContext = contractDbContext;
public async Task<int?> Handle(CreateContractCommand command, CancellationToken token)
{
// this just attaches
if (command.Contact is not null)
context.Contacts?.Add(command.Contact);
await context.SaveChangesAsync();
return command.Contract?.Id;
}
}
} Following a state management patternYou can use a library like Blazor-State directly or as a source of inspiration. It has actions and action handlers which are very similar to commands/queries/handlers. Actions and action handlers can be organized by feature. For example, check this one out. // Sending an action
async Task IncrementCount()
{
await Mediator.Send(new CounterState.IncrementCountAction { Amount = 5 });
}
// ...
// Elsewhere in your code, handling the action
namespace Sample.Client.Features.Counter;
using System.Threading;
using System.Threading.Tasks;
using BlazorState;
using MediatR;
public partial class CounterState
{
public class IncrementCountHandler : ActionHandler<IncrementCountAction>
{
public IncrementCountHandler(IStore aStore) : base(aStore) { }
CounterState CounterState => Store.GetState<CounterState>();
public override Task<Unit> Handle(IncrementCountAction aIncrementCountAction, CancellationToken aCancellationToken)
{
CounterState.Count = CounterState.Count + aIncrementCountAction.Amount;
return Unit.Task;
}
}
} Personally, I'd be inclined to go with this approach. Not really for any advertised technical advantages (this absolutely isn't to say, it doesn't bring any though). But simply for its help in structuring the code in a really nice way. The decision, of course, depends greatly on the project and task at hand. |
Beta Was this translation helpful? Give feedback.
This example was easy because it was a pre-existing, full-featured sample from Microsoft. If there are other good full examples to adapt, I wouldn't mind doing it.