ASP.NET CORE MVC
1. 默認配置
-
使用Kestrel Web Server
- ASP.NET Core內置,跨平臺
-
IIS集成
- UseIIS(),UseIISIntegration()
-
Log
-
IConfiguration接口
2. 路由
-
Convertional Routing
使用默認的方式
builder.MapRoute("Default", "{Controller}/{Action}/{Id?}");
尋找路由 -
Attribute Routing
在Controller類或其中的方法上,添加路由信息
namespace Tutorial.Web.Controllers { [Route("[controller]")] public class AboutController { public string Me() { return "Dave"; } public string Company() { return "No Company"; } } }
[Route("[controller]")]
或[Route("stringname/[controller]/[action]")]
根據Controller的名字查找路由,也可以在方法名上定義[Route("routeName")]
根據自己設定的routeName查找路由
3. 視圖
在controller中傳入st信息
public IActionResult Index()
{
var st = new Student
{
Id = 1,
FirstName = "Qilong",
LastName = "Wu"
};
return View(st);
}
在index頁面,可以使用@Model
獲取這個信息的值,注意@Model
首字母大寫
cshtml中的@model
,首字母爲小寫,其代表一個指令,能讓Razor視圖正確構建代碼
如,在Index.cshtml中首行寫入
@model Tutorial.Web.Model.Student
則書寫<h2>@Model</h2>
就會有提示信息,提示從controller傳過來的數據中都有哪些屬性。
<h2>@Model.FirstName @Model.LastName @Model.Id</h2>
4. Model
輸出的Model:ViewModel
輸入的Model
用於輸入的Model通常用來創建數據和修改數據,輸入Model的方式有以下兩種:
- Form
- 導航到含有Form的View
4.1 Form
使用Form提交Model,提交方式爲post,如下例
<form method="post">
<input type="text" name="FirstName" value="" />
<input type="date" name="BirthDate" value="" />
</form>
其中input標籤中的name屬性值FirstName
要和Model中的屬性對應
更高級的寫法是使用TagHelper的形式,可以自動匹配類型,如date類型。如果不放心可以再聲明一遍
<input asp-for="FirstName" />
<input asp-for="BirthDate" type="date"/>
在Student類中添加枚舉類型Gender,枚舉類型使用TagHelper的方式如下:
<select asp-for="Gender" asp-items="Html.GetEnumSelectList<Tutorial.Web.Model.Gender>()">
</select>
在以post方式提交的時候,傳入的參數爲Student類型,asp.net mvc會想方設法把傳入參數的屬性補全,一般來說,id值自增就不傳入id值,自動獲取id值就會導致錯誤,於是使用post方式提交的時候,傳入的參數使用重新定義的StudentCreateViewModel類。
//StudentCreateViewModel類
namespace Tutorial.Web.Model
{
public class StudentCreateViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public Gender Gender { get; set; }
}
}
//使用post方式提交,參數使用StudentCreateViewModel,
//然後使用repository服務將創建的學生信息添加到學生列表裏。
[HttpPost]
public IActionResult Create(StudentCreateViewModel student)
{
var newStudent = new Student
{
FirstName = student.FirstName,
LastName = student.LastName,
BirthDate = student.BirthDate,
Gender = student.Gender
};
var newModel = repository.Add(newStudent);
return View("Detail", newModel);
}
還需要注意一點的是,在StartUp中,要使用AddSingleton<>()的方式,在項目運行期間都是這一個實例,
不要使用AddScoped,AddScoped每次發出請求都是生成一個新的實例,會導致添加數據添加不上。
services.AddSingleton<IRepository<Student>, InMemoryRepository>();
還有一個問題:當添加成功之後,返回到detial頁面,此時刷新網頁,會發現id中自增1,這是因爲刷新一次頁面導致又發出了一個post請求,又產生了新的數據信息。可以使用重定向解決此問題。
//跳轉到Detail頁面需要傳入id信息。
return RedirectToAction(nameof(Detail), new { id = newModel.Id });
4.2 Model驗證
關於模型驗證的詳細內容,參見微軟官方文檔
使用post方法處理表單
第一步:首先要防止跨站請求僞造CSRF(Cross-site request forgery),需要在方法上方添加
[HttpPost]
[ValidateAntiForgeryToken]
第二步:將驗證規則添加到數據模型
Required
:必需屬性Display(Name = "xxx")
:提示信息。如果不寫則按照label標籤中asp-for="xxx"
的值StringLength(60, MinimumLength = 3)
:規定長度RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")
:驗證屬性是否滿足自定義的正則表達式[Range(0,100)]
:範圍限制[DataType(DataType.Date)]
指定日期類型,但是日期的格式不做規定。還可以對Password
、Currency
進行指定[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
:指定日期格式CreditCard
、EmailAddress
、Phone
、url
等:驗證屬性是否具有這種格式
如下例
public class StudentCreateViewModel
{
[Required]
[Display(Name = "名")]
public string FirstName { get; set; }
[Display(Name = "姓")]
public string LastName { get; set; }
[Display(Name = "出生日期")]
public DateTime BirthDate { get; set; }
[Display(Name = "性別")]
public Gender Gender { get; set; }
}
第三步:驗證錯誤UI
在使用到Model的view視圖中進行錯誤消息顯示
可以在每個input框旁邊添加一個span標籤以顯示錯誤信息
<span asp-validation-for="FirstName"></span>
還可以使用如下代碼,對所有的錯誤驗證信息進行輸出
<div asp-validation-summary="All"></div>
或 <div asp-validation-summary="ModelOnly"></div>
其中ModelOnly
進隊模型中出現的錯誤驗證信息進行顯示
第四步:在controller處理
使用if(ModelState.isValid)
,如果驗證通過,執行後續的步驟
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(StudentCreateViewModel student)
{
if (ModelState.IsValid)
{
var newStudent = new Student
{
FirstName = student.FirstName,
LastName = student.LastName,
BirthDate = student.BirthDate,
Gender = student.Gender
};
var newModel = repository.Add(newStudent);
//return View("Detail", newModel);
return RedirectToAction(nameof(Detail), new { id = newModel.Id });
}
else
{
ModelState.AddModelError(string.Empty, "Model level Error!");
return View();
}
}
其中視圖頁面的例子如下
@using Tutorial.Web.Model
@model StudentCreateViewModel
<h1>創建學生</h1>
<form method="post">
<div>
<div>
<label asp-for="FirstName"></label>
<input asp-for="FirstName" />
<span asp-validation-for="FirstName"></span>
</div>
<div>
<label asp-for="LastName"></label>
<input asp-for="LastName" />
<span asp-validation-for="LastName"></span>
</div>
<div>
<label asp-for="BirthDate"></label>
<input asp-for="BirthDate" type="date" />
<span asp-validation-for="BirthDate"></span>
</div>
<div>
<label asp-for="Gender"></label>
<select asp-for="Gender" asp-items="Html.GetEnumSelectList<Tutorial.Web.Model.Gender>()">
</select>
</div>
</div>
<div asp-validation-summary="ModelOnly"></div>
<button type="submit" name="save">保存</button>
</form>
5. 集成EntityFramework Core
微軟官方文檔可以查看更詳細的信息。
用於對象關係映射ORM
支持的數據庫有:
- MSSQL localDB(在windows下開發,如使用visual studio時)
- PostgreSQL(在Linux下開發)
- MySQL/MariaDB
- Oracle
- DB2
- SQLite
- In Memory
- …
第一步:在appsettings.json文件中添加數據庫鏈接字符串
"ConnectionStrings": {
"DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Tutorial;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"CustomConnectionXXX": "Data Source=..."
}
可以添加多個鏈接,需要用的時候指定名稱就行了。
如果使用Visual studio開發,使用的數據庫是MSSQLLocalDB,我們只需要指定Initial Catalog
的值就行了,生成的數據表就會存到這個文件夾裏。
第二步:創建數據庫上下文
需要一個數據庫上下文類來協調 Student
模型的 EF Core 功能(創建、讀取、更新和刪除)。 數據庫上下文派生自 Microsoft.EntityFrameworkCore.DbContext 並指定要包含在數據模型中的實體。
使用以下代碼添加Data/DataContext.cs文件
namespace Tutorial.Web.Data
{
public class DataContext: DbContext
{
public DataContext (DbContextOptions<DataContext> options): base(options)
{
}
public DbSet<Student> Students { get; set; }
}
}
前面的代碼爲實體集創建DbSet<Student>
屬性。 在實體框架術語中,實體集通常與數據表相對應。 實體對應表中的行。
DbSet Class用於TEntity實例的查詢和保存,對應於DbSet的LINQ查詢會被轉換成數據庫查詢
第三步:註冊數據庫上下文
使用依賴注入的方式,需要在Startup.cs文件中添加服務。
要從appsettings.json文件中獲取字符串,可以使用Startup()
構造函數中使用IConfiguration
服務,然後在ConfigureServices()
使用該IConfiguration
服務
public class Startup
{
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
//獲取數據庫鏈接的兩種方式
//var connectionString =configuration["ConnectionStrings:DefaultConnection"];
string connectionString = configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(connectionString);
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IRepository<Student>, InMemoryRepository>();
}
第四步:遷移
遷移是可用於創建和更新數據庫以匹配數據模型的一組工具。使用visual studio中的Package Manage Console。從“工具”菜單中選擇“NuGet 包管理器”>“包管理器控制檯 (PMC)” 。
使用如下命令:
Add-Migration InitialDB
然後就會生成 Migrations/{timestamp}_InitialDB.cs 遷移文件,InitialDB
參數是遷移名稱, 可以使用任何名稱。改文件中的Up
方法創建 Movie 表,並將 Id
配置爲主鍵。 Down
方法可還原 Up
遷移所做的架構更改。
然後輸入命令
Update-Database
將數據庫更新到上一個命令創建的最新遷移。 此命令執行上述文件中的 Up
方法 。
這樣的話,數據表就創建好了
第五步:使用數據庫
新建EfCoreRepository服務,實現IRepository接口
namespace Tutorial.Web.Services
{
public class EfCoreRepository : IRepository<Student>
{
private readonly DataContext _context;
public EfCoreRepository(DataContext context)
{
_context = context;
}
public IEnumerable<Student> GetAll()
{
return _context.Students.ToList();
}
public Student GetById(int id)
{
return _context.Students.Find(id);
}
public Student Add(Student newModel)
{
_context.Students.Add(newModel);
_context.SaveChanges();
return newModel;
}
}
}
將以上服務註冊到Startup,使用AddScoped()方法,防止多線程問題。
services.AddScoped<IRepository<Student>, EfCoreRepository>();
然後就可以用了
6. 佈局
6.1 _ViewStart.cshtml
在每個視圖或頁面之前運行的代碼應置於 _ViewStart.cshtml 文件中。其直接放在Views文件夾下
如果_ViewStart.cshtml 文件中寫了如下代碼,則爲Views文件夾下的所有頁面都採用_Layout
佈局
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
也可以簡寫成
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
每個頁面也可以具體指定要引用哪個佈局
6.2 _ViewImports.cshtml
視圖和頁面可以使用 Razor 指令來導入命名空間並使用依賴項注入。
_ViewImports.cshtml 文件可以放在任何文件夾中,在這種情況下,它只會應用於該文件夾及其子文件夾中的頁面或視圖。 從根級別開始處理 _ViewImports
文件,然後處理在頁面或視圖本身的位置之前的每個文件夾。 可以在文件夾級別覆蓋根級別指定的 _ViewImports
設置。
_ViewImports
文件支持以下指令:
@addTagHelper
@removeTagHelper
@tagHelperPrefix
@using
@model
@inherits
@inject
示例文件
@using WebApplication1.Models
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
6.3 _Layout.cshtml
佈局文件
其他文件可以用@RenderBody()
來使用此佈局文件
佈局也可以通過調用 RenderSection
來選擇引用一個或多個節
@RenderSection("Scripts", required: false)
6.4 Partial View
分部視圖:複用View代碼
6.5 View Component
視圖組件與分部視圖類似,但它們的功能更加強大。
7. 安裝前端庫
在項目文件下添加npm配置文件package.json,內容如下
{
"version": "1.0.0",
"name": "tutorial",
"private": true,
"devDependencies": {
},
"dependencies": {
"bootstrap": "4.4.1",
"jquery": "3.3.1",
"jquery-validation": "1.19.0",
"jquery-validation-unobtrusive": "3.2.10"
}
}
點擊保存之後,依賴項裏就會多出依賴的包。
其中jquery-validation
和jquery-validation-unobtrusive
用於前端驗證
顯示項目的隱藏文件,會發現其實依賴庫是安裝在了項目文件夾下node_modules文件夾中,而不是wwwroot文件夾中,那麼如何伺服這些靜態文件呢。
可以在Startup.cs中指定,添加如下語句
app.UseStaticFiles(new StaticFileOptions
{
RequestPath = "/node_modules",
FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath,"node_modules"))
});
打開_layout.cshtml頁面,添加如下代碼
<link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
也可以使用CDN,如BootCDN
一般建議在開發環境下使用本地的庫,其他環境下使用CDN庫
<!--開發環境-->
<environment include="Development">
<link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</environment>
<!--非開發環境-->
<environment exclude="Development">
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap-grid.css" rel="stylesheet"></environment>
8. ASP.NET CORE IDENTITY
參見官方文檔:ASP.NET Core 身份驗證概述
- 身份認證和授權系統
- 成員管理
- 默認使用MSSQL
- 支持外部的Provider
使用ASP.NET CORE IDENTITY
-
需要登錄註冊的view
Register頁面和Login頁面
-
AccountController
實現註冊、登錄、登出
- 跳轉到註冊或登錄頁面——GET
- 提交註冊或登錄信息——POST
- 登出——POST
-
Model
使用LoginViewModel和RegisterViewModel
ASP.NET CORE IDENTITY重點類
- UserManager< IdentityUser>
- SignInManager< IdentityUser>
配置identity服務
在startup類中的ConfigureServices方法中添加如下代碼
//Identity
services.AddDbContext<IdentityDbContext>(options =>
{
options.UseSqlServer(connectionString, b => b.MigrationsAssembly("Tutorial.Web"));
});
services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<IdentityDbContext>();
services.Configure<IdentityOptions>(options =>
{
// 密碼設置,爲了簡單起見,先不對密碼做任何限制。
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 0;
options.Password.RequiredUniqueChars = 0;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = false;
});
通過調用UseAuthentication來啓用標識。 UseAuthentication
將身份驗證中間件添加到請求管道。
在Startup類中的Configure方法中,app.UseMve()
之前添加app.UseAuthentication();
遷移
由於有多個DbContext,所以需要使用-Context
指定具體的Context
Add-Migration InitialIdentity -Context IdentityDbContext
Update database -Context IdentityDbContext
在視圖中判斷是否登錄
@inject SignInManager<IdentityUser> SignInManager
......
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
@if (SignInManager.IsSignedIn(User))
{
<form asp-controller="Account" asp-action="Logout" method="post" id="LogoutForm">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a href="javascript:document.getElementById('LogoutForm').submit()">登出</a>
</li>
</ul>
</form>
}
else
{
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a asp-controller="Account" asp-action="Register">註冊</a>
</li>
<li class="nav-item">
<a asp-controller="Account" asp-action="Login">登陸</a>
</li>
</ul>
}
</nav>
......
用戶登錄後才能創建學生,需要在HomeController類中的兩個Create方法上面添加[Authorize]
[Authorize]
[HttpGet]
public IActionResult Create()
{
return View();
}