0

I'm starting to learn Web dev with Blazor. I'm on Blazor server. The idea is to display a message to the user if the name already exists in db.

Let's consider this simple method (in a service class and in another assembly)

public async Task<Category> Add(string categoryName)
    {
        var cat = new Category { Name = categoryName };

        if (_context.Category.Any(c => c.Name == categoryName))
        {
            //Display "Already exists"
        }
        else
        {
            await _context.AddAsync(cat);
            await _context.SaveChangesAsync();
            return cat;
        }
    }

In the Razor page, I'm calling the method like that:

protected async Task<Category> IncrementCount()
{
    return category= await _categoryService.Add("test");
}

How to display the message created in the method here for example

<div>@errorMessage</div>

I would like to keep the logic in the service class, it means that I would avoid something like that in the Razor page:

protected async Task<Category> IncrementCount()  
{
    category= await _categoryService.Add("test"); 
    errorMessage=category is null?"Already exists":string.Empty;
    return category;
}

Thanks

Mike Brind
  • 28,238
  • 6
  • 56
  • 88
Manta
  • 490
  • 5
  • 18
  • I think your original idea is better - let the service class take care of the DB concerns. It doesn't need to handle a purely UI concern (displaying messages). – topsail Jul 05 '23 at 18:19
  • Ok but how to display the message to the user in the UI? – Manta Jul 05 '23 at 18:37
  • There are many examples if you search *blazor alert*, *blazor messagebox*, *blazor message*, *blazor popup*, *blazor toast* For example (simple js interop): [how-to-use-alert-confirm-and-prompt-function-using-blazor](https://stackoverflow.com/questions/60773229/how-to-use-alert-confirm-and-prompt-function-using-blazor) or (creating an alert service): [building-an-alert-service-in-blazor](https://dev.to/supachris28/building-an-alert-service-in-blazor-4poo) or [blazor-toast-notifications-using-only-csharp-html-css](https://chrissainty.com/blazor-toast-notifications-using-only-csharp-html-css/) – topsail Jul 05 '23 at 20:59
  • or (using a free blazor library): blazorise (https://blazorise.com/docs/components) – topsail Jul 05 '23 at 20:59

1 Answers1

1

In good design you should separate out Commands and Queries. Pass into the data pipeline request objects and get out result objects, not raw data objects. You need status information as well as a result. You should never return null. What does it mean?

So a command looks like this:

public Task<CommandResult> AddItem(CommandRequest<Category> command)
{
    //...
}

Where CommandRequest looks like this:

public record struct CommandRequest<TRecord>(TRecord Item, CancellationToken Cancellation = new());

And a CommandResult looks like this:

public sealed record CommandResult : IDataResult
{ 
    public bool Successful { get; init; }
    public string? Message { get; init; }
    public int InsertedId { get; init; }

    private CommandResult() { }

    public static CommandResult Success(int insertedId, string? message = null)
        => new CommandResult { Successful = true, Message= message };

    public static CommandResult Success(string? message = null)
        => new CommandResult { Successful = true, Message= message };

    public static CommandResult Failure(string message)
        => new CommandResult { Message = message};
}

where:

public interface IDataResult
{
    public bool Successful { get; }
    public string? Message { get; }
}

If you want the inserted record get it by a separate query. If you want to access specific fields or create data object instances to submit into the pipeline do so in the Presentation layer.

You can then interact with the returned IDataResult in the UI. Here's a demo using Bootstrap alerts.

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

<div class="m-2 border border-2 border-dark rounded-3 m-2 p-3">
    <div class="mb-5">
        Data Edit form
    </div>
    <div class="m-2">
        <button class="btn btn-sm btn-success" @onclick=this.SuccessCommand>Successful Submit</button>
        <button class="btn btn-sm btn-primary" @onclick=this.SuccessCommandWithMessage>Successful Submit with A Message</button>
        <button class="btn btn-sm btn-danger" @onclick=this.FailureCommand>Fail Submit</button>
    </div>
</div>


<div>
    @if (!_dataResult.Successful)
    {
        <div class="alert alert-danger">@_dataResult.Message</div>
    }

    @if (_dataResult.Successful && _dataResult.Message is not null)
    {
        <div class="alert alert-success">@_dataResult.Message</div>
    }
</div>

@code {
    private IDataResult _dataResult = CommandResult.Success();

    private async Task SuccessCommand()
    {
        //fake a call into the data pipeline
        await Task.Delay(100);
        _dataResult = CommandResult.Success(56);
    }

    private async Task SuccessCommandWithMessage()
    {
        //fake a call into the data pipeline
        await Task.Delay(100);
        _dataResult = CommandResult.Success(56, $"Inserted Record with Id: {56}");
    }

    private async Task FailureCommand()
    {
        //fake a call into the data pipeline
        await Task.Delay(100);
        _dataResult = CommandResult.Failure("Something went wrong");
    }
}

enter image description here

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • Ok, thanks. Here interface is a part of the magic. Now about "and pass in requests and result objects, not raw data objects", if a need a property of the object for example, are you suggesting to add this property to the interface? – Manta Jul 06 '23 at 16:04
  • "if a need a property of the object for example". Get the object, you can then read off the property. In your code don't pass `categoryName` into the backend. Create a category and pass the object. – MrC aka Shaun Curtis Jul 06 '23 at 16:43