Problem
How to reuse parts of web pages using View Components in ASP.NET Core MVC.
Solution
In an empty project, update Startup class to add services and middleware for MVC.
- public void ConfigureServices(
- IServiceCollection services)
- {
- services.AddScoped<IAddressFormatter, AddressFormatter>();
- services.AddMvc();
- }
-
- public void Configure(
- IApplicationBuilder app,
- IHostingEnvironment env)
- {
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
- }
Add a Model to display in the View.
- public class EmployeeViewModel
- {
- public int Id { get; set; }
- public string Firstname { get; set; }
- public string Surname { get; set; }
- }
Add a Controller with action method returning ViewResult.
- public IActionResult Index()
- {
- var model = new EmployeeViewModel
- {
- Id = 1,
- Firstname = "James",
- Surname = "Bond"
- };
- return View(model);
- }
Add a parent View named Index.cshtml.
- @using Fiver.Mvc.ViewComponents.Models.Home
- @model EmployeeViewModel
-
- <div style="border: 1px solid black; margin: 5px">
- <strong>Employee Details (view)</strong>
-
- <p>Id: @Model.Id</p>
- <p>Firstname: @Model.Firstname</p>
- <p>Surname: @Model.Surname</p>
-
- @await Component.InvokeAsync("Address", new { employeeId = Model.Id })
- </div>
-
- @await Component.InvokeAsync("UserInfo")
Add a View Component’s Model.
- public class AddressViewModel
- {
- public int EmployeeId { get; set; }
- public string Line1 { get; set; }
- public string Line2 { get; set; }
- public string Line3 { get; set; }
- public string FormattedValue { get; set; }
- }
Add a View Component’s class.
- [ViewComponent(Name = "Address")]
- public class AddressComponent : ViewComponent
- {
- private readonly IAddressFormatter formatter;
-
- public AddressComponent(IAddressFormatter formatter)
- {
- this.formatter = formatter;
- }
-
- public async Task InvokeAsync(int employeeId)
- {
- var model = new AddressViewModel
- {
- EmployeeId = employeeId,
- Line1 = "Secret Location",
- Line2 = "London",
- Line3 = "UK"
- };
- model.FormattedValue =
- this.formatter.Format(model.Line1, model.Line2, model.Line3);
- return View(model);
- }
- }
Add a View Component’s View named as Default.cshtml.
- @using Fiver.Mvc.ViewComponents.Models.Home
- @model AddressViewModel
-
- <div style="border: 1px dashed red; margin: 5px">
- <strong>Address Details (view component in Views/Home)</strong>
-
- <p>Employee: @Model.EmployeeId</p>
- <p>Line1: @Model.Line1</p>
- <p>Line2: @Model.Line2</p>
- <p>Line3: @Model.Line3</p>
- <p>Full Address: @Model.FormattedValue</p>
- </div>
Discussion
View Components are special type of Views rendered inside other Views. They are useful for reusing parts of a View or splitting a large View into smaller components.
Unlike Partial Views, View Components do not rely on Controllers. They have their own class to implement the logic to build component’s model and Razor View page to display HTML/CSS.
I like to think of them as mini-controllers, although this is not strictly correct but helps conceptualize their usage. Unlike Controllers, they do not handle HTTP requests or have Controller lifecycle, which means they can’t rely on filters or model binding.
View Components can utilize dependency injection, which makes them powerful and testable.
Creating
There are a few ways to create View Components. I’ll discuss the most commonly used (and best in my view) option.
- Create a class (anywhere in your project) and inherit from ViewComponent abstract class.
- Name of the class, by convention, ends with ViewComponent.
- Create a method called InvokedAsync() that returns Task<IViewComponentResult>.
- This method can take any number of parameters, which will be passed when invoking the component (see Invoking section below).
- Create Model e.g. via database etc.
- Call IViewComponentResult by calling the View() method of base ViewComponent. You could pass your model to this method.
- Optionally you could specify the name of razor page (see Discovery section below).
The base ViewComponent class gives access to useful details (via properties) like HttpContext, RouteData, IUrlHelper, IPrincipal, and ViewData.
Invoking
View Components can be invoked by either,
- Calling @await Component.InvokeAsync(component, parameters) from the razor view.
- Returning ViewComponent(component, parameters) from a controller.
Here, “component” is a string value refereeing to the component class.
InvokeAsync() method can take any number of parameters and is passed using an anonymous object when invoking the View Component.
Below is an example of second option above. Notice that the second action method doesn’t work because the Razor page for the component is not under Controller’s Views folder,
- public class ComponentsController : Controller
- {
- public IActionResult UserInfo()
- {
-
- return ViewComponent("UserInfo");
- }
-
- public IActionResult Address()
- {
-
- return ViewComponent("Address", new { employeeId = 5 });
- }
- }
Discovery
MVC will search for the razor page for View Component in the following sequence,
- Views/[controller]/Components/[component]/[view].cshtml
- Views/Shared/Components/[component]/[view].cshtml
Here matches either,
- Name of the component class, minus the ViewComponent suffix if used.
- Value specified in [ViewComponent] attribute applied to component class.
Also, [view] by default is Default.cshtml, however, it can be overwritten by returning a different name from the component class. Below the component returns a view named Info.cshtml,
- public class UserInfoViewComponent : ViewComponent
- {
- public async Task InvokeAsync()
- {
- var model = new UserInfoViewModel
- {
- Username = "[email protected]",
- LastLogin = DateTime.Now.ToString()
- };
- return View("info", model);
- }
- }
jQuery
You could access View Components via jQuery as well. To do so enable the use of Static Files in Startup,
- public void Configure(
- IApplicationBuilder app,
- IHostingEnvironment env)
- {
- app.UseStaticFiles();
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
- }
Add jQuery script file to wwwroot and use it in a page
- <html>
- <head>
- <meta name="viewport" content="width=device-width" />
- <title>ASP.NET Core View Components</title>
-
- <script src="~/js/jquery.min.js"></script>
-
- </head>
- <body>
- <div>
- <strong>ASP.NET Core View Components</strong>
-
- <input type="button" id="GetViewComponent" value="Get View Component" />
-
- <div id="result"></div>
- </div>
- <script>
- $(function () {
- $("#GetViewComponent").click(function () {
-
- $.ajax({
- method: 'GET',
- url: '@Url.Action("UserInfo", "Components")'
- }).done(function (data, statusText, xhdr) {
- $("#result").html(data);
- }).fail(function (xhdr, statusText, errorText) {
- $("#result").text(JSON.stringify(xhdr));
- });
-
- });
- });
- </script>
- </body>
- </html>
Source Code
GitHub