How to create shared form in ASP.NET CORE Razor Pa

2020-07-17 07:02发布

问题:

I have to create a reusable form in my page header. It should be displayed on each page. Simple input and submit button that will send a POST request.

Options that I'm aware of are partial views or view components. I checked a documentation about view components but there is no mention that it works with forms. Only InvokeAsync method is available to initialize a view.

With partial view, it may be hard to define its page model and I don't understand where to put its POST handler.

One other option I see, somehow to place a form directly on _Layout.cshtml, but, again, it doesn't have its page model (afaik),

So what is a way to create a shared form, and where POST request should be handled?

回答1:

You should use a view component, as this allows you to have a somewhat self-contained model and view interaction.

public class SharedFormViewComponent : ViewComponent
{
    public Task<IViewComponentResult> InvokeAsync() =>
        Task.FromResult(View(new SharedFormViewModel()));
}

Then, put your form HTML code in Views\Shared\Components\SharedForm\Default.cshtml. For your form's action, you'll need to specify a route. More on that in a sec. Then, to display your form:

@await Component.InvokeAsync("SharedForm")

Now, as you've somewhat discerned, view components can't be posted to. It's not an issue of "not supporting forms"; they're literally not part of the request pipeline, and therefore can't respond to requests such as a POST. You'll need a distinct action that will handle the POST on some controller. On your component's form tag, then:

<form asp-action="SharedFormHandlerAction" asp-controller="Foo" asp-area="" method="post">

The asp-area attribute should be provided just in case this is used in the context of different areas.

You'll also need a "return URL". This will be the URL of the current page, so that after the user successfully posts the form, they'll go back to the page they submitted it from. You can achieve that by adding a hidden input to your form:

<input type="hidden" name="returnUrl" value="@(Context.Request.Query["returnUrl"].FirstOrDefault() ?? (Context.Request.Path + Context.Request.QueryString))" />

Your handler action should then take a param like string returnUrl = null, and on success you should do:

return !string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)
    ? Redirect(returnUrl)
    : RedirectToAction("SomeDefaultAction");

Where things get a little tricky is in handling validation errors. Since you're posting to a different action, you can't return the previous view the user was on to show the validation errors within the from in the layout or whatever. Instead, you'll want a view specific to this handler action which will simply be your shared form. You can use the view for your view component here as a partial:

<partial name="~\Views\Shared\Components\SharedForm\Default.cshtml" />


回答2:

If you want to use a PageModel, you could do so in a BasePageModel class that inherits from PageModel and that all your pages inherit from. You can use a named handler (https://www.learnrazorpages.com/razor-pages/handler-methods#named-handler-methods) in the BasePageModel class to process the form submission. Add the form directly to the Layout, which will need an @model directive for your BasePageModel type.

public class BasePageModel : PageModel
{
    [BindProperty]
    public string SearchString { get; set; }

    public void  OnPostBaseSearch()
    {
        // process the search
    }
}