FluentValidation.Blazor icon indicating copy to clipboard operation
FluentValidation.Blazor copied to clipboard

Suitable for Blazor webassembly?

Open mkopara opened this issue 3 years ago • 2 comments

Is this project suitable for Blazor Webassembly? I'm trying to follow instruction, but I am unable to get it to work

mkopara avatar Mar 16 '21 08:03 mkopara

I'm using it in Blazor WASM without any issues. Can you provide more details about the problem you're having?

Akinzekeel avatar Mar 25 '21 20:03 Akinzekeel

I've been trying to get it to work in Blazor WASM as well, but I'm getting an exception saying a method is not found. I've build a model, an abstract validator for the model, and I'm using the model in a component used on the page which passes the model as a parameter to the component. Here are some examples of my code:

Model: `public class Period:BaseModel, ICreatedModified { public Period() : base() { Created=DateTimeOffset.Now; Modified=DateTimeOffset.Now; Folders = new List<Folder>(); }

    public DateTime? PeriodStart { get; set; }
    public DateTime? PeriodEnd { get; set; }
    public string Name { get; set; }
    public List&lt;Folder&gt; Folders { get; set; }
    public DateTimeOffset? Created { get; set; }
    public DateTimeOffset? Modified { get; set; }
}`

Validator: public class PeriodValidator:AbstractValidator&lt;Period&gt; { public PeriodValidator() { RuleFor(p =&gt; p.Name).NotEmpty().MaximumLength(50).MinimumLength(5); RuleFor(p =&gt; p.PeriodStart).NotEmpty().LessThanOrEqualTo(p =&gt; p.PeriodEnd); RuleFor(p =&gt; p.PeriodEnd).NotEmpty(); RuleFor(p =&gt; p.ItemId).NotEmpty(); } }

Host Page (abbreviated): `@page "/budget/{id}" @inject HttpClient http @inject NavigationManager nav

@if (IsLoaded) { <PeriodEditorDialog Period="@SelectedPeriod" ShowDialog="@_showPeriodDialog" CloseAndAbandonChanges="OnCancelPeriod" SaveChanges="OnSavePeriod"></PeriodEditorDialog> } else { <LoadingPanel ShowLoading="@_isLoading"></LoadingPanel> }

@code { Budget _budget; bool _isLoading = true; protected bool IsLoaded => _budget != null; bool _showPeriodDialog = false; Guid _selectedPeriodId = Guid.Empty;

protected override async Task OnInitializedAsync()
{
    await LoadBudget();
}

private async Task LoadBudget()
{
    var result = await http.GetFromJsonAsync&lt;Budget&gt;($"api/budget/{Id}");
    _budget = result;
}

Period SelectedPeriod =&gt; _selectedPeriodId != Guid.Empty
    ? _budget.Periods.FirstOrDefault(p =&gt; p.ItemId.Equals(_selectedPeriodId))
    : new Period();

protected void OnAddPeriod()
{
    _showPeriodDialog = true;
}

protected void OnEditPeriod(Guid id)
{
    _selectedPeriodId = id;
    _showPeriodDialog = true;
}

protected async Task OnSavePeriod(Period updatedPeriod)
{
/* code omitted */
}

protected void OnCancelPeriod()
{
    _selectedPeriodId = Guid.Empty;
    _showPeriodDialog = false;
}

[Parameter]
public string Id { get; set; }`

Editor Dialog Component: `@if (ShowDialog) { <div class="period-editor-dialog"> <EditForm Model="@Period"> <FluentValidator/> <div class="dialog"> <div class="dialog-title">Period Information</div> <div class="dialog-body"> <div class="mb-3"> <label for="periodName" class="form-label">Period Name</label> <InputText id="periodName" DisplayName="Period Name" @bind-Value="Period.Name" class="form-control"></InputText> <ValidationMessage For="() => Period.Name"></ValidationMessage> </div> <div class="mb-3"> <label for="periodStart" class="form-label">Period Start</label> <InputDate id="periodStart" DisplayName="Period Start" @bind-Value="Period.PeriodStart" class="form-control"></InputDate> <ValidationMessage For="() => Period.PeriodStart"></ValidationMessage> </div> <div class="mb-3"> <label for="periodEnd" class="form-label">Period End</label> <InputDate id="periodEnd" DisplayName="Period End" @bind-Value="Period.PeriodEnd" class="form-control"></InputDate> <ValidationMessage For="() => Period.PeriodEnd"></ValidationMessage> </div> </div> <div class="dialog-actions"> <button type="button" class="btn btn-secondary" @onclick="@OnClickCancel">Cancel</button> <button type="submit" class="btn btn-primary">Save</button> </div> </div> </EditForm> </div> }

@code { [Parameter] public bool ShowDialog { get; set; }

[Parameter]
public Period Period { get; set; }

[Parameter]
public EventCallback CloseAndAbandonChanges { get; set; }

[Parameter]
public EventCallback&lt;Period&gt; SaveChanges { get; set; }

protected async Task OnClickCancel()
{
    Period = new Period();
    await CloseAndAbandonChanges.InvokeAsync();
}

public async Task SubmitFormAsync()
{
    await SaveChanges.InvokeAsync(Period);
}

} `

Finally, in my startup I have used the FluentValidation.DependencyInjection to add the validators. `public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app");

        builder.Services.AddScoped(sp =&gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

        builder.Services.AddValidatorsFromAssemblyContaining&lt;Period&gt;();

        await builder.Build().RunAsync();
    }
}`

The error: crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Method not found: System.Collections.Generic.IList1&lt;FluentValidation.Results.ValidationFailure&gt; FluentValidation.Results.ValidationResult.get_Errors() System.MissingMethodException: Method not found: System.Collections.Generic.IList1<FluentValidation.Results.ValidationFailure> FluentValidation.Results.ValidationResult.get_Errors() at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[<ValidateModel>d__25](<ValidateModel>d__25& stateMachine) at Microsoft.AspNetCore.Components.Forms.FluentValidator.ValidateModel(EditContext editContext, ValidationMessageStore messages) at Microsoft.AspNetCore.Components.Forms.FluentValidator.<>c__DisplayClass24_0.<AddValidation>b__0(Object sender, ValidationRequestedEventArgs eventArgs) at Microsoft.AspNetCore.Components.Forms.EditContext.Validate() at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync() at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

cquick avatar Jun 02 '21 13:06 cquick