Using dependency injection with view components

On ASP.NET Core view components support dependency injection. We can use some DI/IoC container but we can also go with framework level dependency injection. This blog post explains how dependency injection works with view components and as an example I show you some code I’m using for top menu of my demo site.

Dependency injection in ASP.NET Core

When using framework level dependency injection we will define mappings between implementations and interfaces when web application is configured.


public class Startup
{
    // Other methods of start-up class
 
    public virtual void ConfigureServices(IServiceCollection services)
    {
        // configure other services
 
        services.AddScoped<IFileClient, AzureFileClient>();
        services.AddScoped<IProductService, ProductService>();
        services.AddScoped<ISearchService, AzureSearchService>();
    }
}

The code above shows fragment of ConfigureServices() method of start-up class of ASP.NET Core application. AddScoped() method adds mapping that works on request level. It’s also possible define singletons and transient instances.

Injecting domain services to view component

Suppose we have product catalogue and we want to build top menu of our site from first level categories. There is no point to duplicate top menu to all views we have. We have layout page and this is where top menu will be. Also suppose we have domain service for products and we want to use it to display top menu like shown on screenshot fragment below.

Top menu of sample beer store

Previously we had to write some extension method that renders out top menu. The other option was code in view. I don’t like these options as they don’t follow the ideas of MVC pattern very well. View components are better because they encapsulate their functionality to separate components. Also they have separate views. This is architecturally better because internals of those components are separated from other code behind controller actions and views.

As domain service for products already exists we can start with view component.


public class TopMenuViewComponent : ViewComponent
{
    private readonly IProductService _productService;
 
    public TopMenuViewComponent(IProductService productService)
    {
        _productService = productService;
    }
 
    public IViewComponentResult Invoke()
    {
        return View(_productService.GetTopLevelCategories());
    }
}

To get products service to view component the code uses constructor injection. In constructor we just save the reference to service so we can use it in Invoke() method when it is called.

Here is the view component view. It just iterates through list of categories and creates links for top menu.


@model IList<ProductCategoryDto>
 
<ul class="topmenu">
    @foreach (var item in Model)
    {
        <li>
            <a href="@Url.Action("Category","Home", new { id = item.Id })">
@item.Name
</a
>
        </li>
    }
</ul>

Highlighting selected top menu item

Now let’s build our own request context class we can use to carry our application specific information through the request. One piece of information is current product category. This helps us to find out top level menu item that must be displayed as highlighted.

To keep sample code small we define extremely primitive context class.


public class ShopContext
{
    public int CategoryId { get; set; }
}

Using framework level dependency injection we inject new instance of it to every request.


services.AddScoped<ShopContext, ShopContext>();

Our product catalogue knows usually everything about current category. We will inject this context class to our controllers and then assign current category unique ID in controller actions.


public class HomeController : Controller
{
    private readonly ShopContext _shopContext;
 
    public HomeController(ShopContext shopContext)
    {
        _shopContext = shopContext;
    }
 
    public async Task<IActionResult> Category(int id, int page = 1)
    {
        if (id == 0)
            return NotFound();
 
        _shopContext.CategoryId = id;
 
        // Ask data, fill model, return view
    }
}

Everything that is on layout page will be rendered after controller action executes. This goes also for our top menu view component. We create new model for our view component so we can send top level categories and selected category ID to component view.


public class ProductCategoryMenuModel
{
    public IList<ProductCategoryDto> Categories { get; set; }
    public int CurrentTopLevelCategoryId { get; set; }
 
    public ProductCategoryMenuModel()
    {
        Categories = new List<ProductCategoryDto>();
    }
}
  
public class TopMenuViewComponent : ViewComponent
{
    private readonly ShopContext _shopContext;
    private readonly IProductService _productService;
 
    public TopMenuViewComponent(ShopContext shopContext, 
                                IProductService productService)
    {
        _productService = productService;
        _shopContext = shopContext;
    }
 
    public IViewComponentResult Invoke()
    {
        var model = new ProductCategoryMenuModel();
        model.Categories = _productService.GetTopLevelCategories();
        model.CurrentTopLevelCategoryId =_productService
.GetTopLevelCategoryFor(_shopContext.CategoryId)
.Id;
        return View(model);
    }
}

And here is the view to display top level menu with highlighting.


@model ProductCategoryMenuModel
 
<ul class="topmenu">
    @foreach (var item in Model.Categories)
    {
        var activeClass = "";
        if (item.Id == Model.CurrentTopLevelCategoryId)
        {
            activeClass = "active";
        }
        <li class="@activeClass">
            <a href="@Url.Action("Category","Home", new { id = item.Id }) ">@item.Name</a>
        </li>
    }
</ul>

The end result for my product catalogue is show on screenshot below.

Highlighted top menu of sample beer store

Wrapping up

Framework level dependency injection is powerful feature of .NET Core. It is easy to configure and easy to use through constructor injection. We used dependency injection with view component to display top menu and to correctly highlight current top level category. We ended up simple isolated component that has its own class and view.


2 thoughts on “Using dependency injection with view components

  • Shuffler says:

    Hey! Thank you for this very itneresting article.
    Do you mind sharing code samples alongside for people to test it out?

  • Gunnar says:

    Hi!
    Code samples are here and in posts if refer. It should be enough to repeat what I did. Do you mean sample project instead?

Leave a Reply

Your email address will not be published. Required fields are marked *