[译]View components and Inject in ASP.NET MVC 6

原文:http://www.asp.net/vnext/overview/aspnet-vnext/vc

介绍view components

view components (VCs) 类似于partial views, 但是VCs更加强大. 可以简单的把VC想象成一个mini的控制器.当你认为使用partial太复杂的时候你可以考虑使用VCs,例如:

  • 动态导航菜单Dynamic navigation menus
  • 标签云(从数据库获取数据)
  • 登录面板
  • 购物车
  • 最近发布的文章
  • 博客的其它边栏

VC由两部分组成, 继承自ViewComponent的类和Razor视图.

View Component类可以通过下面的方式创建:

  • 继承ViewComponent.
  • [ViewComponent] attribute 装饰类, 或者继承在一个被[ViewComponent]装饰的类.
  • 创建一个类, 类名以ViewComponent结尾.

和控制器一样VCs必须是public的类.

添加view component类

  1. 创建一个文件夹名为ViewComponents. View component 类可以包含在任何文件夹下.
  2. ViewComponents 文件夹下常见一个类文件名为PriorityListViewComponent.cs.
  3. PriorityListViewComponent.cs 内容如下:
    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);
        }
      }
    }

备注:

  • 因为类名PriorityListViewComponentViewComponent 结尾, 在视图中我们可以使用字符串 "PriorityList".
  • [ViewComponent] attribute 被使用来改变他的引用名. 例如, 我们有一个类名为XYZ,  我们应用ViewComponent attribute:
    [ViewComponent(Name = "PriorityList")]
    public class XYZ : ViewComponent
    上面的[ViewComponent] attribute 告诉view component选择器当查找视图关联的component的使用 使用PriorityList, 在视图中使用字符串"PriorityList" 来关联相应的类.
  • component使用构造函数注入.
  • Invoke 暴露一个方法在相应的视图中被调用, invoke可以包含任意参数. Invoke对应的异步方法是InvokeAsync

添加 view component view

  1. 在 Views\Todo 文件夹下面创建文件夹名为Components. 注意了必须名为Components.
  2. Views\Todo\Components 文件夹下面创建文件夹名为PriorityList. 这个文件夹的名字必须和view component 类的名字匹配, 或者类名的前缀匹配(如果类名使用 ViewComponent 后缀). 如果你使用了ViewComponent attribute, 名字必须和attribute 名匹配. 
  3. 在 Views\Todo\Components\PriorityList 文件夹下创建Default.cshtml Razor视图文件 , 添加如下内容:
    @model IEnumerable<TodoList.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Title</li>
        }
    </ul>
  4. views\todo\index.cshtml 文件中调用VC:
    @{
      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>
    @await Component.InvokeAsync() 是对应的异步方法. 第一个参数是要调用的component的名字. 后面的参数是传到component类的参数. 

注意: 一般来说View Component 视图加在Views\Shared 文件夹下, 因为VCs一般没有指定controller.

添加InvokeAsyn component

更新VC类如下:

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;
        }

    }
}
注意: 同步的Invoke方法被移除了. 当调用数据库的时候使用异步InvokeAsync是最佳实践.

更新VC Razor视图如下 :

@model IEnumerable<TodoList.Models.TodoItem>

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

最后, 更新views\todo\index.cshtml 视图文件:

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

指定view的名字

一个复杂的VC可能会根据不同的条件指定使用非默认的视图. 下面的代码展示如何指定view的名字:

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);
}

复制 Views\Todo\Components\PriorityList\Default.cshtml 文件到Views\Todo\Components\PriorityList\PVC.cshtml . 改变PVC视图的内容以示区分:

@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>

最后, 更新 Views\Todo\Index.cshtml :

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

注入服务到视图中去

ASP.NET MVC 6 支持注入服务到视图中使用. 服务必须是非抽象和公开的. 在这个例子中, 我们穿件一个简单的类暴露todo的数量, 完成的数量等.

    1. 创建一个文件夹名为Services,添加一个名为StatisticsService.cs 的文件.

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);
    }
  }
}
  1. 更新Index视图. 在最上面添加如下注入语句:

    @inject TodoList.Services.StatisticsService Statistics

    在下面调用StatisticsService:

    <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>

    完整代码如下:

    @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>
  2. Startup.cs 文件中注册StatisticsService类:

    // 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>();
    }
 
posted @ 2015-02-25 14:31  irocker  阅读(441)  评论(0编辑  收藏  举报