View Components in ASP.NET Core MVC

As part of ASP.NET MVC 6, a new feature called view components has been introduced. View components are similar to child actions and partials views, allowing you to create reusable components with (or without) logic. Here's the summary from the ASP.NET documentation:

View components include the same separation-of-concerns and testability benefits found between a controller and view. You can think of a view component as a mini-controller—it’s responsible for rendering a chunk rather than a whole response. You can use view components to solve any problem that you feel is too complex with a partial.

Before ASP.NET Core, you would've probably used a child action to create a reusable component that requires some code for its logic. ASP.NET MVC 6, however, doesn't have child actions anymore. You can now choose between a partial view or a view component, depending on the requirements for the feature you're implementing.

Writing A Simple View Component

Let's implement a simple dynamic navigation menu as a view component. We want to be able to display different navigation items based on some conditional logic (e.g. the user's claims or the hosting environment). Like controllers, view components must be public, non-nested, and non-abstract classes that either …

  • derive from the ViewComponent class,
  • are decorated with the [ViewComponent] attribute, or
  • have a name that ends with the "ViewComponent" suffix.

We'll choose the base class approach because ViewComponent provides a bunch of helper methods that we'll be calling to return and render a chunk of HTML. In a folder named "Components", we'll create a new C# class:

public class Navigation : ViewComponent
{
    
}

If you'd rather make the view component even more explicit by appending the "ViewComponent" suffix to the class name, you can additionally decorate the class with the ViewComponent attribute to specify an unsuffixed component name:

[ViewComponent(Name = "Navigation")]
public class NavigationViewComponent : ViewComponent
{
    
}

We'll also add a special Invoke method that returns an IViewComponentResult to be rendered. Similar to MVC controllers, the Content helper method handed down from the ViewComponent base class accepts a string and simply returns its value:

public IViewComponentResult Invoke()
{
    return Content("Navigation");
}

Another method provided by the ViewComponent class is Json, which serializes a given object and returns its JSON representation. And then, there's View, which we'll look at in a minute, but first, let's see how we can render our view component.

Rendering a View Component

Within our Razor views, we can use the Component helper and its Invoke method to render view components. The first argument (which is required) represents the name of the component, "Navigation" in our case. The remaining arguments (which are optional) represent parameters that our component's Invoke method might accept. In this case, we don't pass any further arguments because our Navigation component doesn't accept any:

@Component.Invoke("Navigation")

However, we're passing a magic string here. We can get more compile-time safety by replacing the hardcoded string literal with a nameof() expression that references our view component's class name:

@Component.Invoke(nameof(Navigation))

In order for the Navigation class to be found, we'll have to add its namespace to the list of namespaces imported within all our Razor views. Open up the _ViewImports.cshtml view within the "Views" folder or create it if it doesn't exist yet. In there, add the following line with the namespace used in your project:

@using ViewComponents.Components

You should now see the string "Navigation" appear. Of course, we don't want to return a hardcoded simple string from our Navigation view component. Instead, we'd like to render a full-blown Razor view.

Returning Views from View Components

Similar to controllers in MVC, the ViewComponent base class offers a View helper method for returning views. That method looks for a Razor view in these two locations:

  • Views/Shared/Components/{ComponentName}/Default.cshtml
  • Views/{ControllerName}/Components/{ComponentName}/Default.cshtml

If no explicit view name is specified, ASP.NET MVC 6 assumes the view to be named Default.cshtml. That convention can be overridden by passing the view name as a string to the viewName parameter of the View method.

I recommend you put your view components underneath the Shared folder, even if you don't use them multiple times.

Here's a simple view that renders a given list of navigation items:

@model Navigation.ViewModel

<nav>
    <ul>
        @foreach (var navigationItem in Model.NavigationItems)
        {
            <li>
                <a href="@navigationItem.TargetUrl">@navigationItem.Name</a>
            </li>
        }
    </ul>
</nav>

Let's now create the view model classes for the navigation items and instantiate a view model that is then passed to the above Default.cshtml view.

Adding a View Model

In the spirit of high cohesion within our component, these view model classes are defined as nested classes of our Navigation class. You could, of course, declare them elsewhere if you so choose. Nothing fancy here, really:

public class Navigation : ViewComponent
{
    public class ViewModel
    {
        public IList<ItemViewModel> NavigationItems { get; }

        public ViewModel(IList<ItemViewModel> navigationItems)
        {
            NavigationItems = navigationItems;
        }
    }

    public class ItemViewModel
    {
        public string Name { get; }
        public string TargetUrl { get; }

        public ItemViewModel(string name, string targetUrl)
        {
            Name = name;
            TargetUrl = targetUrl;
        }
    }

    // ...
}

Within the Invoke method, we'll now create an array of navigation items and pass it to a new view model instance:

public IViewComponentResult Invoke()
{
    var navigationItems = new[]
    {
        new ItemViewModel("Home", Url.RouteUrl(RouteNames.Home)),
        new ItemViewModel("Contact", Url.RouteUrl(RouteNames.Contact)),
        new ItemViewModel("About", Url.RouteUrl(RouteNames.About))
    };

    var viewModel = new ViewModel(navigationItems);

    return View(viewModel);
}

Note that we can use the Url helper within our view component to generate a URL for a given route name. Also note that I've created a extracted a simple RouteNames class that defines all route names, again using nameof:

public static class RouteNames
{
    public const string About = nameof(About);
    public const string Contact = nameof(Contact);
    public const string Home = nameof(Home);
}

The Startup.Configure method also retrieves the route names from this class. Again, we get more compile-time safety for stringly-typed APIs and a much nicer IntelliSense experience:

app.UseMvc(routes =>
{
    routes.MapRoute(RouteNames.Home, "", new { controller = "Home", action = "Index" });
    routes.MapRoute(RouteNames.About, "about", new { controller = "Home", action = "About" });
    routes.MapRoute(RouteNames.Contact, "contact", new { controller = "Home", action = "Contact" });
});

If you now run the application, you should see a list of navigation items linking to the specified route URLs. Sweet!

Asynchronous View Components

Since the entire ASP.NET Core stack is asynchronous from top to bottom, view components can be asynchronous as well. Instead of an Invoke method, you'll have to implement the InvokeAsync method and return a Task<IViewComponentResult>.

Imagine we're loading our navigation items from a database. An IO- or network-bound operation, database calls are the perfect use case for async and await:

public async Task<IViewComponentResult> InvokeAsync()
{
    var navigationItems = await LoadNavigationItemsFromDatabase();
    var viewModel = new ViewModel(navigationItems);

    return View(viewModel);
}

The call to Component.Invoke in our Razor has to be updated as well:

@await Component.InvokeAsync(nameof(Navigation))

Child actions in ASP.NET MVC 5 or earlier versions never really supported asynchronicity, thereby making it impossible to properly perform asynchronous operations within them. That aspect has gotten a lot easier with ASP.NET MVC 6 and asynchronous view components.

Dependency Injection Within View Components

ASP.NET Core has dependency injection built into the core of the stack. Therefore, we can have dependencies injected into the constructor of view components. Powerful stuff!

Let's assume we want to add a "Debug" item to our navigation that links to a debug screen showing various pieces of information useful during development (application settings, user claims, size of all cookies, …). Of course, we only want this item to be visible in hosting environments named "Development". Using dependency injection, we can inspect the hosting environment like this:

public class Navigation : ViewComponent
{
    // Nested classes
    // ...

    private readonly IHostingEnvironment _environment;

    public Navigation(IHostingEnvironment environment)
    {
        _environment = environment;
    }

    public IViewComponentResult Invoke()
    {
        var navigationItems = new List<ItemViewModel>
        {
            new ItemViewModel("Home", Url.RouteUrl(RouteNames.Home)),
            new ItemViewModel("Contact", Url.RouteUrl(RouteNames.Contact)),
            new ItemViewModel("About", Url.RouteUrl(RouteNames.About))
        };

        if (_environment.IsDevelopment())
        {
            var debugItem = new ItemViewModel("Debug", "/debug");
            navigationItems.Add(debugItem);
        }

        var viewModel = new ViewModel(navigationItems);

        return View(viewModel);
    }
}

Pretty cool, isn't it?

Summary

ASP.NET MVC 6 introduces view components, a component-oriented mixture of child actions and partial views. They can return various content, including Razor views, JSON, or plain text. View components can be rendered synchronously or asynchronously. Finally, they can integrate with the dependency injection system of ASP.NET Core through constructor injection.

Use the coupon code LAUNCHDAY for $10 off!

Learn React

8 Comments

Art

Is there a way to pass any data to View Component from the calling scope? Something like, say, User ID?

Marius Schulz

@Art: Yes, you can pass an arbitrary number of arguments to the Invoke and InvokeAsync methods. Just make sure the first arguments represents the name of the view component you want to render.

Robin Disuza

Really, it is a good news for all .net developers and learners because asp.net MVC 6 coming with new feature view components which will provide several amazing features. You are describing step by step with coding which have better prove to everyone. Please keep update more information about new technology and updating.

Danny Allegrezza

Hey Marius,

I enjoyed this article. I appreciate you sharing it with the rest of us .NET Developers!

darrell tunnell

.. and if you want your view component to include a javascript or css file on the page because, you know, its a web component after all, then guess what? You are on your own. Why? because Sections don't work within View Components. This is a great feature, until you actual try to use it in a real application. There are GitHub issues to track this request but no news on when it will be addressed yet. Let's hope it's soon!

Darren Evans

Are ViewComponents and ViewModels separate techniques for manipulating model data or are ViewComponents a replacement for the ViewModel 'way' of doing things?

Tiger

Hi - Fantastic article.

quick question.

Can viewComponents have their own client side components as well

i.e javascript files and css

or does the parent view which hosts the viewcomponent need to also reference any required javascript or css files

Say for example I have a viewcomponent which will be doing some pretty heavy database operations as well as manipulating the viewcomponent dom using javascript

where would i put the javascript ? (can it be inline with the view component in script tags without any problem - i take it i can't use a @section tag as it could conflict with @section on parent view )

Diego

Dear, I am new in view component I can give an example query to BD