dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.6k stars 25.29k forks source link

Serverside validation #17377

Closed besso-betch closed 4 years ago

besso-betch commented 4 years ago

Can you please show us an example on how to do server side validation (by passing the EditContext)?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

guardrex commented 4 years ago

Hello @bessobesso ... This is related to and can be worked with #17360. I'll also work #17362 at the same time. We'll get a priority for these three this Friday.

When we cover it, it will be a more specific example of the existing one in the opening section of the topic ...

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">

    ...

    <button type="submit">Submit</button>
</EditForm>

@code {
    private Starship starship = new Starship();
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(starship);
    }

    private async Task HandleSubmit()
    {
        var isValid = editContext.Validate() && 
            await ServerValidate(editContext);

        if (isValid)
        {
            ...
        }
        else
        {
            ...
        }
    }

    private async Task<bool> ServerValidate(EditContext editContext)
    {
        var serverChecksValid = ...

        return serverChecksValid;
    }
}
ptrotter1 commented 4 years ago

I would also like to see a more detailed example on how to use the editContext in the ServerValidate method. Thanks.

bdnts commented 4 years ago

Hey @guardrex I just built a full example for that other project we've been working on. I added ServerValidate() for the SignIn.razor component. While researching I came across this page. You want me to post the link again, or paste the page or something?

guardrex commented 4 years ago

I have it, and I haven't forgotten about the other issue that you opened pertaining to this topic. I probably won't reach these issues until after the 3.2 release in May, then there should be a period of a month or two of low engineering churn that will allow me to catch up.

btw -- The Blazor work is tracked on the Blazor.Docs project ...

https://github.com/dotnet/AspNetCore.Docs/projects/35

bdnts commented 4 years ago

NP. I've seen the number of PRs with your name on it. You are a busy man. Just hope I've been a little helpful. Cheers.

alpachinois commented 4 years ago

Hi,

I have the same problem. I'm using the free devexpress blazor components. Following the responses here and documentation, only client validation with DataAnnotationsValidator works for me. Could you check where could be my mistake?

        <EditForm Model="@_identifiantForm" OnValidSubmit="@Search" Context="_editContext">
            <DataAnnotationsValidator />
            <DxFormLayout>
                <DxFormLayoutItem Caption="Siren:" ColSpanMd="12">
                    <Template Context="Siren">
                        <DxTextBox @bind-Text="@_identifiantForm.Siren" />
                        <DxButton RenderStyle="ButtonRenderStyle.Primary" Title="Rechercher les informations à partir de ce Siren." IconCssClass="rechercher" SubmitFormOnClick="true" />
                    </Template>
                </DxFormLayoutItem>
                <DxFormLayoutItem ColSpanMd="12">
                    <Template>
                        <ValidationMessage For="@(() => _identifiantForm.Siren)" />
                        <ValidationSummary Model="@_identifiantForm" />
                    </Template>
                </DxFormLayoutItem>
            </DxFormLayout>
        </EditForm>
@code{

   EditContext _editContext;
   private IdentifiantForm _identifiantForm = new IdentifiantForm();
   protected override async Task OnInitializedAsync()
   {
       _editContext = new EditContext(_identifiantForm);

   }

async Task Search()
{
    _etablissements.Clear();
    var response = await MyService.GetEtablissementsBySiren(_identifiantForm.Siren);
    bool clientValidation = _editContext.Validate();
    bool serverValidation = await ServerValidate(_editContext, response);
    if(clientValidation && serverValidation)
    {
        _etablissements.AddRange(response.OrderByDescending(x => x.IsSiege && x.IsActive));
        popupVisibleSearch = true;
        PopupVisible = false;
        SelectedEtablissement = _etablissements.First();
        InvokeAsync(StateHasChanged);
    }
}
private async Task<bool> ServerValidate(EditContext _editContext, IEnumerable<Etablissement> etablissements )
{
    var validationErrors = new ValidationMessageStore(_editContext);
    if (etablissements.Count() == 0)
    {
        validationErrors.Add(_editContext.Field("Siren"), $"no etablissement founded for {_identifiantForm.Siren}");
        _editContext.NotifyValidationStateChanged();
        return false;
    }
    return true;
}

}
JasonImageOne commented 4 years ago

Do not use both EditForm.EditContext and EditForm.Model. The documentation could be improved by explaining the advantages of EditContext over Model. EditContext is what gives you the ability to use OnFieldChanged which is a very useful event.

guardrex commented 4 years ago

Thanks @JasonImageOne ... I'll get to this as soon as I can. I'm buried at the moment under high priority issues for the 3.2 release and digging my way out ⛏️. This issue is visible from the bottom of the topic, so readers will hopefully 🤞 find it as they consume what we have thus far.

alpachinois commented 4 years ago

[EDIT by guardrex to fix the code block]

Do not use both EditForm.EditContext and EditForm.Model. The documentation could be improved by explaining the advantages of EditContext over Model. EditContext is what gives you the ability to use OnFieldChanged which is a very useful event.

Thanks to you, I resolve my issue.

I could add this post from stackoverflow:

https://stackoverflow.com/a/61359102/13400000

Once a message is added to a field (validationErrors.Add(field, "This data point already exists, please type a different one");) The message will persist, and future Submits will be ignored as Invalid.

So I have to handle OnInvalidSubmit and OnValidSubmit events. In the OnInvalidSubmit event, I create a new instance of context. I can add the code _editContext.Validate() must be raised twice in order to be exact, indeed the first _editContext.Validate() always return true, I don't know why...

<EditForm OnValidSubmit="@Search" OnInvalidSubmit="@InvalidSearch" EditContext="@_editContext">
    <DIDataAnnotationsValidator />
    <DxFormLayout>...
        <DxFormLayoutItem ColSpanMd="12">
            <Template Context="Siren">
                <ValidationMessage For="@(() => _identifiantForm.Siren)" />
                <ValidationSummary Model="@_identifiantForm" />
            </Template>
        </DxFormLayoutItem>
    </DxFormLayout>
</EditForm>

@code {
    async Task Search()
    {
        _etablissements.Clear();
        bool clientValidation = _editContext.Validate();
        if (clientValidation)
        {
            var response = await GirardinService.GetEtablissementsBySiren(_identifiantForm.Siren);
            bool serverValidation = await ServerValidate(_editContext, response);
            if (serverValidation)
            {
                previousServerValid = true;
                _etablissements.AddRange(response.OrderByDescending(x => x.IsSiege && x.IsActive));
                popupVisibleSearch = true;
                PopupVisible = false;
                SelectedEtablissement = _etablissements.First();
                InvokeAsync(StateHasChanged);
            }
            else
            {
                previousServerValid = false;
            }
        }
    }

    async Task InvalidSearch()
    {
        if (!previousServerValid)
        {
            _editContext = new EditContext(_identifiantForm);
            if (_editContext.Validate())
            {
                await Search();
            }
        }
    }
}