MVC介绍
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件的设计模式。
- Model(模型):就是数据模型类
- View(视图):前端页面,但是是后端渲染的。ASP.NET MVC里面的视图一般是Razor模板,也就是
.cshtml
文件 - Controller(控制器):处理逻辑,和视图模型交互。
创建MVC项目
在VS里面创建项目,无论是.NET Framework还是.NET Core,创建web项目,都可以选择MVC模板创建。
里面这三个核心文件夹,把控制器,模型,视图分别放在对应的文件夹里面
Controllers
里面的类一般是Controller结尾的,路由这个控制器的时候一般不写Controller,只写前面半截名
.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这个名字的视图 }
写好了一个新的控制器方法,光标定位到该方法上,右键,添加视图。就能在Views文件夹里面看到了
<a name="ALoWc"></a>
## Views
这个文件夹里面包含了控制器方法的视图页面。一个控制器一个文件夹.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)
<a name="u803S"></a>
### 布局视图
就像常规视图一样,布局视图也具有扩展名为.cshtml的文件<br />布局视图一般放在Shared文件夹里面,布局视图不特定于控制器,前面一般是以_下划线开头。
布局视图就相当于一个页面模型框架,把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属性
```csharp
@{
Layout = "_Layout";
}
@model Person
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<h2>@Model.Name</h2>
</div>
@RenderBody()是注入视图特定内容的位置。比如,如果使用此布局视图呈现Index.chtml视图,则会在我们调用@RenderBody()方法的位置注入Index.cshtml视图内容。
下面是布局视图的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication10</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
</body>
</html>
节点
如果控制器视图A,B,C,D都使用了同一个布局视图,而C需要使用一个x.js文件,其他ABD不需要
在布局页面中,在要渲染节点内容的位置调用RenderSection()方法。我们把@RenderSection() 放置在结束元素之前。第一个参数为节点名字,第二个参数ture就是必须的,false可选的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication10</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
@RenderSection("Name1", false)
</body>
</html>
内容视图使用节点
@{
Layout = "_Layout";
}
@model Person
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<h2>@Model.Name</h2>
</div>
@section Name1{
<script type="text/javascript" src="xxxjs"></script>
}
_ViewStart.cshtml
就是布局视图的总页面,如果有很多页面,都要布局视图,可以把布局写在_ViewStart.cshtml文件里面,所有的视图都能用到这个里面布局
这个总布局视图,支持分层,每层可以定义不同的布局
_ViewImports.cshtml
这个一般是放在Views文件夹下面,里面放了所有视图的公共命名空间,在视图中使用什么类的时候就不用写公共的命名空间了
Models
一般是空的文件夹。自己用在这里面用EF框架,连接数据库,创建数据模型类
传值
控制器传给视图
控制器传值
public ActionResult Index()
{
//1 ViewData传值
ViewData["name1"] = "张三";
//2 ViewBag.name
ViewBag.name = "李四";
//3.TempData["name"]
TempData["name"] = "王五";
//4.上面的都是弱类型,下面的是强类型
Person obj = new Person();
return View(obj);
}
视图接收值
@model Person //指定model是Person类型的值,这里的model要小写
<div class="row">
<h1>@ViewData["name1"]</h1>
<h2>@ViewBag.name</h2>
<h3>@TempData["name"]</h3>
<h4>@Model.Name</h4> //使用时Model大写
</div>
推荐用强类型传值,如果有多个值要传给视图,再写一个ViewModel类,这个类里面包含要传的说有参数类型,把这个类的实例传过去
视图向控制器传值
- 通过路由传值
- 通过ajax。视图前端用js获取到值,ajax传值到控制器,控制器要有相应的参数接收值
可能需要在控制器上加上特性指定是什么方式传过来的,见ActionMethodSelectorAttribute派生类
- 直接在.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();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
然后就可以使用Session服务了
```csharp
using Microsoft.AspNetCore.Http;
public IActionResult Index()
{
HttpContext.Session.SetString("uid", "123456");
string uid = HttpContext.Session.GetString("uid");
return View();
}
路由
我们先了解什么是路由,当来自浏览器的请求到达应用程序时,MVC中的控制器会处理传入的HTTP请求并响应用户操作,请求URL会被映射到控制器的操作方法上。此映射过程由应用程序中定义的路由规则完成。
比如,当向/Home/Index发出请求时,此URL将映射到HomeController类中的Index()操作方法
类似地,当向/Home/Details/1发出请求时,此URL将映射到HomeController类中的Details() 操作方法。URL中的值1自动映射到id,即Details(int id)的操作方法
默认路由
在Startup类中Configure方法中
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
pattern就是默认的路由规则
控制器名/方法名/id 没有控制器名就默认Home,没有方法名就默认Index,参数id加了?就是可选
特性路由
使用Route()特性来定义路由,我们可以在Controller类或Controller()操作方法上应用Route()特性
[Route("")]
[Route("/Home")]
[Route("/Home/Index")]
public IActionResult Index()
{
Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
return View(person);
}
使用这3个路由规则,3个URL中都会访问HomeController的Index()操作方法
同样特性路由还可以设置参数,加问号就是可选
[Route("/Home/Details/{id?}")]
public IActionResult Details(int id)
{
return View();
}
控制器和操作名称不会影响路由,特性路由可以自定义名字,下面例子,url通过 /H/I就能访问到HomeController控制器下的Index方法
[Route("")]
[Route("/H")]
[Route("/H/I")]
public IActionResult Index()
{
Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
return View(person);
}
特性路由可以多层级,减少代码量
[Route("")]
[Route("/Home")]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[Route("")]
[Route("/Index")]
public IActionResult Index()
{
Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
return View(person);
}
[Route("/Details/{id?}")]
public IActionResult Details(int id)
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
可以直接定义一个模板,路由就用控制器加方法名路由,如控制器名,或者方法名改变,也不用修改路由
[Route("[controller]/[action]")]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
Person person = new Person() { Id = 1, Name = "张三", Sex = '男' };
return View(person);
}
public IActionResult Details(int id)
{
return View();
}
}
路由中间件
app.UseRouting();//启动路由
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});//终结点路由
//我们只需要把它放置在UseRouting()中间件之后,如果忘记启用UseRouting()的话会引发异常
TagHelper
TagHelper是服务器端组件。它们在服务器上运行,并在Razor文件中创建和渲染HTML元素。ASP.NET Core有许多内置的Tag Helper用于常见任务,比如生成链接、创建表单和加载数据等。TagHelper的出现可帮助提高生产效率,并生成更稳定、可靠和可维护的代码。
导入内置TagHelper
要在整个应用程序中的所有视图使用内置TagHelper,需要在_ViewImports.cshtml文件导入TagHelper。要导入TagHelper,我们使用@addTagHelper指令。
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
使用TagHelper生成连接
这是视图上的链接
<a asp-controller="Home" asp-action="Details" asp-route-id="1" asp-route-name="张三">点击</a>
这是目标控制器方法的路由和参数
[Route("Details")]
public IActionResult Details(int id,string name)
{
ViewBag.ID = id;
ViewBag.Name = name;
return View();
}
最后生成的链接: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这个范围。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//1.如果不是开发者模式,404直接就显示下面的文字,这个不常用
app.UseStatusCodePages();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
UseStatusCodePagesWithRedirects
把上面的代码改成这个,出现访问异常的页面就重定向到一个控制器方法,这个方法可以指定返回一个新的关于这个错误的视图
//2.如果出现404错误,则会将用户重定向到Home/MyError/404。这里采用了占位符 {0} ,它会自动接收HTTP中的状态码
app.UseStatusCodePagesWithRedirects("/Home/MyError/{0}");
[Route("/Home/MyError/{code}")]
public IActionResult MyError(int code)
{
ViewBag.Code = code;
return View();
}
UseStatusCodePagesWithReExecute
//3.
app.UseStatusCodePagesWithReExecute("/Home/MyError/{0}");
用UseStatusCodePagesWithReExecute,在错误控制器方法里面,可以获取路径等信息
public IActionResult MyError(int code)
{
var codeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
ViewBag.Code = code;
ViewBag.Path = codeResult.OriginalPath;//获取输入的错误路径
ViewBag.QS = codeResult.OriginalQueryString;//获取输入的错误路径的查询字符串
return View();
}
UseDeveloperExceptionPage
发生异常就会进入开发者异常页面
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
UseExceptionHandler
对于非开发环境,使用UseExceptionHandler()方法将异常处理中间件添加到请求处理管道。遇到异常的时候,异常处理中间件会跳转到路径中的方法中
app.UseExceptionHandler("/Home/Error");
public IActionResult Error()
{
//自定义的获取异常信息
var exceptHandle = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
ViewBag.Path = exceptHandle.Path;
ViewBag.Message = exceptHandle.Error.Message;
ViewBag.Stack = exceptHandle.Error.StackTrace;
return View();
}