Asp.Net Core 學習
基於.Net Core 2.2版本的學習筆記。
常識
像Django那樣自動檢查代碼更新,自動重載服務器(太方便了)
dotnet watch run
托管設置
設置項目文件的AspNetCoreHostingModel
屬性。
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
- InProcess:使用IIS服務器托管
- OutOfProcess:使用自帶Kestrel服務器托管
中間件入門
- 可同時被訪問和請求
- 可以處理請求后,將請求傳遞給下一個中間件
- 可以處理請求后,使管道短路
- 可以傳出響應
- 中間件是按照添加順序執行的
通過在Configure
中添加參數ILogger<Startup> logger
引入Asp.Net Core自帶的日志組件。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain;charset=utf-8";
//await context.Response.WriteAsync("Hello!");
logger.LogDebug("M1: 傳入請求");
await next();
logger.LogDebug("M1: 傳出響應");
});
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain;charset=utf-8";
logger.LogDebug("M2: 傳入請求");
await next();
logger.LogDebug("M2: 傳出響應");
});
app.Run(async (context) =>
{
//await context.Response.WriteAsync("你好!");
await context.Response.WriteAsync("M3: 處理請求,生成響應");
logger.LogDebug("M3: 處理請求,生成響應");
});
}
輸出日志:(可以看到三個中間件的執行過程)
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 GET https://localhost:44383/
StudyManagement.Startup:Debug: M1: 傳入請求
StudyManagement.Startup:Debug: M2: 傳入請求
StudyManagement.Startup:Debug: M3: 處理請求,生成響應
StudyManagement.Startup:Debug: M2: 傳出響應
StudyManagement.Startup:Debug: M1: 傳出響應
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 52.8954ms 200 text/plain;charset=utf-8
StudyManagement.Startup:Debug: M1: 傳入請求
StudyManagement.Startup:Debug: M2: 傳入請求
StudyManagement.Startup:Debug: M3: 處理請求,生成響應
StudyManagement.Startup:Debug: M2: 傳出響應
StudyManagement.Startup:Debug: M1: 傳出響應
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 34.3387ms 200 text/plain;charset=utf-8
靜態文件支持
所有靜態文件都在目錄wwwroot
下
首先
// 設置默認文件
// 不設置的話,默認就是index.html/default.html這幾個
var defaultFileOpinions = new DefaultFilesOptions();
defaultFileOpinions.DefaultFileNames.Clear();
defaultFileOpinions.DefaultFileNames.Add("test.html");
// 添加默認文件中間件,必須在UseStaticFiles之前注冊
app.UseDefaultFiles(defaultFileOpinions);
// 添加靜態文件中間件
app.UseStaticFiles();
DirectoryBrowser 中間件
可以在瀏覽器瀏覽 wwwroot
下的內容。不推薦在生產環境中使用。
app.UseDirectoryBrowser();
FileServer 中間件
集成了UseDefaultFiles
, UseStaticFiles
, UseDirectoryBrowser
三個中間件的功能。同樣不推薦在生產環境中使用。
var fileServerOpinions = new FileServerOptions();
fileServerOpinions.DefaultFilesOptions.DefaultFileNames.Clear();
fileServerOpinions.DefaultFilesOptions.DefaultFileNames.Add("test.html");
app.UseFileServer(fileServerOpinions);
開發者異常頁面
if (env.IsDevelopment())
{
var developerExceptionPageOptions = new DeveloperExceptionPageOptions();
// 顯示代碼行數
developerExceptionPageOptions.SourceCodeLineCount = 10;
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
throw new Exception("自己拋出的異常");
});
開發環境變量
- Development:開發環境
- Staging:演示(模擬、臨時)環境
- Production:正式(生產)環境
Ops:
- 使用
ASPNETCORE_ENVIRONMENT
環境變量設置開發環境。 - 在開發機上,在
launchSettings.json
文件中設置環境變量。 - 在Staging和Production環境時,盡量在操作系統設置環境變量。
- 使用
IHostEnvironment
服務訪問運行時環境 - 除了標准環境之外還支持自定義環境(UAT、QA等)
引入MVC框架
首先添加MVC服務。
public void ConfigureServices(IServiceCollection services)
{
// 單純引入核心MVC服務,只有核心功能
services.AddMvcCore();
// 一般用這個,功能多
services.AddMvc();
}
添加中間件
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
if (env.IsDevelopment())
{
var developerExceptionPageOptions = new DeveloperExceptionPageOptions();
// 顯示代碼行數
developerExceptionPageOptions.SourceCodeLineCount = 10;
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
MVC路由規則:/控制器名稱/方法名稱
,(不區分大小寫)
例如下面例子的路由是:/home/index
HomeController代碼:
public class HomeController : Controller
{
public string Index()
{
return "home controller";
}
}
初步了解模型和依賴注入
定義模型
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string ClassName { get; set; }
public string Email { get; set; }
}
定義接口
public interface IStudentRepository
{
Student GetById(int id);
void Save(Student student);
}
實現接口
目前還沒接入數據庫,定義一個假數據的類
public class MockStudentRepository : IStudentRepository
{
private List<Student> _students;
public MockStudentRepository()
{
_students = new List<Student>
{
new Student { Id=1, Name="小米", ClassName="紅米", Email="hello1@deali.cn" },
new Student { Id=2, Name="華為", ClassName="榮耀", Email="hello2@deali.cn" },
new Student { Id=3, Name="oppo", ClassName="vivo", Email="hello3@deali.cn" },
};
}
public Student GetById(int id)
{
return _students.FirstOrDefault(a => a.Id == id);
}
public void Save(Student student) => throw new NotImplementedException();
}
注冊依賴注入
Asp.Net Core依賴注入容器注冊服務有三種
- AddSingleton
- AddTransient
- AddScoped
依賴注入的優點
- 低耦合
- 高測試性,更加方便進行單元測試
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// 注冊依賴注入,將實現類與接口綁定
services.AddSingleton<IStudentRepository, MockStudentRepository>();
}
在模型中使用依賴注入
public class StudentController : Controller
{
private readonly IStudentRepository _studentRepository;
// 通過構造函數注入的方式注入 IStudentRepository
public StudentController(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public JsonResult Index(int id)
{
return Json(_studentRepository.GetById(id));
}
}
控制器入門
內容格式協商
在控制器方法中使用 ObjectResult
返回類型,支持內容協商,根據請求頭參數返回數據,
// 支持內容格式協商
public ObjectResult Details(int id)
{
return new ObjectResult(_studentRepository.GetById(id));
}
如:
Accept: application/xml
將返回xml格式。注:還要添加xml序列化器。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
// 注冊XML序列化器
.AddXmlSerializerFormatters();
}
視圖入門
將數據從控制器傳遞到視圖的方法
前兩種都是弱類型的
- ViewData
- ViewBag
- 強類型視圖
ViewData
- 弱類型字典對象
- 使用string類型的鍵值,存儲和chaxun
- 運行時動態解析
- 沒有智能感知,編譯時也沒有類型檢查
使用方法:
ViewData["Title"] = "學生視圖";
ViewData["Model"] = model;
cshtml代碼:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<h1>@ViewData["Title"]</h1>
@{
var student = ViewData["model"] as StudyManagement.Models.Student;
}
<div>姓名:@student.Name</div>
<div>班級:@student.ClassName</div>
</body>
</html>
ViewBag
// 直接給動態屬性賦值
ViewBag.PageTitle = "ViewBag標題";
ViewBag.Student = model;
cshtml使用:
<h1>@ViewBag.PageTitle</h1>
<div>姓名:@ViewBag.Student.Name</div>
<div>班級:@ViewBag.Student.ClassName</div>
強類型視圖
在控制器中傳給View()模型
public IActionResult GetView()
{
var model = _studentRepository.GetById(1);
return View(model);
}
在cshtml中指定模型類型
@model StudyManagement.Models.Student
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<h1>強類型模型</h1>
<ul>
<li>@Model.Id</li>
<li>@Model.Name</li>
<li>@Model.ClassName</li>
<li>@Model.Email</li>
</ul>
</body>
</html>
ViewModel 視圖模型
類似於DTO(數據傳輸對象)
定義ViewModel
public class StudentDetailsViewModel
{
public Student Student { get; set; }
public string PageTitle { get; set; }
}
修改控制器
public IActionResult Details()
{
var model = _studentRepository.GetById(1);
var viewModel = new StudentDetailsViewModel
{
Student = model,
PageTitle = "viewmodel里的頁面標題"
};
return View(viewModel);
}
在View中使用
<!-- 這里注冊的模型改成了ViewModel了 -->
@model StudyManagement.ViewModels.StudentDetailsViewModel
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<h1>強類型模型</h1>
<h2>@Model.PageTitle</h2>
<ul>
<li>@Model.Student.Id</li>
<li>@Model.Student.Name</li>
<li>@Model.Student.ClassName</li>
<li>@Model.Student.Email</li>
</ul>
</body>
</html>
View中使用循環
@model IEnumerable<StudyManagement.Models.Student>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<table border="1">
<tr>
<td>Id</td>
<td>姓名</td>
<td>班級</td>
<td>郵箱</td>
</tr>
@foreach (var student in Model)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
<td>@student.ClassName</td>
<td>@student.Email</td>
</tr>
}
</table>
</body>
</html>
布局視圖 LayoutView
創建布局視圖
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
@RenderSection("Scripts", required: false)
</body>
</html>
渲染視圖
@model IEnumerable<StudyManagement.Models.Student>
@{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewBag.Title = "首頁 學生列表";
}
<div></div>
視圖節點 Section
在布局視圖里渲染節點
@RenderSection("Scripts", required: false)
在普通視圖里定義節點
@section Scripts{
<script>
document.write("hello");
</script>
}
視圖開始 ViewStart
我的理解就是_ViewStart.cshtml
文件所在目錄下的每個視圖文件開始渲染先執行這個文件的內容。一般直接放在Views
目錄下,全局生效,可以放在各個子文件夾下,這樣可以覆蓋全局的_ViewStart.cshtml
。
@{
Layout = "_Layout";
}
視圖導入 ViewImports
用來導入命名空間、注冊模型等等n多種操作。
生效機制和ViewStart差不多。
路由
- 常規路由(傳統路由)
- 屬性路由
常規路由
在MapRoute
方法中傳入就好了。
// 自定義路由
app.UseMvc(route =>route.MapRoute("default",
"{controller=Home}/{action=Index}/{id?}"));
屬性路由
比傳統路由更加靈活,可以搭配傳統路由使用。
即在控制器方法上添加路由注解,一個方法可以同時映射多個路由。
[Route("Home/Index")]
public IActionResult Index()
{
return View(_studentRepository.GetAll());
}
路由中也可以指定參數
[Route("test/{id?}")]
public IActionResult Details(int id = 1)
{
var model = _studentRepository.GetById(id);
var viewModel = new StudentDetailsViewModel
{
Student = model,
PageTitle = "viewmodel里的頁面標題"
};
return View(viewModel);
}
可以直接在控制器類上加注解,[controller]/[action]
。
歡迎交流
交流問題請在微信公眾號后台留言,每一條信息我都會回復哈~
- 微信公眾號:畫星星高手
- 打代碼直播間:https://live.bilibili.com/11883038
- 知乎:https://www.zhihu.com/people/dealiaxy
- 專欄:https://zhuanlan.zhihu.com/deali