ASP.NET Core MVC

ASP.NET Core MVC 是 ASP.NET Core 内，提供给 Web 应用程序开发的框架，它可视为 ASP.NET MVC 的后继版本，其主要功能均衍生自 ASP.NET MVC，但它除了基于 ASP.NET Core 外，也将 ASP.NET MVC 与类似平台进行了整合，例如负责 View 的 ASP.NET Web Pages 以及负责 RESTful API 的 ASP.NET Web API，都与 ASP.NET Core MVC 的核心合并，因此在 ASP.NET Core MVC 中将可同时并存 MVC 网页以及 RESTful API。

## 相关组件

ASP.NET Core MVC 包含了下列组件，基于 .NET Core 的精神，只有需要用到的才需要加入参考 (于 project.json)，因此开发者可以自由选择，而不必把所有的组件都加进来。

组件｜功能

Microsoft.AspNetCore.Mvc｜ASP.NET Core MVC 引用套件

Microsoft.AspNetCore.Mvc.Abstractions｜ASP.NET Core MVC 功能的抽象层

Microsoft.AspNetCore.Mvc.ApiExplorer｜ASP.NET Core MVC 的 API 文件支援

Microsoft.AspNetCore.Mvc.Core｜ASP.NET Core MVC 核心组件

Microsoft.AspNetCore.Mvc.Cors｜提供 Web API 所需要的 CORS 能力

Microsoft.AspNetCore.Mvc.DataAnnotations｜MVC 所需的资料标示 (Data Annotation) 功能

Microsoft.AspNetCore.Mvc.Formatters.Json｜MVC/Web API 所需的 JSON 序列化器

Microsoft.AspNetCore.Mvc.Formatters.Xml｜MVC/Web API 所需的 XML 序列化器

Microsoft.AspNetCore.Mvc.Localization｜MVC 应用程序本地化支援

Microsoft.AspNetCore.Mvc.Razor｜MVC Razor 的核心类别库 (若要在 MVC 中使用 Razor 就必须参考此组件)

Microsoft.AspNetCore.Mvc.Razor.Host｜MVC Razor 的执行期引擎

Microsoft.AspNetCore.Mvc.TagHelpers｜MVC Tag Helper 的核心类别库

Microsoft.AspNetCore.Mvc.ViewFeatures｜MVC View 功能的类别库 (Controller 类别的实作在此)

Microsoft.AspNetCore.Mvc.WebApiCompatShim｜Web API 相容套件

Microsoft.AspNetCore.Razor｜Razor 的核心类别库

## 基础建设

ASP.NET Core MVC 采用 ASP.NET Core 作为基础，因此享有内建的相依注入能力 (Dependency Injection)，ASP.NET Core MVC 本身也是 ASP.NET Core 的服务之一，因此必须要在 ASP.NET Core 的起始类别中注册并使用 MVC，才可以享有 MVC 的功能。下列例子即为在一个 ASP.NET Core 的程式的起始类别 (通常被命名为 Startup) 中注册并启用 ASP.NET Core MVC 的程式码：

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // 加入 ASP.NET Core MVC 服務
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    // ...
    // 啟用 ASP.NET Core MVC
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}
```

注册 ASP.NET Core MVC 服务后，ASP.NET Core 会自动将 MVC 的执行引擎加入 ASP.NET Core 的管线式相依注入 (Pipeline-based Dependency Injection) 的服务清单内，以开始提供 MVC 的相关服务。

## 路由

ASP.NET Core MVC 强化了 ASP.NET Routing 技术，使其更具弹性，除了原有的由起始类别加入的路由外，亦全面整合了之前在 ASP.NET MVC 5.2 / Web API 2.1 起支援的属性路由能力 (Attribute Routing)，这表示开发人员不一定需要在起始类别注册 MVC 时定义路由，只需要在 Controller 内加入路由设定即可，但官方还是建议至少加入预设路由 (default routes)，例如：

```csharp
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
```

## Controller

ASP.NET Core MVC 可同时支援 MVC 本身的功能以及 Web API 的功能，它们都源自相同的 Controller 基底类别，此类别已被重新实作，以支援一般的 View 以及 RESTful API 的回传值，微软亦重新定义了 ActionResult 类别，提出新的 IActionResult 界面，但开发人员不一定要回传 IActionResult 界面，也可以直接回传 .NET 内建的资料型态，Controller 会自动将它对应到 Content Result。虽然微软建议以 IActionResult 为传回型别，但原本的 ActionResult 型别仍然适用。

下列程式是一个标准的 ASP.NET Core MVC Controller 的实作，和 ASP.NET MVC 差异相当小。

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebApplication18.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";

            return View();
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}
```

## Model

ASP.NET Core MVC 的 Model 与 ASP.NET MVC 上使用的概念类似，官方虽建议使用 Entity Framework Core，但却不是强制，开发者可以依应用程序自身的需求来定义 Model，也可以将 Model 移到别的类别库内与其他专案共用。

基于关注点分离的需要，在 MVC 应用程序内会依 View 的需求另外建立单独的 Model，此类 Model 称为 View Model，不过 ASP.NET Core MVC 也没有针对这个做特别的限制。

## View

ASP.NET Core MVC 的 View 除了由 ASP.NET MVC 衍生而来的标准的 View 功能外，另外新增了数项 View 的功能，包含 View Component 以及 Tag Helper。

### View Component

View Component (检视元件) 与原有的 Partial View (部分检视) 相当类似，MVC 5 也可以利用 Child Action 加上 Partial View 的机制来实作出与 View Component 相同的功能，但基于关注点分离原则，若在 Controller 中加入过多的 Child Action 反而会造成 Controller 职责过重，Controller 的程式码也会变得肥大，因此 Core MVC 加入这个新功能，每一个 View Component 都是独立的后端程式，以一对一的方式对应 View。

View Component 基本上可以看做是一个类似 Controller 的元件，它也可以使用像 ViewBag 或 TempData 这样的功能，不过它是由 View 来唤起的，例如下列程式为 View Component 的范例实作：

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace HelloMvc.Components
{
    [ViewComponent(Name = "AddNumber")]
    public class AddNumberComponent : ViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync(int Number1, int Number2)
        {
            int result = Number1 + Number2;
            ViewBag.N1 = Number1;
            ViewBag.N2 = Number2;
            return View(result);
        }
    }
}
```

View Component 可以由 Controller 唤起 (回传 ViewComponentResult) 或是由 View 唤起 (使用 Razor 的 Component.InvokeAsync)，例如：

```html
<div>@await Component.InvokeAsync("AddNumber", new { Number1 = 1, Number2 = 2})</div>
```

### Tag Helper

Tag Helper 是 ASP.NET Core MVC 加入的最具威力的 View 功能，在还没有 Tag Helper (即 ASP.NET MVC) 的时候，当 View 所需的功能愈来愈多时，一张 View (cshtml/vbhtml) 会充斥着 Razor 程式码或是 Partial View 的呼叫，让整个 View 显得相当凌乱，而 Tag Helper 所提供的功能就是直接基于 HTML Tag 本身进行处理，不但可以扩充现有的 tag，也可以自订自己的 tag，例如 ASP.NET MVC 时期的表单，大多会用 @Html.BeginForm 进行包装，例如：

```csharp
@using (Html.BeginForm("Login", "Account", new { returnurl = ViewData["ReturnUrl"] }, FormMethod.Post, new { @class = "form-horizontal" }))
{
  // ...
}
```

但使用了 Tag Helper 之后，View 上的程式码就可以移除，变成：

```html
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
...
</form>
```

其中的 asp-controller、asp-action、asp-route-returnurl 即是使用 Tag Helper 扩充而得。

Tag Helper 的实作与 View Component 类似，它要求继承自 Microsoft.AspNetCore.Razor.TagHelpers 内的 TagHelper 类别，例如：

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace HelloMvc.TagHelpers
{
    public class EmailTagHelper : TagHelper
    {
        private const string EmailDomain = "contoso.com";

        // Can be passed via <email mail-to="..." />. 
        // Pascal case gets translated into lower-kebab-case.
        public string MailTo { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "a";    // Replaces <email> with <a> tag

            var address = MailTo + "@" + EmailDomain;
            output.Attributes.SetAttribute("href", "mailto:" + address);
            output.Content.SetContent(address);
        }
    }
}
```

然后在 View 中引用这个 Tag Helper (可以在 _ViewImport.cshtml 内引用或是在该 View 中引用)，就可以直接使用自己定义的 Tag 了。

```html
<div>
    <h4><email mail-to="jason">support</email></h4>
</div>
```

## 相依注入功能

受惠于 ASP.NET Core 的基础建设，ASP.NET Core MVC 能充份享有基础建设所支援的相依注入能力，在起始类别中加入对服务的注册，就能够在 Controller 与 View 中使用注册的类别。

例如下列程式会在服务清单中加入一个自订的类别：

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // 註冊服務
    services.AddTransient<IDateTimeIndicator, DefaultDateTimeIndicator>(); // DefaultDateTimeIndicator 實作了 IDateTimeIndicator 介面
}
```

若要在 Controller 中使用这个注册的服务，可以使用下列三种方式：

- 建构式注入 (Constructor Injection)：在 Controller 的建构式中加入该服务的参数，当 Controller 被生成时，ASP.NET Core MVC 会由服务清单中取出指定的界面物件。
- 方法注入 (Method Injection)：在 Controller 的方法中加入 FromService 的修饰，ASP.NET Core MVC 若发现方法中有这样的修饰时就会由服务清单中取出指定的界面物件。
- 属性注入 (Property Injection)：在 Controller 的属性以 FromService 修饰，ASP.NET Core MVC 若发现属性中有这样的修饰时就会由服务清单中取出指定的界面物件。

例如：

```csharp
public class SettingsController : Controller
{
    private readonly SampleWebSettings _settings;

    public SettingsController(IOptions<SampleWebSettings> settingsOptions ) // 注入服務
    {
        _settings = settingsOptions.Value;
    }

    public IActionResult Index()
    {
        ViewData["Title"] = _settings.Title;
        ViewData["Updates"] = _settings.Updates;
        return View();
    }
}
```

若要在 View 中使用，则需要用 @inject 指令指定服务，接着就可以使用此变数来操作服务。

```html
@using HelloMvc.Services;
@inject IDateTimeIndicator indicator 
<html>
    <body>
        ...
        <div>
            <ul>
                <li>@indicator.GetNowIndicator(new DateTime(2016, 5, 7, 0, 0, 0))</li>
                <li>@indicator.GetNowIndicator(new DateTime(2016, 5, 7, 0, 5, 0))</li>
                <li>@indicator.GetNowIndicator(new DateTime(2016, 5, 6, 0, 20, 0))</li>
                <li>@indicator.GetNowIndicator(new DateTime(2016, 5, 5, 0, 0, 0))</li>
            </ul>
        </div>
    </body>
</html>
```

## 基本资料

- 开发者：Microsoft
- 类型：跨平台Web应用程序、MVC
- 许可协议：Apache License 2.0
- 编程语言：.NET 程式语言，例如C#、VB.NET
- 网站：文件库、 GitHub、入口