I want to use same contact form on multiple pages in my project.
I tried it on a sample project named FormInViewComponent
as below:
Controllers
> TestController.cs
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FormInViewComponent.Controllers
{
public class TestController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
Views
> Test
> Index.cshtml
(I called the ViewComponent
on this page)
@model FormModel
<div class="container">
<div class="row">
<div class="col-8">
<h3>Page content is here</h3>
</div>
<div class="col-4 bg-light p-3">
@await Component.InvokeAsync("SampleForm")
</div>
</div>
</div>
Models
> FormModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace FormInViewComponent.Models
{
public class FormModel
{
[Required]
public string Name { get; set; }
}
}
ViewComponents
> SampleFormViewComponent.cs
using FormInViewComponent.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FormInViewComponent.ViewComponents
{
public class SampleFormViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(FormModel formModel)
{
return await Task.FromResult(View(formModel));
}
}
}
Views
> Shared
> Components
> SampleForm
> Default.cshtml
@model FormModel
<h4>ViewComponent is here</h4>
<b class="text-info">Sample Form</b>
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="col-12">
<div class="form-group">
<label asp-for="Name"></label><br />
<input type="text" asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<p class="text-center">
<input class="btn btn-success" type="submit" value="Send" />
</p>
</div>
</form>
The most immediate issue is that you're trying to invoke the
SampleFormViewComponent
without any parameters—but yourInvokeAsync()
method requires aFormModel
parameter. As such, while it may be able to find yourSampleFormViewComponent
, it won't be able to executeInvokeAsync()
.Passing
FormModel
as a parameterTo remedy this, you must pass an instance of a
FormModel
to yourComponent.InvokeAsync()
call using the following syntax:Constructing
FormModel
inlineThe alternative, depending on your exact requirements, is to remove the
formModel
parameter, and instead construct your view component inside yourInvokeAsync()
method:Obviously, a view model will usually have data associated with it. You can pull that data from the current context (e.g., from the route parameters or query string), or use that information to lookup data from another location (e.g. a database).
Processing your form
This raises another issue, however: How are you expecting to process your form? If you're posting it to a separate controller action or e.g. web service, that's no problem. If you're expecting your view component to also process the results, as you might in a controller, you're going to run into difficulties. Notably, view components don't provide any type of routing or complex model binding. So while you can render the form using a view component, you should process the form using a controller or web service.