MVC介绍

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件的设计模式。

  • Model(模型):就是数据模型类
  • View(视图):前端页面,但是是后端渲染的。ASP.NET MVC里面的视图一般是Razor模板,也就是.cshtml文件
  • Controller(控制器):处理逻辑,和视图模型交互。

    创建MVC项目

    在VS里面创建项目,无论是.NET Framework还是.NET Core,创建web项目,都可以选择MVC模板创建。
    里面这三个核心文件夹,把控制器,模型,视图分别放在对应的文件夹里面
    image.png

    Controllers

    里面的类一般是Controller结尾的,路由这个控制器的时候一般不写Controller,只写前面半截名
    image.png
    .NET Framework里面的控制器里面的方法返回的是ActionResult类型。而.NET Core里面的返回的是接口IActionResult类型。
    通过View方法返回视图页面,默认的就是和方法同名的视图,会自动在Views文件夹里面查找。当然可以传入一个名字,或者路径指定返回视图 ```csharp //.NET Framework public ActionResult Index() { return View(); }

//.NET Core public IActionResult Index() { return View(); //View(“TestView”)//指定返回TestView这个名字的视图 }

  1. 写好了一个新的控制器方法,光标定位到该方法上,右键,添加视图。就能在Views文件夹里面看到了
  2. <a name="ALoWc"></a>
  3. ## Views
  4. 这个文件夹里面包含了控制器方法的视图页面。一个控制器一个文件夹.cshtml文件默认和控制器的方法对应。当然可以自定义视图<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21464164/1623150628158-4bb20a70-eb95-486f-ae76-8687bd5c09ef.png#clientId=u6e2ed908-9528-4&from=paste&height=209&id=u926dfc5e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=209&originWidth=307&originalType=binary&ratio=1&size=6858&status=done&style=none&taskId=ucbedbdb5-fd43-42ef-be78-cae5445d7e7&width=307)<br />.cshtml文件就是写前端。不过里面可以穿插一些C#的语言,可以传值等。而且这个是后端渲染出来整个页面,返回给前端的方式,比较消耗服务器资源。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21464164/1623150814046-779f5514-5c8b-4bbb-a566-25662630e113.png#clientId=u6e2ed908-9528-4&from=paste&height=368&id=u0a20ca52&margin=%5Bobject%20Object%5D&name=image.png&originHeight=368&originWidth=554&originalType=binary&ratio=1&size=19718&status=done&style=none&taskId=ub754a66c-8e41-4c97-820b-068a68a5949&width=554)
  5. <a name="u803S"></a>
  6. ### 布局视图
  7. 就像常规视图一样,布局视图也具有扩展名为.cshtml的文件<br />布局视图一般放在Shared文件夹里面,布局视图不特定于控制器,前面一般是以_下划线开头。
  8. 布局视图就相当于一个页面模型框架,把html模板,样式都写好了,一个框框。但是中间空了一块,那一块就是内容。就需要把正常视图的东西补充到模板视图空缺的那块去。这样就合成了一个完整的页面。<br />也就是说,布局视图写页面框架样式,这个可以复用。而正常的控制器视图写要展示在布局视图里面的内容。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21464164/1623672426046-cc48a00f-9b7b-42a9-86b6-38e20aecd5ef.png#clientId=ue68d30fa-f608-4&from=paste&height=371&id=u12f5b5b7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=371&originWidth=360&originalType=binary&ratio=1&size=6289&status=done&style=none&taskId=uec85e17e-ffad-4be3-b291-d08a92a9153&width=360)<br />要使用布局视图(Layout.cshtml)渲染视图,需设置Layout属性。比如,要将布局视图与Index.cshtml一起使用,需要修改Index.cshtml中的代码以包含Layout属性
  9. ```csharp
  10. @{
  11. Layout = "_Layout";
  12. }
  13. @model Person
  14. <div class="text-center">
  15. <h1 class="display-4">Welcome</h1>
  16. <h2>@Model.Name</h2>
  17. </div>

@RenderBody()是注入视图特定内容的位置。比如,如果使用此布局视图呈现Index.chtml视图,则会在我们调用@RenderBody()方法的位置注入Index.cshtml视图内容。
下面是布局视图的代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>@ViewData["Title"] - WebApplication10</title>
  7. <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
  8. <link rel="stylesheet" href="~/css/site.css" />
  9. </head>
  10. <body>
  11. <div class="container">
  12. <main role="main" class="pb-3">
  13. @RenderBody()
  14. </main>
  15. </div>
  16. </body>
  17. </html>

节点

如果控制器视图A,B,C,D都使用了同一个布局视图,而C需要使用一个x.js文件,其他ABD不需要
在布局页面中,在要渲染节点内容的位置调用RenderSection()方法。我们把@RenderSection() 放置在结束元素之前。第一个参数为节点名字,第二个参数ture就是必须的,false可选的。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>@ViewData["Title"] - WebApplication10</title>
  7. <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
  8. <link rel="stylesheet" href="~/css/site.css" />
  9. </head>
  10. <body>
  11. <div class="container">
  12. <main role="main" class="pb-3">
  13. @RenderBody()
  14. </main>
  15. </div>
  16. @RenderSection("Name1", false)
  17. </body>
  18. </html>

内容视图使用节点

  1. @{
  2. Layout = "_Layout";
  3. }
  4. @model Person
  5. <div class="text-center">
  6. <h1 class="display-4">Welcome</h1>
  7. <h2>@Model.Name</h2>
  8. </div>
  9. @section Name1{
  10. <script type="text/javascript" src="xxxjs"></script>
  11. }

_ViewStart.cshtml

就是布局视图的总页面,如果有很多页面,都要布局视图,可以把布局写在_ViewStart.cshtml文件里面,所有的视图都能用到这个里面布局
image.png
这个总布局视图,支持分层,每层可以定义不同的布局
image.png

_ViewImports.cshtml

这个一般是放在Views文件夹下面,里面放了所有视图的公共命名空间,在视图中使用什么类的时候就不用写公共的命名空间了
image.png

Models

一般是空的文件夹。自己用在这里面用EF框架,连接数据库,创建数据模型类

传值

控制器传给视图

控制器传值

  1. public ActionResult Index()
  2. {
  3. //1 ViewData传值
  4. ViewData["name1"] = "张三";
  5. //2 ViewBag.name
  6. ViewBag.name = "李四";
  7. //3.TempData["name"]
  8. TempData["name"] = "王五";
  9. //4.上面的都是弱类型,下面的是强类型
  10. Person obj = new Person();
  11. return View(obj);
  12. }

视图接收值

  1. @model Person //指定model是Person类型的值,这里的model要小写
  2. <div class="row">
  3. <h1>@ViewData["name1"]</h1>
  4. <h2>@ViewBag.name</h2>
  5. <h3>@TempData["name"]</h3>
  6. <h4>@Model.Name</h4> //使用时Model大写
  7. </div>

推荐用强类型传值,如果有多个值要传给视图,再写一个ViewModel类,这个类里面包含要传的说有参数类型,把这个类的实例传过去

视图向控制器传值

  1. 通过路由传值
  2. 通过ajax。视图前端用js获取到值,ajax传值到控制器,控制器要有相应的参数接收值

可能需要在控制器上加上特性指定是什么方式传过来的,见ActionMethodSelectorAttribute派生类

  1. 直接在.cshtml页面写@控制器方法名(参数)

    Session

    在ASP.NET Core MVC中,Session是没有配置的,.net core里面要使用什么服务,要自己在startup类中注册服务,使用中间件 ```csharp public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddSession();//添加Session服务 }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(“/Home/Error”); } app.UseSession();//使用Session中间件 app.UseStaticFiles();

  1. app.UseRouting();
  2. app.UseAuthorization();
  3. app.UseEndpoints(endpoints =>
  4. {
  5. endpoints.MapControllerRoute(
  6. name: "default",
  7. pattern: "{controller=Home}/{action=Index}/{id?}");
  8. });

}

  1. 然后就可以使用Session服务了
  2. ```csharp
  3. using Microsoft.AspNetCore.Http;
  4. public IActionResult Index()
  5. {
  6. HttpContext.Session.SetString("uid", "123456");
  7. string uid = HttpContext.Session.GetString("uid");
  8. return View();
  9. }

路由

我们先了解什么是路由,当来自浏览器的请求到达应用程序时,MVC中的控制器会处理传入的HTTP请求并响应用户操作,请求URL会被映射到控制器的操作方法上。此映射过程由应用程序中定义的路由规则完成。
比如,当向/Home/Index发出请求时,此URL将映射到HomeController类中的Index()操作方法
类似地,当向/Home/Details/1发出请求时,此URL将映射到HomeController类中的Details() 操作方法。URL中的值1自动映射到id,即Details(int id)的操作方法
image.png

默认路由

在Startup类中Configure方法中

  1. app.UseEndpoints(endpoints =>
  2. {
  3. endpoints.MapControllerRoute(
  4. name: "default",
  5. pattern: "{controller=Home}/{action=Index}/{id?}");
  6. });

pattern就是默认的路由规则

控制器名/方法名/id 没有控制器名就默认Home,没有方法名就默认Index,参数id加了?就是可选

特性路由

使用Route()特性来定义路由,我们可以在Controller类或Controller()操作方法上应用Route()特性

  1. [Route("")]
  2. [Route("/Home")]
  3. [Route("/Home/Index")]
  4. public IActionResult Index()
  5. {
  6. Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
  7. return View(person);
  8. }

使用这3个路由规则,3个URL中都会访问HomeController的Index()操作方法

同样特性路由还可以设置参数,加问号就是可选

  1. [Route("/Home/Details/{id?}")]
  2. public IActionResult Details(int id)
  3. {
  4. return View();
  5. }

控制器和操作名称不会影响路由,特性路由可以自定义名字,下面例子,url通过 /H/I就能访问到HomeController控制器下的Index方法

  1. [Route("")]
  2. [Route("/H")]
  3. [Route("/H/I")]
  4. public IActionResult Index()
  5. {
  6. Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
  7. return View(person);
  8. }

特性路由可以多层级,减少代码量

  1. [Route("")]
  2. [Route("/Home")]
  3. public class HomeController : Controller
  4. {
  5. private readonly ILogger<HomeController> _logger;
  6. public HomeController(ILogger<HomeController> logger)
  7. {
  8. _logger = logger;
  9. }
  10. [Route("")]
  11. [Route("/Index")]
  12. public IActionResult Index()
  13. {
  14. Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
  15. return View(person);
  16. }
  17. [Route("/Details/{id?}")]
  18. public IActionResult Details(int id)
  19. {
  20. return View();
  21. }
  22. public IActionResult Privacy()
  23. {
  24. return View();
  25. }
  26. [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
  27. public IActionResult Error()
  28. {
  29. return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
  30. }
  31. }

可以直接定义一个模板,路由就用控制器加方法名路由,如控制器名,或者方法名改变,也不用修改路由

  1. [Route("[controller]/[action]")]
  2. public class HomeController : Controller
  3. {
  4. private readonly ILogger<HomeController> _logger;
  5. public HomeController(ILogger<HomeController> logger)
  6. {
  7. _logger = logger;
  8. }
  9. public IActionResult Index()
  10. {
  11. Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
  12. return View(person);
  13. }
  14. public IActionResult Details(int id)
  15. {
  16. return View();
  17. }
  18. }

路由中间件

  1. app.UseRouting();//启动路由
  2. app.UseEndpoints(endpoints =>
  3. {
  4. endpoints.MapControllerRoute(
  5. name: "default",
  6. pattern: "{controller=Home}/{action=Index}/{id?}");
  7. });//终结点路由
  8. //我们只需要把它放置在UseRouting()中间件之后,如果忘记启用UseRouting()的话会引发异常

TagHelper

TagHelper是服务器端组件。它们在服务器上运行,并在Razor文件中创建和渲染HTML元素。ASP.NET Core有许多内置的Tag Helper用于常见任务,比如生成链接、创建表单和加载数据等。TagHelper的出现可帮助提高生产效率,并生成更稳定、可靠和可维护的代码。

导入内置TagHelper

要在整个应用程序中的所有视图使用内置TagHelper,需要在_ViewImports.cshtml文件导入TagHelper。要导入TagHelper,我们使用@addTagHelper指令。

  1. @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

使用TagHelper生成连接

这是视图上的链接

  1. <a asp-controller="Home" asp-action="Details" asp-route-id="1" asp-route-name="张三">点击</a>

这是目标控制器方法的路由和参数

  1. [Route("Details")]
  2. public IActionResult Details(int id,string name)
  3. {
  4. ViewBag.ID = id;
  5. ViewBag.Name = name;
  6. return View();
  7. }

最后生成的链接:http://localhost:20513/Home/Details?id=1&name=张三

  • asp-controller指定控制器名称
  • asp-action指定操作方法的名称
  • asp-route-{value}属性用于在生成的href中包含路由数据属性值。{value}可以替换为路由参数。不写路由参数,只写方法参数一样的,多个参数就写多个asp-route-{value}

TagHelper还有更多的用法,比如表单啊form之类的。个人感觉还是vue方便。用过mvc和vue结合开发

异常情况处理中间件

UseStatusCodePages

一般的url不对,找不到资源,就会报404错误。拦截访问失败的http请求,400-599这个范围。

  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. else
  8. {
  9. //1.如果不是开发者模式,404直接就显示下面的文字,这个不常用
  10. app.UseStatusCodePages();
  11. }
  12. app.UseStaticFiles();
  13. app.UseRouting();
  14. app.UseAuthorization();
  15. app.UseEndpoints(endpoints =>
  16. {
  17. endpoints.MapControllerRoute(
  18. name: "default",
  19. pattern: "{controller=Home}/{action=Index}/{id?}");
  20. });
  21. }

image.png

UseStatusCodePagesWithRedirects

把上面的代码改成这个,出现访问异常的页面就重定向到一个控制器方法,这个方法可以指定返回一个新的关于这个错误的视图

  1. //2.如果出现404错误,则会将用户重定向到Home/MyError/404。这里采用了占位符 {0} ,它会自动接收HTTP中的状态码
  2. app.UseStatusCodePagesWithRedirects("/Home/MyError/{0}");
  1. [Route("/Home/MyError/{code}")]
  2. public IActionResult MyError(int code)
  3. {
  4. ViewBag.Code = code;
  5. return View();
  6. }

image.png
image.png

UseStatusCodePagesWithReExecute

  1. //3.
  2. app.UseStatusCodePagesWithReExecute("/Home/MyError/{0}");

image.png
image.png
用UseStatusCodePagesWithReExecute,在错误控制器方法里面,可以获取路径等信息

  1. public IActionResult MyError(int code)
  2. {
  3. var codeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
  4. ViewBag.Code = code;
  5. ViewBag.Path = codeResult.OriginalPath;//获取输入的错误路径
  6. ViewBag.QS = codeResult.OriginalQueryString;//获取输入的错误路径的查询字符串
  7. return View();
  8. }

UseDeveloperExceptionPage

发生异常就会进入开发者异常页面

  1. if (env.IsDevelopment())
  2. {
  3. app.UseDeveloperExceptionPage();
  4. }

image.png

UseExceptionHandler

对于非开发环境,使用UseExceptionHandler()方法将异常处理中间件添加到请求处理管道。遇到异常的时候,异常处理中间件会跳转到路径中的方法中

  1. app.UseExceptionHandler("/Home/Error");
  1. public IActionResult Error()
  2. {
  3. //自定义的获取异常信息
  4. var exceptHandle = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
  5. ViewBag.Path = exceptHandle.Path;
  6. ViewBag.Message = exceptHandle.Error.Message;
  7. ViewBag.Stack = exceptHandle.Error.StackTrace;
  8. return View();
  9. }

image.png