Using ViewComponents in ASP.Net Core
Introduction
ViewComponent, new to ASP.Net Core. In case, it's not only similar to partial views, but also more powerful than that.
In modern front-end frameworks, View Components could be observed usually. Such as Angular, VueJS, ... and so on.
A ViewComponent doesn't run with model binding, it only depend on the data you provided.
- Renders a chunk rather than a whole response
- Includes the same separation-of-concerns and testability benefits found between a controller and view
- Can have parameters and business logic
- Is typically invoked from a layout page
Working with View Components is also helped us to implement the thought of Separation of concerns.
There are scenarios worked perfectly by using View Component. Such as:
- Dynamic navigation menus
- Tag cloud (where it queries the database)
- Login panel
- Shopping cart
- Recently published articles
- Sidebar content on a typical blog
- A login panel that would be rendered on every page and show either the links to log out or log in, depending on the log in state of the user
Creating a view component
The view component class itself
It could be placed in any folder. But need to follow the rules.
- Deriving from ViewComponent (inherits)
- Decorating a class with the [ViewComponent] attribute, or deriving from a class with the [ViewComponent] attribute
- Creating a class where the name ends with the suffix ViewComponent
Just any one of list rules followed, compiler would recognize it as a View Component.
Like controllers, view component classes 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.
Please note that, as View Component does NOT take part in controller lifecycle, therefore, Filters won't work on it.
View component methods
A view component defines its logic in an or Invoke InvokeAsync method (InvokeAsync will be great) 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. A view component usually initializes a model and passes it to a view by calling the ViewComponent View method.
The view component search path
The runtime searches for the view in the following paths:
- MVC scaffolding
- Views/<controller_name>/Components/<view_component_name>/<view_name>
- Views/Shared/Components/<view_component_name>/<view_name>
- Razor Pages scaffolding
- /Pages/Components/<view_component_name>/<view_name>
- /Pages/Shared/Components/<view_component_name>/<view_name>
- /Views/Shared/Components/<view_component_name>/<view_name>
The default view name for a view component is Default, aka your view file will be named "Default.cshtml". You can specify a different view name when creating the view component result or when calling the View method.
Invoking a view component
To use a view component, call
@Component.InvokeAsync("Name of view component", <anonymous type containing parameters>)
from a view directly.
Or, you could call it as a Tag Helper.
Before doing that, we should register the assembly containing the view component by using
@addTagHelper *, <Your_Assembly_Name>
directive first, then call
<vc:[view-component-name] parameter1="parameter1 value" ... ></vc:[view-component-name]>
Plus, we can also invok view component inside a controller action
public IActionResult View_Component_Action()
{
return ViewComponent("Your_View_Component_Name", new { Parameter1 = Parameter1Value });
}
Mixed them together, creating a WeatherIndicator view component
Creating a web project, first. Our goal is to grab weather data from OpenWebMap, and using it as a view component.
Let's create a WeatherService as data privider.
public class WeatherService
{
private readonly HttpClient _httpClient;
public WeatherService(IHttpClientFactory httpClientFactory)
{
this._httpClient = httpClientFactory.CreateClient();
}
public async Task<WeatherData> GetWeatherAsync()
{
// TODO: DON'T do this. api uri should be move to .config
const string weatherApi = "https://openweathermap.org/data/2.5/weather/?appid=b6907d289e10d714a6e88b30761fae22&id=1673820&units=metric";
var req = await Task.FromResult(this._httpClient.GetStringAsync(weatherApi).Result);
var data = JsonSerializer.Deserialize<WeatherData>(req);
return data;
}
}
Don't forget to register the service, after creating it.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient(); // when service
services.AddTransient<WeatherService, WeatherService>(); // when service
services.AddRazorPages();
//.AddViewComponentsAsServices(); // when vc itself
// services.AddHttpClient<WeatherIndicatorViewComponent>(); // when vc itself
}
We draw weather data by calling a api request. HttpClient again. See, it's really that important! After api calling, we Deserialize the result by using System.Text.Json, and pass it to a view component.
Creating a WeatherIndicatorViewComponent class as a view component itself inside your web project.
public class WeatherIndicatorViewComponent : ViewComponent
{
private readonly WeatherService _weatherService;
public WeatherIndicatorViewComponent(WeatherService weatherService)
{
this._weatherService = weatherService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var data = await this._weatherService.GetWeatherAsync();
return View(data);
// return View("Default", data);
}
}
The view component gets weather data from the service.
@model WeatherVC.WeatherData
@{
Layout = null;
}
<h2>Weather Indicator</h2>
<div class="row">
<div><label>Long</label> @Model.coord?.lon</div>
<div><label>Lat</label> @Model.coord?.lat</div>
<div>
<label>Temp</label> @Model.main?.temp
<label>Feels like</label> @Model.main?.feels_like
</div>
</div>
And the view of view component looks like shown above. It's quiet easy.
Next, how to use a view component we created. Here is the way how.
@page
@addTagHelper *, WeatherVC
@{
Layout = null;
}
<div class="row">
<h1>View Component Test Run</h1>
<vc:weather-indicator></vc:weather-indicator>
<hr/>
@await Component.InvokeAsync("WeatherIndicator")
</div>
Invoke as a tag,
<vc:weather-indicator></vc:weather-indicator>
Note that, the view componet name will be transfomed automatically to tag-style by compiler. We don't worry about that.
or by razor syntax,
@await Component.InvokeAsync("WeatherIndicator")
They're all show same things.