ViewComponents in ASP.NET 5 and ASP.NET MVC 6

Β· 1255 words Β· 6 minutes to read

Let’s have a quick look at another new feature in ASP.NET MVC 6, and that is the ViewComponent feature. View components are intended to be replacements to ChildActions and, to some extent, of partial views.

Traditionally in ASP.NET MVC (and in general in the textbook MVC pattern), you had to compose the entire model in the controller and pass it along to the view, which simply rendered the entire page based on the data from the model. The consequence of this is that the view does not need to explicitly ask for any data - as its sole purpose is to just act upon the model it received.

While this sounds very nice in theory, it has traditionally posed a number of practical difficulties. There are a number of reusable components on pretty much every website - think a menu, a shopping cart, lists of all kinds, breadcrumbs, metadata and so on - so things that appear on multiple pages.

Let’s have a look at how this is solved in MVC 6.

The reusable component problem πŸ”—

Having to compose and gather the data required to create these components in multiple controllers over and over can be really painful, and has often led developers to create a all kinds of model builders and inherited models (have you ever created a BaseModel containing things like menu or page metadata?).

One way to easy that pain was to try to use child actions - which were actions that could only be called from the view (not publicly by the client). I have seen plenty of MVC projects that used child actions to render such reusable components. Another alternative has always been to create elaborate HtmlHelpers. Unfortunately since the helpers were static, you couldn’t do dependency injection into them, so trying to load any extra data for the purpose of such helper, would require you to use service locator - the MVC global DependencyResolver.

View components are really a mesh of both HtmlHelper and child actions.

ViewComponents example πŸ”—

Consider the following sample service:

public class Product  
{  
public string Name { get; set; }

public decimal Price { get; set; }  
}

public interface IProductService  
{  
Task<Product[]> GetPromotedProducts();  
}

public class ProductService : IProductService  
{  
public Task<Product[]> GetPromotedProducts()  
{  
//for simplicity data is in memory  
var data = new[]  
{  
new Product  
{  
Name = "Etape: 20 Great Stages from the Modern Tour de France",  
Price = 9.90m  
},  
new Product  
{  
Name = "Anarchy Evolution: Faith, Science and Bad Religion in a World Without God",  
Price = 8.90m  
},  
new Product  
{  
Name = "The Bright Continent: Breaking Rules and Making Changes in Modern Africa",  
Price = 12.50m  
}  
};  
return Task.FromResult(data);  
}  
}  

It’s not unreasonable to think that you might have code resembling something that in your projects. Imagine that we need to display a list of these promoted products in multiple places on the website.

Typically in MVC 5 and earlier, you would have a child action, an HtmlHelper or a base model builder to handle this. With view component you can simply add a view component class:

[ViewComponent(Name = "PromotedProducts")]  
public class PromotedProductsViewComponent : ViewComponent  
{  
private readonly IProductService _productService;

public PromotedProductsViewComponent(IProductService productService)  
{  
_productService = productService;  
}

public async Task<IViewComponentResult> InvokeAsync()  
{  
var products = await _productService.GetPromotedProducts();  
return View(products);  
}  
}  

That class can use ASP.NET 5 dependency injection - which is very convenient. It can return something primitive like a JSON or a string, but it can also have its own partial view; in the above example the partial exists under /Views/Shared/Components/{componentname}/Default.cshtml. The Default.cshtml is the default convenion for all views of view component. You can also pass the name of the view returning i.e. _return View("Products", products)

In our case the Razor view would look like you would expect any partial view to look:

//file: /Views/Shared/Components/PromotedProducts/Default.cshtml  
@model IEnumerable<ViewComponentSite.Controllers.Product>

<div class="panel panel-default">
  <div class="panel-heading">
    Promoted products
  </div>
  
  <ul class="list-group">
    @foreach (var product in Model)<br /> {</p> 
    
    <li class="list-group-item">
      @product.Name - @product.Price
    </li>
    <p>
      } </ul> </div> 
      
      <p>
        ```
      </p>
      
      <p>
        In order to embed this view component into a proper, main view, you use the <em>Component</em> property of the Razor pages - in the form of the following syntax:
      </p>
      
      <p>
        ```csharp

      </p>
      
      <div class="col-md-4">
        <h2>
          Important menu
        </h2>
        
        <ul>
          <li>
            Home
          </li>
          <li>
            About
          </li>
        </ul>
        
        <p>
          @await Component.InvokeAsync("PromotedProducts")
        </p>
      </div>
      
      <p>
        ```
      </p>
      
      <p>
        There is also a sync version, if your component is synchronous. And that's about it - a bit against the textbook MVC pattern, but very convenient and much cleaner than the old approaches or workarounds we had to use.
      </p>
      
      <h3>
        Some background info
      </h3>
      
      <p>
        In order for a class to be recognized as a view component, it needs to be:
      </p>
      
      <ul>
        <li>
          public
        </li>
        <li>
          non-abstract
        </li>
        <li>
          non generic
        </li>
        <li>
          end with <em>ViewComponent</em> suffix or be decorated with <em>ViewComponentAttribute</em>
        </li>
      </ul>
      
      <p>
        As you can see, just like the rest of MVC 6, it heavily relies on conventions. To build a view component you do not need to inherit from any specific class or implement any interface.
      </p>
      
      <p>
        As far as the actual functionality inside your view component, it is also defined by convention - it should be wrapped in one of the following methods (the method name and return type are important):
      </p>
      
      <p>
        ```csharp
<br /> public Task<IViewComponentResult> InvokeAsync()<br /> {<br /> //do stuff<br /> }
      </p>
      
      <p>
        public IViewComponentResult Invoke()<br /> {<br /> //do stuff<br /> }<br /> ```
      </p>
      
      <p>
        The default view component invoker (which could be changed if you wish) will first try to look for the async method, and if it's not found, then it will try to fallback to the sync version. Afterwards, if it's still not found, you'll get a runtime error. This is important to note, because given the above mentioned requirements for a class to be considered a <em>valid</em> view component, it is still possible to get a runtime error if you misspell or misname the actual <em>Invoke</em> method.
      </p>
      
      <p>
        Depending on what you need to do, there is a base class <em>ViewComponent</em>, which you can choose to inherit from for your own convenience. It will give you - through it's expose properties - access to all contextual information about the view such as user principal information, current <em>HttpContext</em>, <em>ViewData</em> and all other information you typically would have inside a view. It will also allow you to easily attach a Razor view to the view component - which you can do by calling the base <em>View()</em> method when returning from your view component.
      </p>
      
      <p>
        As far as discovery of view components goes, all assemblies that reference ASP.NET MVC packages will be considered valid to search. This is particularly important for developers of reusable, CMS-like components that are intended to be used cross-project. The discovery is controlled by a type called <em>DefaultViewComponentDescriptorProvider</em>, which allows you to extend itself with additional source to look at or provide different rules for the class to be recognized as view component.
      </p>
      
      <p>
        Finally, it is also possible to pass data to the components. Then you add the relevant parameters to the signature, for example:
      </p>
      
      <p>
        ```csharp
<br /> public Task<IViewComponentResult> InvokeAsync(string text, Foo bar)<br /> {<br /> //do stuff<br /> }<br /> ```
      </p>
      
      <p>
        In order to invoke such a component from a view, you use an overload of the <em>Component.InvokeAsync</em> which takes <em>param</em> of <em>object[]</em>:
      </p>
      
      <p>
        ```csharp
<br /> @await Component.InvokeAsync("MyComponent", "text", new Foo())<br /> ```
      </p>

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP