How can I reuse a contact form in a ViewComponent?

387 views Asked by At

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>
1

There are 1 answers

0
Jeremy Caney On

The most immediate issue is that you're trying to invoke the SampleFormViewComponent without any parameters—but your InvokeAsync() method requires a FormModel parameter. As such, while it may be able to find your SampleFormViewComponent, it won't be able to execute InvokeAsync().

Passing FormModel as a parameter

To remedy this, you must pass an instance of a FormModel to your Component.InvokeAsync() call using the following syntax:

@await Component.InvokeAsync("SampleForm", new { formModel = new FormModel() })

Constructing FormModel inline

The alternative, depending on your exact requirements, is to remove the formModel parameter, and instead construct your view component inside your InvokeAsync() method:

namespace FormInViewComponent.ViewComponents
{
    public class SampleFormViewComponent : ViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var formModel = new FormModel();
            return await Task.FromResult(View(formModel));
        }
    }
}

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.