Language

View components and Inject in ASP.NET MVC 6

By Rick Anderson and Mike Wasson|

This topic shows how to create a view component in ASP.NET MVC 6 and how to inject a service into a view.

Create a new ASP.NET 5 starter project

Start Visual Studio 2015 Preview. From the File menu, select New > Project.

In the New Project dialog, click Templates > Visual C# > Web, and select the ASP.NET Web Application project template. Name the project "TodoList" and click OK.

new project dlg

In the New ASP.NET Project dialog, select the "ASP.NET 5.0 Starter Web" template.

New project dlg - select a template choices

Add the Todo controller

  1. Download and open the completed project.
  2. In solution explorer, in the new app you've created, right click the Controllers folder > Add > Exiting Item. Enter the path to the TodoController.cs file in the download project.
  3. Using the same approach, add the Models\TodoItem.cs and Models\TodoItemEditModel.cs to the Models folder.
  4. Create a ToDo folder under Views. Using the same approach, add all the views in the Views\ToDo folder to the Views\Todo folder.
  5. Change the ActionLink in the Views\Shared\_Layout.cshtml file to use the Todo controller:
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>@ViewBag.Title - Todo</title>
    
      <link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.css" />
      <link rel="stylesheet" href="~/css/site.css" />
    </head>
    <body>
      <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
          <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
            @Html.ActionLink("Todo app", "Index", "Todo", new { area = "" }, new { @class = "navbar-brand" })
          </div>
          <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
              @* Markup removed for brevity *@
    
    </body>
    </html>
  6. Add a DbSet containing the TodoItem model to the Models\IdentityModels.cs file.
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
      private static bool _created = false;
    
      public DbSet<TodoItem> TodoItems { get; set; }
    
      // Code removed for brevity.
      
    }
  7. Run the app and click on the Todo app link. You will get the following error (which we will fix in the next section).

    A database operation failed while processing the request.

    SqlException: Invalid object name 'TodoItem'.

    There are pending model changes for ApplicationDbContext

    Scaffold a new migration for these changes and apply them to the database from the command line

    > k ef migration add [migration name]
    > k ef migration apply

Install the K Version Manager (KVM)

  1. Open a command prompt with Run as administrator.
  2. Run the following command:
    @powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Home/master/kvminstall.ps1'))"

    The script installs KVM for the current user.
  3. Exit the command prompt window and start another as an administrator (you need to start a new command prompt to get the updated path environment).
  4. Upgrade KVM with the following command:
    KVM upgrade
    You are now ready to run EF migrations.

Run EF migrations

  1. In an administrator command window, change the directory to the project directory. The project directory contains the project.json file.
  2. In a command prompt, run the following:
    k ef migration add initial

    k ef migration apply


    The ef migration add initial command will add a migration file with the name <date>_<migration name>.cs which contains the migration code that adds the TodoItem DbSet. The Migrations\ApplicationDbContextModelSnapshot.cs file will be updated to include instructions to create the TodoItem entity.
    builder.Entity("TodoList.Models.TodoItem", b =>
        {
            b.Property<int>("Id")
                .GenerateValuesOnAdd();
            b.Property<bool>("IsDone");
            b.Property<int>("Priority");
            b.Property<string>("Title");
            b.Key("Id");
        });

    added files
  • Run the app and click the Todo app link. Click the Create New Todo link and then create a new Todo item.
    todo app showing two items
  • About the Todo controller

    The Todo controller is similar to what you would have created with ASP.NET MVC 5 with one exception. In this sample, the database context is injected into the Todo controller with the new Inversion of Control (IoC) container. You can read more about this in the blog Dependency Injection in ASP.NET vNext.

    Introducing view components

    New to ASP.NET MVC 6, view components (VCs) are similar to partial views, but they are much more powerful. VCs include the same separation-of-concerns and testability benefits found between a controller and view. You can think of a VC as a mini-controller—it’s responsible for rendering a chunk rather than a whole response. You can use VCs to solve any problem that you feel is too complex with a partial, such as:

    • Dynamic navigation menus
    • Tag cloud (where it queries the database)
    • Login panel
    • Shopping cart
    • Recently published articles
    • Any other sidebar content on a typical blog

    One use of a VC could be to create a login panel that would be displayed on every page with the following functionality:

    • If the user is not logged in, a login panel is rendered.
    • If the user is logged in, links to log out and manage his or her account are rendered.
    • If the user is in the admin role, an admin panel is rendered.

    You can also create a VC that gets and renders data depending on the user's claims. You can add this VC view to the layout page and have it get and render user-specific data throughout the whole application.

    A VC consists of two parts, the class (typically derived from ViewComponent) and the Razor view which calls methods in the VC class. Like the new ASP.NET controllers, a VC can be a POCO, but most users will want to take advantage of the methods and properties available by deriving from ViewComponent.

    A view component class can be created by:

    • Deriving from ViewComponent.
    • Decorating the 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.

    Like controllers, VCs must be public, non-nested, non-abstract classes.

    Adding a view component class

    1. Create a new folder called ViewComponents. View component classes can be contained in any folder in the project.
    2. Create a new class file called PriorityListViewComponent.cs in the ViewComponents folder.
    3. Replace the contents of the PriorityListViewComponent.cs file with the following:
      using System.Linq;
      using Microsoft.AspNet.Mvc;
      using TodoList.Models;
      
      namespace TodoList.ViewComponents
      {
        public class PriorityListViewComponent : ViewComponent
        {
          private readonly ApplicationDbContext db;
      
          public PriorityListViewComponent(ApplicationDbContext context)
          {
            db = context;
          }
      
          public IViewComponentResult Invoke(int maxPriority)
          {
            var items = db.TodoItems.Where(x => x.IsDone == false &&
                                              x.Priority <= maxPriority);
      
            return View(items);
          }
        }
      }
    Notes on the code:
    • Because the class name PriorityListViewComponent ends with the suffix ViewComponent, the runtime will use the string "PriorityList" when referencing the class component from a view. I'll explain that in more detail later.
    • The [ViewComponent] attribute can used to change the name used to reference a VC. For example, we could have named the class XYZ,  and  applied the  ViewComponent attribute:
      [ViewComponent(Name = "PriorityList")]
      public class XYZ : ViewComponent
      The [ViewComponent] attribute above tells the view component selector to use the name PriorityList when looking for the views associated with the component, and to use the string "PriorityList" when referencing the class component from a view. I'll explain that in more detail later.
    • The component uses constructor injection to make the data context available,  just as we did with the Todo controller.
    • Invoke exposes a method to be called from a view and it can take an arbitrary number of arguments. An asynchronous version, InvokeAsync, is available. We'll see InvokeAsync and multiple arguments later in the tutorial. In the previous code, the Invoke method  returns the set of ToDoItems that are not completed and have priority greater than or equal to  maxPriority.

    Adding a view component view

    1. Create a new folder called Components in under the Views\Todo folder. This folder must be named Components.
    2. Create a new folder called PriorityList in under the Views\Todo\Components folder. This folder name must match the name of the view component class, or the name of the class minus the suffix (if we followed convention and used the ViewComponent suffix in the class name). If you used the the  ViewComponent attribute, the class name would need to match the attribute designation. 
    3. Create a Default.cshtml Razor view file in the Views\Todo\Components\PriorityList  folder, and add the following markup:
      @model IEnumerable<TodoList.Models.TodoItem>
      
      <h3>Priority Items</h3>
      <ul>
          @foreach (var todo in Model)
          {
              <li>@todo.Title</li>
          }
      </ul>
      The Razor view takes a list of TodoItems and displays them. If the VC invoke method doesn't pass the name of the view (as in our sample),  Default is used for the view name by convention. Later in the tutorial, I'll show you how to pass the name of the view.
    4. Add a div containing a call to the priority list component to the bottom of the views\todo\index.cshtml file:
      @{
        ViewBag.Title = "ToDo Page";
      }
      
      <div class="jumbotron">
        <h1>ASP.NET vNext</h1>
      </div>
      
      <div class="row">
        <div class="col-md-4">
          @if (Model.Count == 0)
          {
            <h4>No Todo Items</h4>
          }
          else
          {
            <table>
              <tr><th>TODO</th><th></th></tr>
              @foreach (var todo in Model)
              {
                <tr>
                  <td>@todo.Title </td>
                  <td>
                    @Html.ActionLink("Details", "Details", "Todo", new { id = todo.Id }) |
                    @Html.ActionLink("Edit", "Edit", "Todo", new { id = todo.Id }) |
                    @Html.ActionLink("Delete", "Delete", "Todo", new { id = todo.Id })
                  </td>
                </tr>
              }
            </table>
                    }
          <div>@Html.ActionLink("Create New Todo", "Create", "Todo") </div>
        </div>
      
        <div class="col-md-4">
          @Component.Invoke("PriorityList", 1)   
        </div>
      
      </div>
      The markup @await Component.InvokeAsync() shows the syntax for calling view components. The first argument is the name of the component we want to invoke or call. Subsequent parameters are passed to the component. In this case, we are passing "1" as the priority we want to filter on. The InvokeAsync method can take an arbitrary number of arguments.

      The following image shows the priority items:
      View with Priority VC
    Note: View Component views are more typically added to the Views\Shared folder, because VCs are typically not controller specific.

    Add InvokeAsync to the priority component

    Update the priority view component class with the following code:

    using System.Linq;
    using Microsoft.AspNet.Mvc;
    using TodoList.Models;
    using System.Threading.Tasks;
    
    namespace TodoList.ViewComponents
    {
        public class PriorityListViewComponent : ViewComponent
        {
            private readonly ApplicationDbContext db;
    
            public PriorityListViewComponent(ApplicationDbContext context)
            {
                db = context;
            }
    
            // Synchronous Invoke removed.
            
            public async Task<IViewComponentResult> InvokeAsync(int maxPriority, bool isDone)
            {
                string MyView = "Default";
    
                // If asking for all completed tasks, render with the "PVC" view.
                if (maxPriority > 3 && isDone == true)
                {
                    MyView = "PVC";
                }
    
                var items = await GetItemsAsync(maxPriority, isDone);
    
                return View(MyView, items);
            }
    
            private Task<IQueryable<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
            {
                return Task.FromResult(GetItems(maxPriority, isDone));
    
            }
            private IQueryable<TodoItem> GetItems(int maxPriority, bool isDone)
            {
                var items = db.TodoItems.Where(x => x.IsDone == isDone &&
                                                    x.Priority <= maxPriority);
    
                string msg = "Priority <= " + maxPriority.ToString() +
                             " && isDone == " + isDone.ToString();
                ViewBag.PriorityMessage = msg;
    
                return items;
            }
    
        }
    }
    Note: The synchronous Invoke method has been removed. A best practice is to use asynchronous methods when calling a database.

    Update the VC Razor view to show the priority message :

    @model IEnumerable<TodoList.Models.TodoItem>
    
    <h4>@ViewBag.PriorityMessage</h4>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Title</li>
        }
    </ul>

    Finally, update the  views\todo\index.cshtml view:

        @* Markup removed for brevity. *@
        
        <div class="col-md-4">
            @await Component.InvokeAsync("PriorityList", 2, true)
        </div>
    </div>

    The following image reflects the changes we made to the priority VC and Index view:

    index view showing priority and isDone == true

    Specifying a view name

    A complex VC might need to specify a non-default view under some conditions. The following shows how to specify the "PVC" view  from the InvokeAsync method:

    public async Task<IViewComponentResult> InvokeAsync(int maxPriority, bool isDone)
    {
        string MyView = "Default";
    
        // If asking for all completed tasks, render with the "PVC" view.
        if (maxPriority > 3 && isDone == true)
        {
            MyView = "PVC";
        }
    
        var items = await GetItemsAsync(maxPriority, isDone);
    
        return View(MyView, items);
    }

    Copy Views\Todo\Components\PriorityList\Default.cshtml to a Views\Todo\Components\PriorityList\PVC.cshtml view. I changed the PVC view to verify it's being used:

    @model IEnumerable<TodoList.Models.TodoItem>
    
    <h2> PVC Named Priority Component View</h2>
    <h4>@ViewBag.PriorityMessage</h4>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Title</li>
        }
    </ul>

    Finally, update the Views\Todo\Index.cshtml:

    @await Component.InvokeAsync("PriorityList",  4, true)

    Refresh the page to see the PVC view.

    When you're running the app with ^F5 (not in debug mode), you don't need to compile or run the app when you make changes to your controller (or any code files) as you previously had to with ASP.NET MVC. You only need to save the file and refresh the page.

    Injecting a service into a view

    ASP.NET MVC 6 now supports injection into a view from a class. Unlike a VC class, there are no restrictions other than the class must be must be public, non-nested and non-abstract. For this example, we'll create a simple class that exposes the total todo count, completed count and average priority.

    1. Create a folder called Services and add a class file called StatisticsService.cs (or you can copy existing item from the sample download).
    2. The StatisticsService class:

      using System.Linq;
      using System.Threading.Tasks;
      using TodoList.Models;
      
      namespace TodoList.Services
      {
        public class StatisticsService
        {
          private readonly ApplicationDbContext db;
      
          public StatisticsService(ApplicationDbContext context)
          {
            db = context;
          }
      
          public async Task<int> GetCount()
          {
            return await Task.FromResult(db.TodoItems.Count());
          }
      
          public async Task<int> GetCompletedCount()
          {
            return await Task.FromResult(
                db.TodoItems.Count(x => x.IsDone == true));
          }
      
          public async Task<double> GetAveragePriority()
          {
            return await Task.FromResult(
                db.TodoItems.Average(x =>
                           (double?)x.Priority) ?? 0.0);
          }
        }
      }
    3. Update the Index view to inject the todo statistical data. Add the inject statement to the top of the file:

      @inject TodoList.Services.StatisticsService Statistics

      Add markup calling the StatisticsService to the end of the file:

      <div>@Html.ActionLink("Create New Todo", "Create", "Todo") </div>
          </div>
           
          <div class="col-md-4">
              @await Component.InvokeAsync("PriorityList", 4, true)
      
            <h3>Stats</h3>
            <ul>
              <li>Items: @await Statistics.GetCount()</li>
              <li>Completed:@await Statistics.GetCompletedCount()</li>
              <li>Average Priority:@await Statistics.GetAveragePriority()</li>
            </ul>
          </div>
      </div>

      The completed file is shown below:

      @inject TodoList.Services.StatisticsService Statistics
      @{
          ViewBag.Title = "Home Page";
      }
      
      <div class="jumbotron">
          <h1>ASP.NET vNext</h1>
      </div>
      
      <div class="row">
          <div class="col-md-4">
              @if (Model.Count == 0)
              {
                  <h4>No Todo Items</h4>
              }
              else
              {
                  <table>
                      <tr><th>TODO</th><th></th></tr>
                      @foreach (var todo in Model)
                      {
                          <tr>
                              <td>@todo.Title </td>
                              <td>
                                  @Html.ActionLink("Details", "Details", "Todo", new { id = todo.Id }) |
                                  @Html.ActionLink("Edit", "Edit", "Todo", new { id = todo.Id }) |
                                  @Html.ActionLink("Delete", "Delete", "Todo", new { id = todo.Id })
                              </td>
                          </tr>
                      }
                  </table>
                                  }
              <div>@Html.ActionLink("Create New Todo", "Create", "Todo") </div>
          </div>
           
          <div class="col-md-4">
              @await Component.InvokeAsync("PriorityList", 4, true)
      
            <h3>Stats</h3>
            <ul>
              <li>Items: @await Statistics.GetCount()</li>
              <li>Completed:@await Statistics.GetCompletedCount()</li>
              <li>Average Priority:@await Statistics.GetAveragePriority()</li>
            </ul>
          </div>
      </div>
    4. Register the StatisticsService class in the Startup.cs file:

      // This method gets called by the runtime.
      public void ConfigureServices(IServiceCollection services)
      {
        // Add EF services to the services container.
        services.AddEntityFramework(Configuration)
            .AddSqlServer()
            .AddDbContext<ApplicationDbContext>();
      
        // Add Identity services to the services container.
        services.AddDefaultIdentity<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration);
      
        // Add MVC services to the services container.
        services.AddMvc();
      
        services.AddTransient<TodoList.Services.StatisticsService>();
      }

      The statistics are displayed:

      Index view with stats data

    Publish to Azure

    To complete this tutorial, you need a Microsoft Azure account. If you don't have an account, you can activate your MSDN subscriber benefits or sign up for a free trial.

    1. Right click on the TodoList project > Publish.

      publish
    2. In the Publish Web dialog, click on Microsoft Azure Websites and log into your Azure subscription.

      Pub Web dlg
    3. Click New.

      Select Existing Websites dlg
    4. Enter a site name and region. If you've never created a database server, create a new one. If you've created a database server in the past, use that.

      Create site on Azure dlg - new db server

      Database servers are a precious resource. For test and development it's best to use an existing server. There is no validation on the database password, so if you enter an incorrect value, you won't get an error until your web app attempts to access the database.

      Create site on Azure dlg - existing db server

    5. On the Connection step > Next.

      connection step

    6. On the Settings step, select the target KRE version.

      settings step dlg

    7. Click Publish.
    8. The app running on Azure.

      Todo app running in azzure

    This article was originally created on November 12, 2014

    Author Information

    Rick Anderson

    Rick Anderson – Rick Anderson works as a programmer writer for Microsoft, focusing on ASP.NET MVC, Windows Azure and Entity Framework. You can follow him on twitter via @RickAndMSFT.

    Mike Wasson

    Mike Wasson – Mike Wasson is a programmer-writer at Microsoft.