1

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>
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
  • Aside, could you edit your question to include an explanation of what's not working? E.g., are you getting an exception—and, if so, what is it? My assumption, based on your code, is that you're not getting an error, but also that your form is not rendering, since issues with `Component.InvokeAsync()` tend to fail silently. If so, that would be useful for respondents to know. In the meanwhile, I've contributed an answer based on what I'd expect reading through your code. – Jeremy Caney Dec 24 '20 at 22:51

1 Answers1

0

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.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
  • Ahmet, do you recall if this answer your question? If so, can you please be sure to mark it as the accepted answer? That will mark this question as resolved—and help boost my own reputation :) – Jeremy Caney Jul 23 '21 at 18:30