Home > OS >  EditForm on Razor component not firing OnValidSubmit
EditForm on Razor component not firing OnValidSubmit

Time:01-02

I have a simple razor component that accepts a comment from a user and then saves the comment. The Page that renders it is currently located at Pages >> Expeditions >> Index.cshtml. When I navigate to /Expeditions in a browser everything loads correctly and the OnValidSubmit works. When I navigate to /Expeditions/Index the page renders properly but the OnValidSubmit is never fired.

I'm guessing there is some type of magic that takes place when I leave out Index in the URL. I'm wondering what I am doing incorrectly here because if I put the component in any page other than an Index page, the Submit button doesn't fire the OnValidSubmit.

Here is the code... Index.cshtml

@page
@model Project1.com.Pages.Expeditions.IndexModel
@{
    ViewData["Title"] = "Home page";
}

@(await Html.RenderComponentAsync<ComposeCommentComponent>(RenderMode.ServerPrerendered, new { PostId = 1 }))

<script src="_framework/blazor.server.js"></script>

ComposeCommentComponent.razor

@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Forms
@using Project1.com.Models
@using Project1.com.Services
@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

@inject CommentService CommentService

<EditForm Model="@Comment" OnValidSubmit="OnValidSubmit">
    <div >
        <InputTextArea id="Comment" @bind-Value="@Comment.Comment"  rows="5" cols="65" placeholder="Leave a Comment!"></InputTextArea>
    </div>
    <button >Submit</button>
</EditForm>

@functions{
    public ClaimsPrincipal User { get; set; }

    protected override async void OnInitialized()
    {
        await base.OnInitializedAsync();

        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        User = authState.User;
    }
}

@code {
    [Parameter]
    public int PostId { get; set; }

    CommentModel Comment = new CommentModel();

    private async void OnValidSubmit()
    {
        // Update Database with New Comment
        CommentService.CreateComment(new CommentModel() { Username = User.Identity.Name, Comment=Comment.Comment, PostId=PostId});

        // Clear Comment
        Comment.Comment = "";

        // Notify Parent Component to Update Data.
        await OnNewComment.InvokeAsync(Comment.Id);
    }

    [Parameter]
    public EventCallback<int> OnNewComment { get; set; }
}

Startup.cs

using Project1.com.Data;
using Project1.com.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Project1.com
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddRazorPages();
            services.AddServerSideBlazor();

            services.AddTransient<ExpeditionService>();
            services.AddTransient<CommentService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages().RequireAuthorization(); 
                endpoints.MapBlazorHub();
            });
        }
    }
}

CodePudding user response:

If you check the page that doesn't work you'll find there are one or more blazor.server.js 404 errors. Probably this:

enter image description here

Your problem is that you are not specifying a base href for your application.

  • /Expeditions works because Blazor thinks you're in the root, but
  • /Expeditions/Index doesn't because it now tries to access resources from the /Expeditions subdirectory.

Blazor uses <base href="~/" /> to define where to get it's resources.

@page 
@using StackOverflow.Web.Components
@model StackOverflowAnswers.Pages.MeModel
@{
    ViewData["Title"] = "Home page";
}
<!DOCTYPE html>
<html lang="en">
<head>
    <base href="~/" />
</head>
<body>
    @(await Html.RenderComponentAsync<ComposeCommentComponent>(RenderMode.ServerPrerendered, new { PostId = 1 }))

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

Note: @enet's points in his answer still apply to clean up your code.

CodePudding user response:

This <button >Submit</button>

is lacking the type attribute with "submit" value. Add it like this and try:

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

The issue may be related, however, to:

private async void OnValidSubmit()

which should be

private async Task OnValidSubmit()

UPDATE:

You've got it all mixed up... Your Razor component, which contains an EditForm component is embedded in a Razor Pages App page. Now when you click on the OnValidSubmit button, your code creates a comment. So far so good, provided you employ both changes I've introduced in my answer. But, alas, we are stuck here: your Index page displays the Razor component embedded with input elements cleared. You may say, but what's about this await OnNewComment.InvokeAsync(Comment.Id); Yes, what's about that ? Is the call back for OnNewComment defined in the Razor page. If true, it should not. Besides, you pass only a single parameter named PostId (RenderMode.ServerPrerendered, new { PostId = 1 }) If the call back for OnNewComment is defined in another Razor component, perhaps a parent of the ComposeCommentComponent component, then the parent component should contain the definition for the call back, which should be assigned to a parameter attribute exposed by the ComposeCommentComponent component.

Here's some code to exemplify how to do that:

Parent.razor

-- Instantiate the ComposeCommentComponent within the Parent -- component

<ComposeCommentComponent OnNewComment="MyCallback" />

@code
{
   // Define the call back method
   // This method will be called when this code is executed

   // Notify Parent Component to Update Data.
   //  await OnNewComment.InvokeAsync(Comment.Id);
   private void MyCallback (int CommentID)
   {
  
   }
}

Note:

@(await Html.RenderComponentAsync<ComposeCommentComponent>(RenderMode.ServerPrerendered, new { PostId = 1 }))

Should be:

@(await Html.RenderComponentAsync<Parent>(RenderMode.ServerPrerendered, new { PostId = 1 }))

Warning: You must apply the changes I've made above the UPDATE section.

  • Related