在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(上)),不对的地方欢迎指出与交流。章节出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位阅读时仔细分辨,唯望莫误人子弟。附英文版原文:Professional C# 6 and .NET Core 1.0 - Chapter 41 ASP.NET MVC C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(中) C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(下) ------------------------------------ 本章内容
Wrox.com网站下载本章代码
第40章“ASP.NET Core”展示了ASP.NET MVC的基础:ASP.NET Core 1.0 第40章展示了中间件以及依赖注入如何与ASP.NET结合使用。本章通过注入ASP.NET MVC服务来使用依赖注入。 ASP.NET MVC是基于MVC(模型 - 视图 - 控制器)模式。如 图41.1所示,这种标准模式(一个文档中描述的设计模式:由Gang of Four [Addison-Wesley Professional,1994]的可重复使用的面向对象软件的元素)定义了一个实现数据实体和数据访问的模型,向用户展示信息的视图,以及利用模型向视图发送数据的控制器。控制器从浏览器接收请求并返回响应。要创建响应,控制器可以使用模型提供一些数据,以及一个视图来定义返回的HTML。
图41.1 MVC模式中的这种分离的最大优点是,可以使用单元测试轻松测试功能。控制器只包含具有参数和返回值的方法,这些参数和返回值可以通过单元测试轻松遍历。 现在开始为ASP.NET MVC 6设置服务。ASP.NET Core 1.0的依赖注入是深入集成的,如第40章中所示。可以选择ASP.NET Core 1.0模板“Web应用程序”创建一个ASP.NET MVC 6项目。该模板已经包括ASP.NET MVC 6所需的NuGet包以及帮助组织应用程序的目录结构。但是,这里将从空模板开始(类似于第40章),所以你可以看到创建一个ASP.NET MVC 6项目所需要的东西,不会有项目可能不需要的额外的东西。 创建的第一个项目名为 MVCSampleApp。要在 Web应用程序MVCSampleApp 使用ASP.NET MVC,需要添加NuGet包 Microsoft.AspNet.Mvc 。该包准备就绪后,通过在ConfigureServices方法中调用扩展方法AddMvc来添加MVC服务(代码文件MVCSampleApp/Startup.cs): using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
// etc.
namespace MVCSampleApp
{
public class Startup
{
// etc.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// etc.
}
// etc.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseDefaultConfiguration(args)
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
AddMvc 扩展方法添加和配置几个 ASP.NET MVC核心服务,如配置功能(IConfigureOptions与MvcOptions和RouteOptions);控制器工厂和控制器触发器(IControllerFactory,IControllerActivator)、动作方法选择器,调用者和约束提供者(IActionSelector,IActionInvokerFactory,IActionConstraintProvider)、参数绑定器和模型验证器(IControllerActionArgumentBinder,IObjectModelValidator)、和过滤器提供程序(IFilterProvider)。 定义路由第40章解释了 IapplicationBuilder 的 Map 扩展方法如何定义一个简单的路由。本章展示了 ASP.NET MVC 路由如何基于映射提供了一种灵活的路由机制,用于将URL映射到控制器和操作方法。 控制器是基于路由选择的。创建默认路由的一种简单方法是在启动类中调用UseMvcWithDefaultRoute方法(代码文件MVCSampleApp/Startup.cs): public void Configure(IApplicationBuilder app)
{
// etc.
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
// etc.
}
注意 扩展方法 UseStaticFiles 已在第40章讨论。此方法需要添加 Microsoft.AspNet.StaticFiles NuGet包。 使用默认路由后,控制器类型的名称(不带Controller后缀)和方法名称组成路由,例如 http://server[:port]/controller/action 。还可以使用名为id的可选参数,例如: http://server[:port]/controller/action/id 。控制器的默认名称为 Home ; action方法的默认名称为Index。 app.UseMvc(routes =< with => routes.MapRoute(
name:"default",
template:"{controller}/{action}/{id?}",
defaults: new {controller ="Home", action ="Index"}
));
此路由定义与默认路由定义相同。template 参数定义URL,id? 定义了该参数是可选的; defaults 参数定义URL的控制器和动作部分的默认值。 让我们看看这个URL: http://localhost:[port]/UseAService/GetSampleStrings
这个URL 中 UseAService 映射到控制器的名称,因为 Controller 后缀是自动添加的,类型名称为UseAServiceController,GetSampleStrings 是操作,它表示 UseAServiceController 类中的一个方法。 添加路由添加或更改路由有几个原因。例如,可以修改路由去使用链接操作,将Home定义为默认控制器,向链接添加实体或使用多个参数。 可以定义路由让用户使用链接(例如 http://<server>/About )在Home控制器中寻址“关于”操作方法,而不传递控制器名称,如以下代码段所示。请注意,控制器名称不在URL中。 controller关键字对于路由是必需的,但可以为其提供默认值: app.UseMvc(routes => routes.MapRoute( name:"default", template:"{action}/{id?}", defaults: new {controller ="Home", action ="Index"} )); 更改路由的另一个方案如以下代码段所示。在此代码段中,向路径中添加变量 language 。该变量设置为URL中位于服务器名称之后并放置在控制器之前,例如 http://server/en/Home/About 。可以使用它来指定语言: app.UseMvc(routes => routes.MapRoute( name:"default", template:"{controller}/{action}/{id?}", defaults: new {controller ="Home", action ="Index"} ).MapRoute( name:"language", template:"{language}/{controller}/{action}/{id?}", defaults: new {controller ="Home", action ="Index"} ); 如果一个路由匹配并且找到了控制器和动作方法,则采取路由,否则选择下一条路由,直到找到一条路由匹配。 使用路由约束 映射路由时,可以指定约束。这种情况下,不符合约束定义的URL 是不能被解析的。以下约束定义 language 参数使用正则表达式(en)|(de)来指定只能是 en或de。例如 只有 http://<server>/en/Home/About 或 http://<server>/de/Home/About 的URL有效: app.UseMvc(routes => routes.MapRoute( name:"language", template:"{language}/{controller}/{action}/{id?}", defaults: new {controller ="Home", action ="Index"}, constraints: new {language = @"(en)|(de)"} )); 如果链接只应启用数字(例如,要访问具有产品编号的产品),正则表达式 \d+ 匹配任意数量的数字,但必须至少匹配一个数字: app.UseMvc(routes => routes.MapRoute( name:"products", template:"{controller}/{action}/{productId?}", defaults: new {controller ="Home", action ="Index"}, constraints: new {productId = @"\d+"} )); 现在已经看到了路由如何指定使用的控制器和控制器的动作。下一节“创建控制器”介绍了控制器的详细信息。 创建控制器控制器对来自用户的请求做出反应并发送响应。本节所讲述的内容,可以不需要视图。 使用ASP.NET MVC时有一些约定要优于配置。控制器也会看到一些约定。可以在目录 Controllers 中找到控制器,并且控制器类的名称必须以名称 Controller 为后缀。 在创建第一个控制器之前,先创建Controllers目录。然后,可以通过在解决方案资源管理器中选择此目录来创建控制器,从上下文菜单中选择 Add -> New Item,并选择MVC控制器类项目模板。 HomeController是为指定的路由创建的。 生成的代码包含从基类Controller派生的HomeController类。此类还包含与 Index 操作对应的 Index 方法。当请求由路由定义的操作时,将调用控制器中的方法(代码文件MVCSampleApp/Controllers/HomeController.cs): public class HomeController : Controller { public IActionResult Index() => View(); } 了解操作方法控制器包含操作(译者注: action,也译作 动作)方法。以下代码片段有一个简单的动作 Hello 方法(代码文件MVCSampleApp/Controllers/HomeController.cs): public string Hello() =>"Hello, ASP.NET MVC 6"; 可以使用链接 http://localhost:5000/Home/Hello 在Home控制器中调用Hello操作。当然,端口号取决于设置,可以使用项目设置中的网络属性进行配置。从浏览器打开此链接时,控制器只返回字符串“Hello, ASP.NET MVC 6”,没有HTML - 只是一个字符串。浏览器可以显示字符串。 动作可以返回任何内容,例如,图像,视频,XML或JSON数据的字节,当然,也可以是HTML。视图对于返回HTML有很大的帮助。 使用参数可以使用参数声明动作方法,如下面的代码片段(代码文件MVCSampleApp/Controllers/HomeController.cs): public string Greeting(string name) => HtmlEncoder.Default.Encode($"Hello, {name}"); 注意 HtmlEncoder需要NuGet包System.Text.Encodings.Web。 有了这个声明,可以调用Greeting操作方法去请求以下的URL,在URL中传递带有name参数的值:http://localhost:18770/Home/Greeting?name=Stephanie。 要使用更容易记住的链接,可以使用路由信息来指定参数。 Greeting2操作方法指定名为id的参数。 public string Greeting2(string id) => HtmlEncoder.Default.Encode($"Hello, {id}"); 这与默认路由{controller}/{action}/{id?}匹配,其中id被指定为可选参数。现在使用下面的链接,id参数包含字符串Matthias:http://localhost:5000/Home/Greeting2/Matthias。 还可以使用任意数量的参数声明操作方法。例如,可以使用两个参数将“Add”操作方法添加到Home控制器,如下所示: public int Add(int x, int y) => x + y;
可以使用URL http://localhost:18770/Home/Add?x=4&y=5 调用此操作以填充x和y参数。 使用多个参数,还可以定义一个路由以使用不同的链接传递值。以下代码片段显示了在路由表中定义的一个附加路由,以指定填充变量x和y的多个参数(代码文件MVCSampleApp/Startup.cs): app.UseMvc(routes =< routes.MapRoute( name:"default", template:"{controller}/{action}/{id?}", defaults: new {controller ="Home", action ="Index"} ).MapRoute( name:"multipleparameters", template:"{controller}/{action}/{x}/{y}", defaults: new {controller ="Home", action ="Add"}, constraints: new {x = @"\d", y = @"\d"} )); 现在,可以使用此URL调用与之前相同的操作:http://localhost:18770/Home/Add/7/2。 注意 本章后面的“将数据传递到视图”部分中,将看到可以使用自定义类型的参数,以及客户端的数据如何映射到属性。 返回数据目前为止,只从控制器返回字符串值。通常,返回实现接口 IActionResult 的对象。 以下是 ResultController 类的几个示例。第一个代码片段使用 ContentResult 类返回简单的文本内容。可以使用来自基类Controller 的方法来返回 ActionResults,而不是创建 ContentResult 类的实例并返回该实例。在以下示例中,方法 Content 用于返回文本内容。 Content方法允许指定内容,MIME类型和编码(代码文件MVCSampleApp/Controllers/ResultController.cs): public IActionResult ContentDemo() => Content("Hello World","text/plain"); 要返回JSON格式的数据,可以使用Json方法。以下示例代码创建一个Menu对象: public IActionResult JsonDemo() { var m = new Menu { Id = 3, Text ="Grilled sausage with sauerkraut and potatoes", Price = 12.90, Date = new DateTime(2016, 3, 31), Category ="Main" }; return Json(m); } Menu类在Models目录中定义,它定义了一个简单的仅有一些属性的POCO 类(代码文件MVCSampleApp/Models/Menu.cs): public class Menu { public int Id {get; set;} public string Text {get; set;} public double Price {get; set;} public DateTime Date {get; set;} public string Category {get; set;} } 客户端在响应正文中看到此JSON数据。 JSON数据可以轻松地作为JavaScript对象使用: {"Id":3,"Text":"Grilled sausage with sauerkraut and potatoes",
"Price":12.9,"Date":"2016-03-31T00:00:00","Category":"Main"}
使用Controller类的Redirect方法,客户端接收HTTP重定向请求。在接收到重定向请求后,浏览器请求它接收的链接。 Redirect方法返回一个RedirectResult(代码文件MVCSampleApp/Controllers/ResultController.cs): public IActionResult RedirectDemo() => Redirect("http://www.cninnovation.com"); 还可以通过指定重定向到另一个控制器和操作来为客户端生成重定向请求。 RedirectToRoute 返回一个 RedirectToRouteResult,用于指定路由名称,控制器,操作和参数。这将构建一个使用HTTP重定向请求返回到客户端的链接: public IActionResult RedirectRouteDemo() => RedirectToRoute(new {controller ="Home", action="Hello"}); Controller基类的File方法定义了返回不同类型的不同重载。此方法可以返回FileContentResult,FileStreamResult和VirtualFileResult。不同的返回类型取决于所使用的参数 - 例如,VirtualFileResult的字符串,FileStreamResult的Stream和FileContentResult的字节数组。 下一个代码段返回一个图像。创建 Images 文件夹并添加一个JPG文件。要使下一个代码段工作,请在 wwwroot 目录中创建一个Images文件夹,并添加文件Matthias.jpg。示例代码返回一个VirtualFileResult,它在第一个参数指定文件名,第二个参数指定MIME类型为image/jpeg的contentType参数: public IActionResult FileDemo() => File("~/images/Matthias.jpg","image/jpeg"); 下一节显示如何返回不同的ViewResult。 使用控制器基类和POCO控制器到目前为止,所有创建的控制器都是从基类 Controller 派生的。 ASP.NET MVC 6还支持不从这个基类派生的控制器 - 被称为POCO(普通旧CLR对象)控制器。这样,可以用自定义的基类来定义控制器类型层次结构。 从Controller基类中得到什么?这个基类能使控制器直接访问基类的属性。下表描述了这些属性及其功能。
POCO控制器没有Controller基类,但访问这些信息仍然很重要。以下代码片段定义了从对象基类派生的POCO控制器(当然您可以使用自定义类型作为基类)。要使用POCO类创建一个ActionContext,可以创建此类型的属性。 POCO Controller类使用ActionContext 作为此属性的名称,这类似于Controller类的方式。但是,有一个属性不会自动设置。需要应用ActionContext属性。使用此属性注入实际的ActionContext。 Context属性直接从ActionContext访问HttpContext属性。 Context属性用于从UserAgentInfo 操作方法访问并返回请求中的User-Agent头信息(代码文件MVCSampleApp/Controllers/POCOController.cs): public class POCOController { public string Index() => "this is a POCO controller"; [ActionContext] public ActionContext ActionContext {get; set;} public HttpContext Context => ActionContext.HttpContext; public ModelStateDictionary ModelState => ActionContext.ModelState; public string UserAgentInfo() { if (Context.Request.Headers.ContainsKey("User-Agent")) { return Context.Request.Headers["User-Agent"]; } return"No user-agent information"; } } 创建视图返回到客户端的HTML代码最好使用视图来指定。本节中的示例将创建 ViewsDemoController 。视图都在Views文件夹中定义。 ViewsDemo 控制器的视图需要一个 ViewsDemo子目录。这是视图的约定(代码文件MVCSampleApp/Controllers/ViewsDemoController.cs): public ActionResult Index() => View();
注意 另一个搜索视图的地方是 Shared 目录。可以将多个控制器(以及多个视图使用的特殊部分视图)要使用的视图放入 Shared 目录。 在Views目录中创建 ViewsDemo 目录之后,可以使用Add -> New Item并选择 “MVC View Page” 项目模板来创建视图。因为action方法具有名称Index,所以视图文件被命名为Index.cshtml。 操作方法Index使用没有参数的View方法,因此视图引擎将在ViewsDemo目录中搜索与操作名称相同名称的视图文件。在控制器中使用的View方法有允许传递不同视图名称的重载。在这种情况下,视图引擎将查找传递给View方法的名称的视图。 视图包含混合了一些服务器端代码的HTML代码,如下面的代码段(代码文件 VCSampleApp/Views/ViewsDemo/Index.cshtml)所示: @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Index</title> </head> <body> <div> </div> </body> </html> 服务器端代码使用 @符号 写入,它会启动 Razor 语法,本章后面将讨论。在介绍Razor语法的详细信息之前,下一节将介绍如何将数据从控制器传递到视图。 传递数据到视图控制器和视图在同一进程中运行。视图直接从控制器内创建。这使得将数据从控制器传递到视图变得容易。为了传递数据,可以使用ViewDataDictionary。此字典将键存储为字符串,并启用对象值。可以将 ViewDataDictionary 与 Controller 类的 ViewData 属性一起使用 - 例如,可以将字符串传递到使用键值 MyData 的字典: ViewData[“MyData”] =“Hello” 。一个更简单的语法是使用ViewBag属性。 ViewBag 是一个动态类型,可以分配任何属性名称来传递数据到视图(代码文件MVCSampleApp/Controllers/SubmitDataController.cs): public IActionResult PassingData() { ViewBag.MyData ="Hello from the controller"; return View(); } 注意 使用动态类型的优点是没有从视图到控制器的直接依赖。动态类型在第16章“反射,元数据和动态编程”中有详细说明。 在视图中,可以用类似于控制器的方式访问从控制器传递来的数据。视图的基类(WebViewPage)定义了一个ViewBag属性(代码文件MVCSampleApp/Views/ViewsDemo/PassingData.cshtml): <div> <div>@ViewBag.MyData</div> </div> 了解 Razor 语法如上所述的视图,视图包含HTML和服务器端代码。ASP.NET MVC 中可以使用Razor语法在视图中编写C#代码。 Razor使用@字符 作为转换字符。从@后面就是C#代码。 使用Razor需要区分返回值的语句和不返回值的方法。可以直接使用返回的值。例如,ViewBag.MyData 返回一个字符串。该字符串直接放在HTML div标签之间,如下所示: <div>@ViewBag.MyData</div> 当调用返回 void 的方法或指定一些不返回值的其他语句时,需要一个Razor代码块。以下代码块定义了一个字符串变量: @{
string name ="Angela";
}
现在可以使用简单语法的变量,只需使用转换字符@访问变量: <div>@name</div> 使用Razor语法,引擎会在找到HTML元素时自动检测C#代码的结束。在某些情况下可能无法自动检测到C#代码的结束。可以使用括号来解决此问题,如以下示例所示,以标记变量,然后正常文本继续: <div>@(name), Stephanie</div> 另一种启动Razor代码块的方法是使用foreach语句: @foreach(var item in list) { <li>The item name is @item.</li> } 注意 通常 Razor 自动检测文本内容,例如,Razor检测到打开的尖括号或带有变量的括号。也有一些情况下不起作用。这里可以明确使用 @: 来定义文本的开始。 创建强类型视图将数据传递给视图,我们已经看到了操作中的ViewBag。有另一种方法来传递数据到视图 - 传递模型到视图。使用模型允许您创建强类型视图。 现在 ViewsDemoController 使用动作方法PassingAModel扩展。以下示例创建一个新的菜单项列表,并将此列表作为模型传递给Controller基类的View方法(代码文件MVCSampleApp/Controllers/ViewsDemoController.cs): public IActionResult PassingAModel() { var menus = new List<Menu> { new Menu { Id=1, Text="Schweinsbraten mit Knödel und Sauerkraut", Price=6.9, Category="Main" }, new Menu { Id=2, Text="Erdäpfelgulasch mit Tofu und Gebäck", Price=6.9, Category="Vegetarian" }, new Menu { Id=3, Text="Tiroler Bauerngröst'l mit Spiegelei und Krautsalat", Price=6.9, Category="Main" } }; return View(menus); } 当模型信息从动作方法传递到视图时,可以创建强类型视图。强类型视图使用 model 关键字声明。传递给视图的模型类型必须与模型指令的声明相匹配。在下面的代码片段中,强类型视图声明类型 IEnumerable<Menu> ,它与模型类型匹配。因为 Menu 类在命名空间 MVCSampleApp.Models 中定义,所以这个命名空间使用 using 关键字打开。 从.cshtml文件创建的视图的基类派生自基类 RazorPage 。有了一个模型,基类是 RazorPage<TModel> 类型;以下代码片段基类是 RazorPage<IEnumerable<Menu>> 。此通用参数依次定义类型为 IEnumerable<Menu> 的Model属性。代码片段基类的Model属性用于通过@foreach 遍历 Menu 项,并显示每个菜单的列表项(代码文件MVCSampleApp/ViewsDemo/PassingAModel.cshtml): @using MVCSampleApp.Models @model IEnumerable<Menu> @{ Layout = null; } <!DOCTYPE html> <html> <head> |
请发表评论