ASP.NET Core 1.0: View Components

Synopsis: Child Actions are not even supported anymore and Partial Views are less powerful. Meet ASP.NET Core 1.0’s View Components! Example project to accompany this article can be found here: https://github.com/DannyvanderKraan/ViewComponents

Intro

Child Actions often got you into trouble (if for instance you want to authorize them with an Action Filter and they are used in the _Layout.cshtml) and they were pretty messy as they had to be implemented as methods in your Controller. They participate in the lifecycle and pipeline of the controller they are implemented in. Not always what you want, because they were reachable by HTTP requests as one of the biggest pet peeves! But you needed them to build the backend logic for reusable components in your Views. Partial Views are a cleaner way to make reusable content, but lack the ability to have complex/reusable backend logic.

View Components basically combine the two, because they consist out of a class with the backend logic and a Razor view for presentation [1]. This is more in line with SoC[3] and is much more testable. View Components are responsible for a portion of the response, instead of the entire response, while staying away from the lifecycle and pipeline of controllers. This makes them very light weight. They are basically a tiny controller by themselves.

Side note:Don’t forget about Partial Views though, because sometimes a View Component is like shooting with a cannon and a light weight Partial View is much more appropriate.

Before we look at a class for the back end logic of the View Component, if you want to build along you should add MVC Middleware and set the basics up. Go to the ConfigureServices method of the Startup class and type “services.AddMvc();”, have Visual Studio add the necessary NuGet package to your ‘project.json’ file and add the correct ‘using’ statement. Then in the Configure method add “app.UseMvcWithDefaultRoute();” and make sure the “app.Run(…” statement is commented out if this is still there. Add a “HomeController” class to the “Controllers” folder (as long as it derives from the Controller base class you’re fine). Add an “Index.cshtml” file to the “Views\Home” folder. Make the “HomeController” return the “Index.cshtml”.

Class with Backend Logic

As almost everything with ASP.NET Core 1.0 creating a class for a View Component can be convention based. This can be done by creating a class with the suffix “ViewComponent”, or by decorating the class with the “[ViewComponent]” attribute, or derive from a class with this attribute. The advantage is that you’ll have a POCO class. But you can also derive your class from the ViewComponent base class. This is a class that is decorated with the “ViewComponent” attribute and has the advantage of exposing handy methods and properties. And last but not least, the class must also be public, not nested and not abstract.

So if I derive from ViewComponent a basic implementation looks like:

   public class SomeViewComponent : ViewComponent
    {
	    public async Task<IViewComponentResult> InvokeAsync()
	    {
		    return View();
	    }
    }

You see that despite deriving from ViewComponent I still used the ViewComponent suffix. This is not necessary, but I find this to be a good practice. The suffix is removed by convention and thus this ViewComponent is called “Some”. I could change this to any name by using the “[ViewComponent]” attribute which takes a ‘Name’ parameter (for example: [ViewComponent(Name = “XYZ”)]).

Remember I said View Components are like tiny controllers? Well for the sake of being complete, deriving from the base class is what really makes them like controllers. Just look at the properties at your disposal:

[ViewComponent]
public abstract class ViewComponent
{
    protected ViewComponent();
    public HttpContext HttpContext { get; }
    public ModelStateDictionary ModelState { get; }
    public HttpRequest Request { get; }
    public RouteData RouteData { get; }
    public IUrlHelper Url { get; set; }
    public IPrincipal User { get; }
    public ClaimsPrincipal UserClaimsPrincipal { get; }
    
    [Dynamic]    
    public dynamic ViewBag { get; }
    [ViewComponentContext]
    public ViewComponentContext ViewComponentContext { get; set; }
    public ViewContext ViewContext { get; }
    public ViewDataDictionary ViewData { get; }
    public ITempDataDictionary TempData { get; }
    public ICompositeViewEngine ViewEngine { get; set; }
}

Then you see an “Invoke” method in “SomeViewComponent”. This is also by convention, so the method name and return type are important. The DefaultViewComponentInvoker first searches for a ‘InvokeAsync’ and if it didn’t find this async method it’ll search for the synchronous ‘Invoke’ method (if both are not present it’ll throw an exception). Check this [2] cool answer on Stackoverflow for help with customization of invoking view components and a better understanding of the DefaultViewComponentInvoker.

The Invoke method’s return value is rendered with help from the View method from the ViewComponent base class. It returns a ViewViewComponentResult which implements IViewComponentResult. Which means it implements the Execute and the ExecuteAsync methods. In this case the Execute method gets called eventually which locates and renders the view specified. In this example I didn’t specify a view name, which means it searches for the “Default” view.

Well we don’t have a Default.cshtml yet, so let’s add one!

Razor View

As free as you are with the location of the View Component class (I place them in a ViewComponents folder), so constrictive are the conventions for the View Component views. They are expected to be placed in a folder with the same name as the View Component (in the case of our example it should be called “Some”) which has to be a subdirectory of the “Components” folder. You are however free to add a “Components” folder to every controller folder in the “Views” folder you want (like for instance the “Home” folder), but you ought to design View Components for reusability, so there for the “Shared” folder is a better option. This forces you to keep reusability in mind. So I added a Default.cshtml at: “Views\Shared\Components\Some”.

Side note: However if you do not want to follow the conventions you can place the views anywhere you like, just pass the entire path when you return the view in the viewcomponent’s invoke method.

And the only HTML I added in “Default.cshtml” is:


<h2>Some-Default</h2>

Then in the “Index.cshtml” view add the following Razor statement:

@await Component.InvokeAsync("Some")

This is the statement to invoke View Components with. The first parameter is the name of the View Component, any subsequent parameter should correspond to parameters of the Invoke method of the View Component class. In this example there are no parameters, but later on in this article I’ll show an example with a parameter.

Well when we run this web app the ViewComponent doesn’t do much exciting stuff right now. You’ll just see “Some-Default” in the browser. But this basic example is enough to show the core mechanics behind View Components.

You see, a thing to remember is that View Components don’t use model binding, they use the data provided to them. You can provide parameters when you invoke them as said before or by injecting the dependency you need by using ASP.NET Core 1.0’s native support for Dependency Injection. This means you could for instance add a View Component to the layout page and use it throughout the whole application. Let’s make this example more interesting with some parameters and Dependency Injection.

I added the class “SomeStuff” to my “Models” directory:

public class SomeStuff
{
	public string Id { get; set; }
}

Then I added “SomeRepository” to the same folder:

public class SomeRepository
{
	public List<SomeStuff> GetSomeStuff()
	{
		return new List<SomeStuff>()
		{
			new SomeStuff() {Id = "97CFB273-7388-4D85-85F1-061297C5E9A5"},
			new SomeStuff() {Id = "6BB03A93-FFC0-4F5E-BED6-DC3D87C3FCFD"}
		};
	}
}

As you can see, nothing fancy, just returning two instances of the “SomeStuff” class via the “GetSomeStuff” method. To be able to inject this in the View Component we need to add this ‘service’ to the ‘container’. So the “ConfigureServices” method in the “Startup” class looks like:

public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc();
	services.AddTransient<SomeRepository>();
}

This is the simplest way to add a service, as a transient, which ‘news up’ an instance of SomeRepository every time SomeRepository is requested. Dependency Injection in ASP.NET Core 1.0 is worth a separate article, so I am not going into the details right now. Simply modify “SomeViewComponent” like this to request “SomeRepository” via ‘constructor injection’:

private SomeRepository SomeRepository { get; set; }

public SomeViewComponent(SomeRepository someRepository)
{
	if(someRepository == null) throw new ArgumentNullException(nameof(someRepository));
	SomeRepository = someRepository;
}

And change the “Invoke” method as follows:

public async Task<IViewComponentResult> InvokeAsync()
{
	return View(SomeRepository.GetSomeStuff());
}

This will pass an instance of the List of SomeStuff as a parameter to the “Default” view via the base class’s “View” method. “Default.cshtml” looks like:

@model List<ViewComponents.Models.SomeStuff>


<h2>Some-Default</h2>


<ul>
    @foreach (var stuff in Model)
    {

<li>@stuff.Id</li>

    }
</ul>

Which will display the two GUIDs you saw earlier beneath each other in the browser when you run this web app.

But you can also pass parameters to the ViewComponent. To demonstrate this you need to add a parameter the “Invoke” method to “SomeViewComponent”:

public async Task<IViewComponentResult> InvokeAsync(string id)
{
	return View(SomeRepository.GetSomeStuff().Where(s => s.Id.Equals(id)).ToList());
}

The “Index.cshtml” looks like:


<h1>Home-Index</h1>

@await Component.InvokeAsync("Some", new {id = "97CFB273-7388-4D85-85F1-061297C5E9A5"})

As you can see I can simply call invoke as you would with any C# method. The only difference is that it always starts with a ‘name’ parameter for the appropriate View Component. Any subsequent parameter can be passed through with an anonymous object. So imagine the Invoke method has next to the id parameter also an id2 parameter, you’d invoke it as follows:


<h1>Home-Index</h1>

@await Component.InvokeAsync("Some", new {id = "97CFB273-7388-4D85-85F1-061297C5E9A5", id2 = "199b1ef5-b0e1-46ef-8092-e7006ddf7dd1"})

Conclusion

View Components are a very powerful addition. Every time a Partial View just doesn’t do the job you can use a View Component.
They differ from Child Actions that you can’t bind the model anymore, but instead pass parameters to the component. They don’t participate in a controller’s lifecycle, so no Action Filters. And they are not reachable via HTTP like Child Actions were. But in return you get a more reusable and powerful component which adheres more to the SoC principle, because you can simply inject your dependencies or pass in the parameters you need. You can find the examples from this article on my GitHub[4].

Advertisements

About Danny

Bachelor in Commercial ICT MCTS Winforms .NET 2.0 MCTS ASP.NET 3.5 PSM I
This entry was posted in .NET programming, ASP.NET Core 1.0, ASP.NET MVC and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s