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
SampleFormViewComponentwithout any parameters—but yourInvokeAsync()method requires aFormModelparameter. As such, while it may be able to find yourSampleFormViewComponent, it won't be able to executeInvokeAsync().Passing
FormModelas a parameterTo remedy this, you must pass an instance of a
FormModelto yourComponent.InvokeAsync()call using the following syntax:Constructing
FormModelinlineThe alternative, depending on your exact requirements, is to remove the
formModelparameter, 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.