Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
View components are similar to partial views, but they're much more powerful. View components don't use model binding, they depend on the data passed when calling the view component. This article was written using controllers and views, but view components work with Razor Pages.
A view component:
View components are intended anywhere reusable rendering logic that's too complex for a partial view, such as:
A view component consists of two parts:
Like controllers, a view component can be a POCO, but most developers take advantage of the methods and properties available by deriving from ViewComponent.
When considering if view components meet an app's specifications, consider using Razor components instead. Razor components also combine markup with C# code to produce reusable UI units. Razor components are designed for developer productivity when providing client-side UI logic and composition. For more information, see ASP.NET Core Razor components. For information on how to incorporate Razor components into an MVC or Razor Pages app, see Integrate ASP.NET Core Razor components with MVC or Razor Pages.
This section contains the high-level requirements to create a view component. Later in the article, we'll examine each step in detail and create a view component.
A view component class can be created by any of the following:
[ViewComponent]
attribute, or deriving from a class with the [ViewComponent]
attributeViewComponent
Like controllers, view components must be public, non-nested, and non-abstract classes. The view component name is the class name with the ViewComponent
suffix removed. It can also be explicitly specified using the Name property.
A view component class:
To prevent a class that has a case-insensitive ViewComponent
suffix from being treated as a view component, decorate the class with the [NonViewComponent]
attribute:
using Microsoft.AspNetCore.Mvc;
[NonViewComponent]
public class ReviewComponent
{
public string Status(string name) => JobStatus.GetCurrentStatus(name);
}
A view component defines its logic in an:
InvokeAsync
method that returns Task<IViewComponentResult>
.Invoke
synchronous method that returns an IViewComponentResult.Parameters come directly from invocation of the view component, not from model binding. A view component never directly handles a request. Typically, a view component initializes a model and passes it to a view by calling the View
method. In summary, view component methods:
InvokeAsync
method that returns a Task<IViewComponentResult>
or a synchronous Invoke
method that returns an IViewComponentResult
.The runtime searches for the view in the following paths:
The search path applies to projects using controllers + views and Razor Pages.
The default view name for a view component is Default
, which means view files will typically be named Default.cshtml
. A different view name can be specified when creating the view component result or when calling the View
method.
We recommend naming the view file Default.cshtml
and using the Views/Shared/Components/{View Component Name}/{View Name} path. The PriorityList
view component used in this sample uses Views/Shared/Components/PriorityList/Default.cshtml
for the view component view.
To customize the view search path, modify Razor's ViewLocationFormats collection. For example, to search for views within the path /Components/{View Component Name}/{View Name}
, add a new item to the collection:
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews()
.AddRazorOptions(options =>
{
options.ViewLocationFormats.Add("/{0}.cshtml");
});
builder.Services.AddDbContext<ToDoContext>(options =>
options.UseInMemoryDatabase("db"));
var app = builder.Build();
// Remaining code removed for brevity.
In the preceding code, the placeholder {0}
represents the path Components/{View Component Name}/{View Name}
.
To use the view component, call the following inside a view:
@await Component.InvokeAsync("Name of view component",
{Anonymous Type Containing Parameters})
The parameters are passed to the InvokeAsync
method. The PriorityList
view component developed in the article is invoked from the Views/ToDo/Index.cshtml
view file. In the following code, the InvokeAsync
method is called with two parameters:
</table>
<div>
Maximum Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
A View Component can be invoked as a Tag Helper:
<div>
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@{
int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
bool isDone = Convert.ToBoolean(ViewData["isDone"]);
}
<vc:priority-list max-priority=maxPriority is-done=isDone>
</vc:priority-list>
</div>
Pascal-cased class and method parameters for Tag Helpers are translated into their kebab case. The Tag Helper to invoke a view component uses the <vc></vc>
element. The view component is specified as follows:
<vc:[view-component-name]
parameter1="parameter1 value"
parameter2="parameter2 value">
</vc:[view-component-name]>
To use a view component as a Tag Helper, register the assembly containing the view component using the @addTagHelper
directive. If the view component is in an assembly called MyWebApp
, add the following directive to the _ViewImports.cshtml
file:
@addTagHelper *, MyWebApp
A view component can be registered as a Tag Helper to any file that references the view component. See Managing Tag Helper Scope for more information on how to register Tag Helpers.
The InvokeAsync
method used in this tutorial:
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
In the preceding markup, the PriorityList
view component becomes priority-list
. The parameters to the view component are passed as attributes in kebab case.
View components are typically invoked from a view, but they can be invoked directly from a controller method. While view components don't define endpoints like controllers, a controller action that returns the content of a ViewComponentResult
can be implemented.
In the following example, the view component is called directly from the controller:
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
return ViewComponent("PriorityList",
new {
maxPriority = maxPriority,
isDone = isDone
});
}
Download, build and test the starter code. It's a basic project with a ToDo
controller that displays a list of ToDo items.
Update the Index
method to use priority and completion status parameters:
using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;
namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
private readonly ToDoContext _ToDoContext;
public ToDoController(ToDoContext context)
{
_ToDoContext = context;
_ToDoContext.Database.EnsureCreated();
}
public IActionResult Index(int maxPriority = 2, bool isDone = false)
{
var model = _ToDoContext!.ToDo!.ToList();
ViewData["maxPriority"] = maxPriority;
ViewData["isDone"] = isDone;
return View(model);
}
Add a ViewComponent class to ViewComponents/PriorityListViewComponent.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents;
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;
public PriorityListViewComponent(ToDoContext context) => db = context;
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
Notes on the code:
View component classes can be contained in any folder in the project.
Because the class name PriorityListViewComponent ends with the suffix ViewComponent, the runtime uses the string PriorityList
when referencing the class component from a view.
The [ViewComponent]
attribute can change the name used to reference a view component. For example, the class could have been named XYZ
with the following [ViewComponent]
attribute:
[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent
The [ViewComponent]
attribute in the preceding code tells the view component selector to use:
PriorityList
when looking for the views associated with the componentThe component uses dependency injection to make the data context available.
InvokeAsync
exposes a method that can be called from a view, and it can take an arbitrary number of arguments.
The InvokeAsync
method returns the set of ToDo
items that satisfy the isDone
and maxPriority
parameters.
Create the Views/Shared/Components folder. This folder must be named Components.
Create the Views/Shared/Components/PriorityList folder. This folder name must match the name of the view component class, or the name of the class minus the suffix. If the ViewComponent
attribute is used, the class name would need to match the attribute designation.
Create a Views/Shared/Components/PriorityList/Default.cshtml
Razor view:
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h3>Priority Items</h3>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
The Razor view takes a list of TodoItem
and displays them. If the view component InvokeAsync
method doesn't pass the name of the view, Default is used for the view name by convention. To override the default styling for a specific controller, add a view to the controller-specific view folder (for example Views/ToDo/Components/PriorityList/Default.cshtml).
If the view component is controller-specific, it can be added to the controller-specific folder. For example, Views/ToDo/Components/PriorityList/Default.cshtml
is controller-specific.
Add a div
containing a call to the priority list component to the bottom of the Views/ToDo/index.cshtml
file:
</table>
<div>
Maximum Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
The markup @await Component.InvokeAsync
shows the syntax for calling view components. The first argument is the name of the component we want to invoke or call. Subsequent parameters are passed to the component. InvokeAsync
can take an arbitrary number of arguments.
Test the app. The following image shows the ToDo list and the priority items:
The view component can be called directly from the controller:
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
return ViewComponent("PriorityList",
new {
maxPriority = maxPriority,
isDone = isDone
});
}
A complex view component might need to specify a non-default view under some conditions. The following code shows how to specify the "PVC" view from the InvokeAsync
method. Update the InvokeAsync
method in the PriorityListViewComponent
class.
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
string MyView = "Default";
// If asking for all completed tasks, render with the "PVC" view.
if (maxPriority > 3 && isDone == true)
{
MyView = "PVC";
}
var items = await GetItemsAsync(maxPriority, isDone);
return View(MyView, items);
}
Copy the Views/Shared/Components/PriorityList/Default.cshtml
file to a view named Views/Shared/Components/PriorityList/PVC.cshtml
. Add a heading to indicate the PVC view is being used.
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
Run the app and verify PVC view.
If the PVC view isn't rendered, verify the view component with a priority of 4 or higher is called.
Change the priority parameter to three or less so the priority view isn't returned.
Temporarily rename the Views/ToDo/Components/PriorityList/Default.cshtml
to 1Default.cshtml
.
Test the app, the following error occurs:
An unhandled exception occurred while processing the request.
InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
/Views/ToDo/Components/PriorityList/Default.cshtml
/Views/Shared/Components/PriorityList/Default.cshtml
Copy Views/ToDo/Components/PriorityList/1Default.cshtml
to Views/Shared/Components/PriorityList/Default.cshtml
.
Add some markup to the Shared ToDo view component view to indicate the view is from the Shared folder.
Test the Shared component view.
For compile time safety, replace the hard-coded view component name with the class name. Update the PriorityListViewComponent.cs file to not use the "ViewComponent" suffix:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents;
public class PriorityList : ViewComponent
{
private readonly ToDoContext db;
public PriorityList(ToDoContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
The view file:
</table>
<div>
Testing nameof(PriorityList) <br />
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(nameof(PriorityList),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
An overload of Component.InvokeAsync
method that takes a CLR type uses the typeof
operator:
</table>
<div>
Testing typeof(PriorityList) <br />
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(typeof(PriorityList),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
The framework handles invoking a synchronous Invoke
method if asynchronous work isn't required. The following method creates a synchronous Invoke
view component:
using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityListSync : ViewComponent
{
private readonly ToDoContext db;
public PriorityListSync(ToDoContext context)
{
db = context;
}
public IViewComponentResult Invoke(int maxPriority, bool isDone)
{
var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToList();
return View(x);
}
}
}
The view component's Razor file:
<div>
Testing nameof(PriorityList) <br />
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(nameof(PriorityListSync),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
The view component is invoked in a Razor file (for example, Views/Home/Index.cshtml
) using one of the following approaches:
To use the IViewComponentHelper approach, call Component.InvokeAsync
:
@await Component.InvokeAsync(nameof(PriorityList),
new { maxPriority = 4, isDone = true })
To use the Tag Helper, register the assembly containing the View Component using the @addTagHelper
directive (the view component is in an assembly called MyWebApp
):
@addTagHelper *, MyWebApp
Use the view component Tag Helper in the Razor markup file:
<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>
The method signature of PriorityList.Invoke
is synchronous, but Razor finds and calls the method with Component.InvokeAsync
in the markup file.
View components are similar to partial views, but they're much more powerful. View components don't use model binding, they depend on the data passed when calling the view component. This article was written using controllers and views, but view components work with Razor Pages.
A view component:
View components are intended anywhere reusable rendering logic that's too complex for a partial view, such as:
A view component consists of two parts:
Like controllers, a view component can be a POCO, but most developers take advantage of the methods and properties available by deriving from ViewComponent.
When considering if view components meet an app's specifications, consider using Razor components instead. Razor components also combine markup with C# code to produce reusable UI units. Razor components are designed for developer productivity when providing client-side UI logic and composition. For more information, see ASP.NET Core Razor components. For information on how to incorporate Razor components into an MVC or Razor Pages app, see Integrate ASP.NET Core Razor components with MVC or Razor Pages.
This section contains the high-level requirements to create a view component. Later in the article, we'll examine each step in detail and create a view component.
A view component class can be created by any of the following:
[ViewComponent]
attribute, or deriving from a class with the [ViewComponent]
attributeViewComponent
Like controllers, view components must be public, non-nested, and non-abstract classes. The view component name is the class name with the ViewComponent
suffix removed. It can also be explicitly specified using the Name property.
A view component class:
To prevent a class that has a case-insensitive ViewComponent
suffix from being treated as a view component, decorate the class with the [NonViewComponent]
attribute:
using Microsoft.AspNetCore.Mvc;
[NonViewComponent]
public class ReviewComponent
{
public string Status(string name) => JobStatus.GetCurrentStatus(name);
}
A view component defines its logic in an:
InvokeAsync
method that returns Task<IViewComponentResult>
.Invoke
synchronous method that returns an IViewComponentResult.Parameters come directly from invocation of the view component, not from model binding. A view component never directly handles a request. Typically, a view component initializes a model and passes it to a view by calling the View
method. In summary, view component methods:
InvokeAsync
method that returns a Task<IViewComponentResult>
or a synchronous Invoke
method that returns an IViewComponentResult
.The runtime searches for the view in the following paths:
The search path applies to projects using controllers + views and Razor Pages.
The default view name for a view component is Default
, which means view files will typically be named Default.cshtml
. A different view name can be specified when creating the view component result or when calling the View
method.
We recommend naming the view file Default.cshtml
and using the Views/Shared/Components/{View Component Name}/{View Name} path. The PriorityList
view component used in this sample uses Views/Shared/Components/PriorityList/Default.cshtml
for the view component view.
To customize the view search path, modify Razor's ViewLocationFormats collection. For example, to search for views within the path /Components/{View Component Name}/{View Name}
, add a new item to the collection:
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews()
.AddRazorOptions(options =>
{
options.ViewLocationFormats.Add("/{0}.cshtml");
});
builder.Services.AddDbContext<ToDoContext>(options =>
options.UseInMemoryDatabase("db"));
var app = builder.Build();
// Remaining code removed for brevity.
In the preceding code, the placeholder {0}
represents the path Components/{View Component Name}/{View Name}
.
To use the view component, call the following inside a view:
@await Component.InvokeAsync("Name of view component",
{Anonymous Type Containing Parameters})
The parameters are passed to the InvokeAsync
method. The PriorityList
view component developed in the article is invoked from the Views/ToDo/Index.cshtml
view file. In the following code, the InvokeAsync
method is called with two parameters:
</table>
<div>
Maximum Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
A View Component can be invoked as a Tag Helper:
<div>
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@{
int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
bool isDone = Convert.ToBoolean(ViewData["isDone"]);
}
<vc:priority-list max-priority=maxPriority is-done=isDone>
</vc:priority-list>
</div>
Pascal-cased class and method parameters for Tag Helpers are translated into their kebab case. The Tag Helper to invoke a view component uses the <vc></vc>
element. The view component is specified as follows:
<vc:[view-component-name]
parameter1="parameter1 value"
parameter2="parameter2 value">
</vc:[view-component-name]>
To use a view component as a Tag Helper, register the assembly containing the view component using the @addTagHelper
directive. If the view component is in an assembly called MyWebApp
, add the following directive to the _ViewImports.cshtml
file:
@addTagHelper *, MyWebApp
A view component can be registered as a Tag Helper to any file that references the view component. See Managing Tag Helper Scope for more information on how to register Tag Helpers.
The InvokeAsync
method used in this tutorial:
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
In the preceding markup, the PriorityList
view component becomes priority-list
. The parameters to the view component are passed as attributes in kebab case.
View components are typically invoked from a view, but they can be invoked directly from a controller method. While view components don't define endpoints like controllers, a controller action that returns the content of a ViewComponentResult
can be implemented.
In the following example, the view component is called directly from the controller:
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
return ViewComponent("PriorityList",
new {
maxPriority = maxPriority,
isDone = isDone
});
}
Download, build and test the starter code. It's a basic project with a ToDo
controller that displays a list of ToDo items.
Update the Index
method to use priority and completion status parameters:
using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;
namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
private readonly ToDoContext _ToDoContext;
public ToDoController(ToDoContext context)
{
_ToDoContext = context;
_ToDoContext.Database.EnsureCreated();
}
public IActionResult Index(int maxPriority = 2, bool isDone = false)
{
var model = _ToDoContext!.ToDo!.ToList();
ViewData["maxPriority"] = maxPriority;
ViewData["isDone"] = isDone;
return View(model);
}
Add a ViewComponent class to ViewComponents/PriorityListViewComponent.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents;
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;
public PriorityListViewComponent(ToDoContext context) => db = context;
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
Notes on the code:
View component classes can be contained in any folder in the project.
Because the class name PriorityListViewComponent ends with the suffix ViewComponent, the runtime uses the string PriorityList
when referencing the class component from a view.
The [ViewComponent]
attribute can change the name used to reference a view component. For example, the class could have been named XYZ
with the following [ViewComponent]
attribute:
[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent
The [ViewComponent]
attribute in the preceding code tells the view component selector to use:
PriorityList
when looking for the views associated with the componentThe component uses dependency injection to make the data context available.
InvokeAsync
exposes a method that can be called from a view, and it can take an arbitrary number of arguments.
The InvokeAsync
method returns the set of ToDo
items that satisfy the isDone
and maxPriority
parameters.
Create the Views/Shared/Components folder. This folder must be named Components.
Create the Views/Shared/Components/PriorityList folder. This folder name must match the name of the view component class, or the name of the class minus the suffix. If the ViewComponent
attribute is used, the class name would need to match the attribute designation.
Create a Views/Shared/Components/PriorityList/Default.cshtml
Razor view:
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h3>Priority Items</h3>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
The Razor view takes a list of TodoItem
and displays them. If the view component InvokeAsync
method doesn't pass the name of the view, Default is used for the view name by convention. To override the default styling for a specific controller, add a view to the controller-specific view folder (for example Views/ToDo/Components/PriorityList/Default.cshtml).
If the view component is controller-specific, it can be added to the controller-specific folder. For example, Views/ToDo/Components/PriorityList/Default.cshtml
is controller-specific.
Add a div
containing a call to the priority list component to the bottom of the Views/ToDo/index.cshtml
file:
</table>
<div>
Maximum Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync("PriorityList",
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
The markup @await Component.InvokeAsync
shows the syntax for calling view components. The first argument is the name of the component we want to invoke or call. Subsequent parameters are passed to the component. InvokeAsync
can take an arbitrary number of arguments.
Test the app. The following image shows the ToDo list and the priority items:
The view component can be called directly from the controller:
public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
return ViewComponent("PriorityList",
new {
maxPriority = maxPriority,
isDone = isDone
});
}
A complex view component might need to specify a non-default view under some conditions. The following code shows how to specify the "PVC" view from the InvokeAsync
method. Update the InvokeAsync
method in the PriorityListViewComponent
class.
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
string MyView = "Default";
// If asking for all completed tasks, render with the "PVC" view.
if (maxPriority > 3 && isDone == true)
{
MyView = "PVC";
}
var items = await GetItemsAsync(maxPriority, isDone);
return View(MyView, items);
}
Copy the Views/Shared/Components/PriorityList/Default.cshtml
file to a view named Views/Shared/Components/PriorityList/PVC.cshtml
. Add a heading to indicate the PVC view is being used.
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
Run the app and verify PVC view.
If the PVC view isn't rendered, verify the view component with a priority of 4 or higher is called.
Change the priority parameter to three or less so the priority view isn't returned.
Temporarily rename the Views/ToDo/Components/PriorityList/Default.cshtml
to 1Default.cshtml
.
Test the app, the following error occurs:
An unhandled exception occurred while processing the request.
InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
/Views/ToDo/Components/PriorityList/Default.cshtml
/Views/Shared/Components/PriorityList/Default.cshtml
Copy Views/ToDo/Components/PriorityList/1Default.cshtml
to Views/Shared/Components/PriorityList/Default.cshtml
.
Add some markup to the Shared ToDo view component view to indicate the view is from the Shared folder.
Test the Shared component view.
For compile time safety, replace the hard-coded view component name with the class name. Update the PriorityListViewComponent.cs file to not use the "ViewComponent" suffix:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents;
public class PriorityList : ViewComponent
{
private readonly ToDoContext db;
public PriorityList(ToDoContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
The view file:
</table>
<div>
Testing nameof(PriorityList) <br />
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(nameof(PriorityList),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
An overload of Component.InvokeAsync
method that takes a CLR type uses the typeof
operator:
</table>
<div>
Testing typeof(PriorityList) <br />
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(typeof(PriorityList),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
The framework handles invoking a synchronous Invoke
method if asynchronous work isn't required. The following method creates a synchronous Invoke
view component:
using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityListSync : ViewComponent
{
private readonly ToDoContext db;
public PriorityListSync(ToDoContext context)
{
db = context;
}
public IViewComponentResult Invoke(int maxPriority, bool isDone)
{
var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToList();
return View(x);
}
}
}
The view component's Razor file:
<div>
Testing nameof(PriorityList) <br />
Maxium Priority: @ViewData["maxPriority"] <br />
Is Complete: @ViewData["isDone"]
@await Component.InvokeAsync(nameof(PriorityListSync),
new {
maxPriority = ViewData["maxPriority"],
isDone = ViewData["isDone"] }
)
</div>
The view component is invoked in a Razor file (for example, Views/Home/Index.cshtml
) using one of the following approaches:
To use the IViewComponentHelper approach, call Component.InvokeAsync
:
@await Component.InvokeAsync(nameof(PriorityList),
new { maxPriority = 4, isDone = true })
To use the Tag Helper, register the assembly containing the View Component using the @addTagHelper
directive (the view component is in an assembly called MyWebApp
):
@addTagHelper *, MyWebApp
Use the view component Tag Helper in the Razor markup file:
<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>
The method signature of PriorityList.Invoke
is synchronous, but Razor finds and calls the method with Component.InvokeAsync
in the markup file.
View or download sample code (how to download)
View components are similar to partial views, but they're much more powerful. View components don't use model binding, and only depend on the data provided when calling into it. This article was written using controllers and views, but view components also work with Razor Pages.
A view component:
View components are intended anywhere you have reusable rendering logic that's too complex for a partial view, such as:
A view component consists of two parts: the class (typically derived from ViewComponent) and the result it returns (typically a view). Like controllers, a view component can be a POCO, but most developers take advantage of the methods and properties available by deriving from ViewComponent
.
When considering if view components meet an app's specifications, consider using Razor components instead. Razor components also combine markup with C# code to produce reusable UI units. Razor components are designed for developer productivity when providing client-side UI logic and composition. For more information, see ASP.NET Core Razor components. For information on how to incorporate Razor components into an MVC or Razor Pages app, see Integrate ASP.NET Core Razor components with MVC or Razor Pages.
This section contains the high-level requirements to create a view component. Later in the article, we'll examine each step in detail and create a view component.
A view component class can be created by any of the following:
[ViewComponent]
attribute, or deriving from a class with the [ViewComponent]
attributeLike controllers, view components must be public, non-nested, and non-abstract classes. The view component name is the class name with the "ViewComponent" suffix removed. It can also be explicitly specified using the ViewComponentAttribute.Name
property.
A view component class:
To stop a class that has a case-insensitive ViewComponent suffix from being treated as a view component, decorate the class with the [NonViewComponent] attribute:
[NonViewComponent]
public class ReviewComponent
{
// ...
A view component defines its logic in an InvokeAsync
method that returns a Task<IViewComponentResult>
or in a synchronous Invoke
method that returns an IViewComponentResult
. Parameters come directly from invocation of the view component, not from model binding. A view component never directly handles a request. Typically, a view component initializes a model and passes it to a view by calling the View
method. In summary, view component methods:
InvokeAsync
method that returns a Task<IViewComponentResult>
or a synchronous Invoke
method that returns an IViewComponentResult
.ViewComponent
View
method.The runtime searches for the view in the following paths:
The search path applies to projects using controllers + views and Razor Pages.
The default view name for a view component is Default, which means your view file will typically be named Default.cshtml
. You can specify a different view name when creating the view component result or when calling the View
method.
We recommend you name the view file Default.cshtml
and use the Views/Shared/Components/{View Component Name}/{View Name} path. The PriorityList
view component used in this sample uses Views/Shared/Components/PriorityList/Default.cshtml
for the view component view.
To customize the view search path, modify Razor's ViewLocationFormats collection. For example, to search for views within the path "/Components/{View Component Name}/{View Name}", add a new item to the collection:
services.AddMvc()
.AddRazorOptions(options =>
{
options.ViewLocationFormats.Add("/{0}.cshtml");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
In the preceding code, the placeholder "{0}" represents the path "Components/{View Component Name}/{View Name}".
To use the view component, call the following inside a view:
@await Component.InvokeAsync("Name of view component", {Anonymous Type Containing Parameters})
The parameters will be passed to the InvokeAsync
method. The PriorityList
view component developed in the article is invoked from the Views/ToDo/Index.cshtml
view file. In the following, the InvokeAsync
method is called with two parameters:
@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
For ASP.NET Core 1.1 and higher, you can invoke a view component as a Tag Helper:
<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>
Pascal-cased class and method parameters for Tag Helpers are translated into their kebab case. The Tag Helper to invoke a view component uses the <vc></vc>
element. The view component is specified as follows:
<vc:[view-component-name]
parameter1="parameter1 value"
parameter2="parameter2 value">
</vc:[view-component-name]>
To use a view component as a Tag Helper, register the assembly containing the view component using the @addTagHelper
directive. If your view component is in an assembly called MyWebApp
, add the following directive to the _ViewImports.cshtml
file:
@addTagHelper *, MyWebApp
You can register a view component as a Tag Helper to any file that references the view component. See Managing Tag Helper Scope for more information on how to register Tag Helpers.
The InvokeAsync
method used in this tutorial:
@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
In Tag Helper markup:
<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>
In the sample above, the PriorityList
view component becomes priority-list
. The parameters to the view component are passed as attributes in kebab case.
View components are typically invoked from a view, but you can invoke them directly from a controller method. While view components don't define endpoints like controllers, you can easily implement a controller action that returns the content of a ViewComponentResult
.
In this example, the view component is called directly from the controller:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
Download, build and test the starter code. It's a simple project with a ToDo
controller that displays a list of ToDo items.
Create a ViewComponents folder and add the following PriorityListViewComponent
class:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;
public PriorityListViewComponent(ToDoContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}
Notes on the code:
View component classes can be contained in any folder in the project.
Because the class name PriorityListViewComponent ends with the suffix ViewComponent, the runtime uses the string PriorityList
when referencing the class component from a view.
The [ViewComponent]
attribute can change the name used to reference a view component. For example, the class could have been named XYZ
with the ViewComponent
attribute:
[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent
The [ViewComponent]
attribute in the preceding code tells the view component selector to use:
PriorityList
when looking for the views associated with the componentThe component uses dependency injection to make the data context available.
InvokeAsync
exposes a method which can be called from a view, and it can take an arbitrary number of arguments.
The InvokeAsync
method returns the set of ToDo
items that satisfy the isDone
and maxPriority
parameters.
Create the Views/Shared/Components folder. This folder must be named Components
.
Create the Views/Shared/Components/PriorityList folder. This folder name must match the name of the view component class, or the name of the class minus the suffix (if we followed convention and used the ViewComponent suffix in the class name). If you used the ViewComponent
attribute, the class name would need to match the attribute designation.
Create a Views/Shared/Components/PriorityList/Default.cshtml
Razor view:
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h3>Priority Items</h3>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
The Razor view takes a list of TodoItem
and displays them. If the view component InvokeAsync
method doesn't pass the name of the view (as in our sample), Default is used for the view name by convention. Later in the tutorial, I'll show you how to pass the name of the view. To override the default styling for a specific controller, add a view to the controller-specific view folder (for example Views/ToDo/Components/PriorityList/Default.cshtml).
If the view component is controller-specific, you can add it to the controller-specific folder (Views/ToDo/Components/PriorityList/Default.cshtml
).
Add a div
containing a call to the priority list component to the bottom of the Views/ToDo/index.cshtml
file:
</table>
<div>
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
</div>
The markup @await Component.InvokeAsync
shows the syntax for calling view components. The first argument is the name of the component we want to invoke or call. Subsequent parameters are passed to the component. InvokeAsync
can take an arbitrary number of arguments.
Test the app. The following image shows the ToDo list and the priority items:
You can also call the view component directly from the controller:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
A complex view component might need to specify a non-default view under some conditions. The following code shows how to specify the "PVC" view from the InvokeAsync
method. Update the InvokeAsync
method in the PriorityListViewComponent
class.
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
string MyView = "Default";
// If asking for all completed tasks, render with the "PVC" view.
if (maxPriority > 3 && isDone == true)
{
MyView = "PVC";
}
var items = await GetItemsAsync(maxPriority, isDone);
return View(MyView, items);
}
Copy the Views/Shared/Components/PriorityList/Default.cshtml
file to a view named Views/Shared/Components/PriorityList/PVC.cshtml
. Add a heading to indicate the PVC view is being used.
@model IEnumerable<ViewComponentSample.Models.TodoItem>
<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
@foreach (var todo in Model)
{
<li>@todo.Name</li>
}
</ul>
Update Views/ToDo/Index.cshtml
:
@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
Run the app and verify PVC view.
If the PVC view isn't rendered, verify you are calling the view component with a priority of 4 or higher.
Change the priority parameter to three or less so the priority view isn't returned.
Temporarily rename the Views/ToDo/Components/PriorityList/Default.cshtml
to 1Default.cshtml
.
Test the app, you'll get the following error:
An unhandled exception occurred while processing the request.
InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
/Views/ToDo/Components/PriorityList/Default.cshtml
/Views/Shared/Components/PriorityList/Default.cshtml
EnsureSuccessful
Copy Views/ToDo/Components/PriorityList/1Default.cshtml
to Views/Shared/Components/PriorityList/Default.cshtml
.
Add some markup to the Shared ToDo view component view to indicate the view is from the Shared folder.
Test the Shared component view.
If you want compile time safety, you can replace the hard-coded view component name with the class name. Create the view component without the "ViewComponent" suffix:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityList : ViewComponent
{
private readonly ToDoContext db;
public PriorityList(ToDoContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}
Add a using
statement to your Razor view file, and use the nameof
operator:
@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>
<h2>ToDo nameof</h2>
<!-- Markup removed for brevity. -->
<div>
@*
Note:
To use the below line, you need to #define no_suffix in ViewComponents/PriorityList.cs or it won't compile.
By doing so it will cause a problem to index as there will be multiple viewcomponents
with the same name after the compiler removes the suffix "ViewComponent"
*@
@*@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })*@
</div>
You can use an overload of Component.InvokeAsync
method that takes a CLR type. Remember to use the typeof
operator in this case:
@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>
<h2>ToDo typeof</h2>
<!-- Markup removed for brevity. -->
<div>
@await Component.InvokeAsync(typeof(PriorityListViewComponent), new { maxPriority = 4, isDone = true })
</div>
The framework handles invoking a synchronous Invoke
method if you don't need to perform asynchronous work. The following method creates a synchronous Invoke
view component:
public class PriorityList : ViewComponent
{
public IViewComponentResult Invoke(int maxPriority, bool isDone)
{
var items = new List<string> { $"maxPriority: {maxPriority}", $"isDone: {isDone}" };
return View(items);
}
}
The view component's Razor file lists the strings passed to the Invoke
method (Views/Home/Components/PriorityList/Default.cshtml
):
@model List<string>
<h3>Priority Items</h3>
<ul>
@foreach (var item in Model)
{
<li>@item</li>
}
</ul>
The view component is invoked in a Razor file (for example, Views/Home/Index.cshtml
) using one of the following approaches:
To use the IViewComponentHelper approach, call Component.InvokeAsync
:
@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })
To use the Tag Helper, register the assembly containing the View Component using the @addTagHelper
directive (the view component is in an assembly called MyWebApp
):
@addTagHelper *, MyWebApp
Use the view component Tag Helper in the Razor markup file:
<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>
The method signature of PriorityList.Invoke
is synchronous, but Razor finds and calls the method with Component.InvokeAsync
in the markup file.
Each parameter in a view component is a required attribute. See this GitHub issue. If any parameter is omitted:
InvokeAsync
method signature won't match, therefore the method won't execute.ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreTraining
Learning path
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization