dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.37k stars 9.99k forks source link

"There is no event handler associated with this event" error when submitting a Blazor form #49145

Closed danroth27 closed 1 year ago

danroth27 commented 1 year ago

I'm working on a Blazor version of the Razor Pages movie app using .NET 8 Preview 6. When I try to create a form in Blazor for editing a movie, I get the following error when submitting the form:

System.ArgumentException: There is no event handler associated with this event. EventId: '1'. (Parameter 'eventHandlerId')
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetRequiredEventCallback(UInt64 eventHandlerId)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs, Boolean quiesce)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.DispatchCapturedEvent()
   at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore()
   at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<<InvokeAsync>b__9_0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Here's the code for the Edit.razor page:

@page "/movies/edit"
@inject BlazorMovieContext DB
@inject NavigationManager NavigationManager

<PageTitle>Edit</PageTitle>

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
@if (Movie is null)
{
    <p><em>Loading...</em></p>
}
else
{
    <div class="row">
        <div class="col-md-4">
            <EditForm method="post" Model="Movie" OnValidSubmit="UpdateMovie">
                <DataAnnotationsValidator />
                <ValidationSummary />
                <input type="hidden" name="Movie.Id" value="@Movie.Id" />
                <div class="mb-3">
                    <label for="title" class="form-label">Title:</label>
                    <InputText id="title" @bind-Value="Movie.Title" class="form-control" />
                    <ValidationMessage For="() => Movie.Title" class="text-danger" />
                </div>
                <div class="mb-3">
                    <label for="release-date" class="form-label">Release date:</label>
                    <InputDate id="release-date" @bind-Value="Movie.ReleaseDate" class="form-control" />
                    <ValidationMessage For="() => Movie.ReleaseDate" class="text-danger" />
                </div>
                <div class="mb-3">
                    <label for="genre" class="form-label">Genre:</label>
                    <InputText id="genre" @bind-Value="Movie.Genre" class="form-control" />
                    <ValidationMessage For="() => Movie.Genre" class="text-danger" />
                </div>
                <div class="mb-3">
                    <label for="price" class="form-label">Price:</label>
                    <InputNumber id="price" @bind-Value="Movie.Price" min="0" step="0.01" class="form-control" />
                    <ValidationMessage For="() => Movie.Price" class="text-danger" />
                </div>
                <button type="submit" class="btn btn-primary">Save</button>
            </EditForm>
        </div>
    </div>
}

<div>
    <a href="/movies">Back to List</a>
</div>

@code {
    [SupplyParameterFromQuery]
    public int Id { get; set; }

    [SupplyParameterFromForm]
    public Movie? Movie { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Movie = await DB.Movie.FirstOrDefaultAsync(m => m.Id == Id);

        if (Movie is null)
        {
            // Need a way to trigger a 404 here
            // https://github.com/dotnet/aspnetcore/issues/48983#issuecomment-1607617945
        }
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task UpdateMovie()
    {
        DB.Attach(Movie!).State = EntityState.Modified;

        try
        {
            await DB.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie!.Id))
            {
                // Need a way to trigger a 404 here
                // https://github.com/dotnet/aspnetcore/issues/48983#issuecomment-1607617945
            }
            else
            {
                throw;
            }
        }

        // Currently navigating throws: https://github.com/dotnet/aspnetcore/issues/49143
        NavigationManager.NavigateTo("/movies");
    }

    bool MovieExists(int id)
    {
        return DB.Movie.Any(e => e.Id == id);
    }
}
SteveSandersonMS commented 1 year ago

The problem reported here no longer repros (at least, when https://github.com/dotnet/aspnetcore/pull/49340 is merged).

However, the logic in the sample is buggy. In OnInitializedAsync, it overwrites its own [SupplyParameterFromForm] parameter, which will erase the incoming form data.

SteveSandersonMS commented 1 year ago

Fixed in https://github.com/dotnet/aspnetcore/pull/49340