abpframework / abp

Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation.
https://abp.io
GNU Lesser General Public License v3.0
12.92k stars 3.45k forks source link

Blazor Form Validation utilizing User friendly errors/exceptions #9477

Open MichalCadecky opened 3 years ago

MichalCadecky commented 3 years ago

Hello!

TL;DR; It would be nice if ABP Framework could provide means of displaying errors/exceptions in Form Validation instead of error dialog until there is an option to hook asynchronous validation in Blazorise.

Currently Blazor is using Blazorise Validation/Validations components to perform Form Validation utilizing Data Annotations from Models' properties. Or you can replace the Data Annotations validator with custom one (dropping all validations coming from Data Annotations). This is of course limitation of Blazorise and I don't want to talk about it too much.

What I would like to talk about is utilizing the errors or exceptions coming from API and propagate their (localized) messages to Blazorise Validation instead of showing the error dialog (if it is being opted for). I haven't found any documentation or code allowing us to do this so I guess this is not currently possible.

Lets say we have an Entity with Name property which has to be unique. Normally you would implement this kind of validation at domain level in the Entity itself or respective EntityManager domain service.

public async Task<Entity> CreateAsync(string name)
{
    if (await _entityRepository.AnyAsync(lu => lu.Name == name))
    {
        throw new BusinessException(ErrorCodes.DuplicateName).WithData("Name", name);
    }

    return new Entity(this.GuidGenerator.Create(), name) { TenantId = this.CurrentTenant.Id };
}

Normally the BusinessException would be caught and if there is respective localization message for the DuplicateName it would be shown as an error dialog. The error is clearly related to the Name field. What is really not nice is that after you close the dialog you will see all the fields with green border and a checkmark with no further information on what is wrong. That is why we would like to show this error message not in the dialog but as a classic validation feedback under respective form field.

Blazorise does not support much extension possibilities for the validation (especially, when the validations needs to be performed asynchronously). We are currently overcoming this limitation overriding the CreateEntityAsync or UpdateEntityAsync.

What we are trying to achieve is to transform this exception into a validation message. I have tried to catch the BusinessException by the type but this does not seems to be possible in Blazor.

private Validation NewNameValidation; // this is referenced in Razor part

protected override async Task CreateEntityAsync()
{
    try
    {
        await base.CreateEntityAsync();
    }
    catch (BusinessException e) when (e.Code == ErrorCodes.DuplicateName)
    {
        this.NewNameValidation.NotifyValidationStatusChanged(ValidationStatus.Error, new string[] { L["Validation:DuplicateName"] });
    }
}

So we ended up with adding validation API to the Application Service and calling the API method.

private Validation NewNameValidation; // this is referenced in Razor part

protected override async Task CreateEntityAsync()
{
    try
    {
        var hasError = false;
        if (await this.AppService.NameExistsAsync(this.NewEntity.Name))
        {
            this.NewNameValidation.NotifyValidationStatusChanged(ValidationStatus.Error, new string[] { L["Validation:DuplicateName"] });
            hasError = true;
        }

        if (!hasError)
        {
            await base.CreateEntityAsync();
        }
    }
    catch (Exception e)
    {
        await this.HandleErrorAsync(e);
    }
}

I really like the idea that we are adding the client-side validation utilizing APIs but the other code that has to be added to the codebehind part of the page is kind of inconvenient. Unfortunattely there is no way of hooking this asynchronous code to the Blazorise Validation for specific field without dropping the Data Annotations validation support.

Now finally I am getting to the proposal of our idea. I am not yet familiar with the Message Service responsible for identifying the type of exception (lookup of message based on code) but the AbpCrudPageBase might be exntended with functionality to allow regisration of Error Codes and Validation components to forward the message into provided Validation component.

Registration could look like this:

public partial class EntityManagement
{
    private Validation NewNameValidation; // this is referenced in Razor part

    public EntityManagement()
    {
        this.AssignErrorCodeToValidation(ErrorCodes.DuplicateName, this.NewNameValidation);
    }
}

The HandleErrorAsync method will be able to lookup the code from the BusinessException in the registration dictionary and if the code is found it will redirect the message in the assigned Validation.

This of course is not utilizing the API async calls but at least it will not be necessary to override the CreateEntityAsync or UpdateEntityAsync methods anymore. But even without the API validation methods it might be something useful.

I might be able to provide a pull request with functionality based on the basic idea. But I would like to get some feedback first - maybe there is already something being worked on by ABP devs or others.

ebicoglu commented 3 years ago

Thanks for the feedback. We'll consider implementing it in the next milestones