目錄html
學習Net Core的我的筆記,記錄前端
建議看微軟官方文檔,看不懂的查一下 教程:ASP.NET Core 入門web
Startup類中的ConfigureServices方法是用於服務註冊IOCsql
ConfigureServices這個方法是用於服務註冊的,服務就是IOC裏面的類數據庫
IOC容器內的服務有三種生命週期json
咱們有一個類和一個接口,接口的實現類,這裏不寫,註冊以下c#
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IStudentService,StudentService>(); }
寫了一個單例的服務,已經注入了,調用後續再寫windows
下圖很經典,用戶的請求須要通過管道,管道內部能夠寫中間件,若是你什麼都不寫,那麼請求返回的就是固定的,你加了中間件,就會有變化,好比權限驗證,身份驗證,MVC處理等中間件後端
Startup類裏面的Configure方法就是管道的方法,能夠在裏面寫中間件api
app.Run就是運行的一箇中間件,如今咱們寫一個新的中間件
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); app.Run(async (context) => { await context.Response.WriteAsync("許嵩!"); });
運行,能夠發現,仍是Hello World,根本沒有許嵩,由於中間件根本沒往下執行,能夠這樣設置
app.Use(async (context,next) => { await context.Response.WriteAsync("Hello World!"); await next(); }); app.Run(async (context) => { await context.Response.WriteAsync("許嵩!"); });
加了一個next,而後執行await next(); 這樣就會執行按照順序的下一個中間件
public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILogger<Startup> logger) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Use(async (context,next) => { logger.LogInformation("管道1開啓"); await context.Response.WriteAsync("Hello World!"); await next(); logger.LogInformation("管道1結束"); }); app.Run(async (context) => { logger.LogInformation("管道2開啓"); await context.Response.WriteAsync("許嵩!"); logger.LogInformation("管道2結束"); }); }
加了一個日誌ILogger,這樣再運行,注意此次運行不選擇IIS了,咱們選擇Kestrel服務器,就是你的解決方案同名的那個,運行,能夠查看日誌
直接新建一個控制器Controller,你會發現,Controller沒有引入,沒法使用.
.net Core和.net Framework不同,.net MVC寫MVC直接就能夠, .net Core須要註冊一下,恰好使用了上面的IOC服務註冊,仍是在Startup類中的ConfigureServices寫:
services.AddMvc();
管道調用的時候能夠加一個路由
app.UseMvc(route => { route.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
Controllers,Models,Views三個文件夾的建立
新建HomeController,添加Index視圖
public class HomeController : Controller { public IActionResult Index() { return View(); } }
@{ ViewData["Title"] = "Index"; } <h1>個人第一個.Net Core Web項目</h1>
有三種官方給的環境,分別是
Development(開發)、Staging (分階段)和 Production(生產)
更改環境變量,點擊項目,右鍵屬性,選擇調試,更改,如圖
在Properties下的launchSettings.json能夠更改不一樣環境的參數配置
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:13335", "sslPort": 44347 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "StudyNetCore": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
Startup能夠判斷環境
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } if (env.IsProduction()) { System.Console.WriteLine("..."); } if (env.IsStaging()) { System.Console.WriteLine("..."); } if (env.IsEnvironment("自定義環境")) { System.Console.WriteLine("..."); }
在ConfigureServices類中注入
services.AddHttpsRedirection(option=> { option.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect; option.HttpsPort = 5001; });
在Configure方法裏面使用HTTPS管道,注意HTTPS管道必須在MVC管道以前,不然沒意義了就
app.UseHttpsRedirection();
改爲這樣就能夠,加了一個launchUrl,初始值爲http://localhost:5000/Home/Index
這樣
{ "profiles": { "StudyNetCore": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000/Home/Index", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
真是一波三折~我開始就遇到報錯了
這個報錯是看看你的項目能不能編譯成功,若是有報錯請解決報錯
這個錯誤不知道是啥,可是能夠找出來是MVC項目的錯,因此我把MVC項目的obj文件夾下面的全刪了,而後從新編譯一次就能夠了
這個報錯的緣由是,須要把默認項目改爲MVC的,才能夠執行 Add-Migration InitialCreate,我也不知道爲啥😠
首先須要新建三個項目,一個是主項目,我建的是MVC
一個是Model類庫,一個是操做EF Core的遷移類庫
如圖,我建了三個,DB是來放EF Core遷移文件的,DomainModels是放領域模型的,下面的MVC是業務
我爲了測試,在DomainModels下新建了一個Blog
using System; using System.Collections.Generic; namespace DomainModels { public class Blog { public int BlogId { get; set; } public string Url { get; set; } public int Rating { get; set; } public List<Post> Posts { get; set; } } }
注意,必定要有主鍵,默認有Id或者XXId的都是主鍵
在DB裏面使用NuGet引用EF Core,以下
新建DBContext的繼承類,個人是這樣的
using DomainModels; using Microsoft.EntityFrameworkCore; using System; namespace DB { public class MyContext:DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Server=.;Database=EFCore;Trusted_Connection=True;"); //若是是帶用戶名密碼的這樣寫 optionsBuilder.UseSqlServer("server=192.168.3.8;uid=sa;pwd=123;database=VaeDB;"); } public DbSet<Blog> Blogs { get; set; } } }
開始遷移,在視圖->其餘窗口->包管理控制檯
首先輸入: Add-Migration InitialCreate
後面的是名稱,隨意寫,我寫的InitialCreate
而後執行遷移文件:Update-Database
等待一會,你就會發現本地的數據庫裏面已經有了EFCore數據庫和Blogs數據表了
在MVC項目裏面新建了一個Controller
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DB; using DomainModels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace StudyNetCore.Controllers { public class EFCoreController : Controller { public IActionResult Index() { return View(); } public void Add() { using (var context = new MyContext()) { var blog = new Blog { Url = "http://sample.com", Rating=98 }; context.Blogs.Add(blog); context.SaveChanges(); } } public void Remove() { using (var context = new MyContext()) { var blog = context.Blogs.Single(b => b.BlogId == 2); context.Blogs.Remove(blog); context.SaveChanges(); } } public void Update() { using (var context = new MyContext()) { var blog = context.Blogs.Single(b => b.BlogId == 1); blog.Url = "http://www.vae.com"; context.SaveChanges(); } } public void Select() { using (var context = new MyContext()) { var blogs = context.Blogs.ToList(); Console.WriteLine(blogs); } } } }
運行項目,輸入對應的url,成功操做了數據
上面的數據庫配置文件是寫在MyContext裏面的,這樣不合適,因此我寫在了json文件裏
找到appsettings.json,在裏面加上
"ConnectionStrings": { "DefaultConnection": "Server=.;Database=EFCore;Trusted_Connection=True;" }
在Startup裏面的ConfigureServices方法裏面寫
public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; set; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddSingleton<IStudentService,StudentService>(); services.AddMvc();
public class MyContext : DbContext { public MyContext(DbContextOptions<MyContext> options) : base(options) { } public DbSet<Blog> Blogs { get; set; } }
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DB; using DomainModels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace StudyNetCore.Controllers { public class EFCoreController : Controller { private readonly MyContext _myContext; public EFCoreController(MyContext myContext) { _myContext = myContext; } public IActionResult Index() { return View(); } public void Add() { var blog = new Blog { Url = "http://sample.com", Rating = 98 }; _myContext.Blogs.Add(blog); _myContext.SaveChanges(); } public void Remove() { var blog = _myContext.Blogs.Single(b => b.BlogId == 2); _myContext.Blogs.Remove(blog); _myContext.SaveChanges(); } public void Update() { var blog = _myContext.Blogs.Single(b => b.BlogId == 1); blog.Url = "http://www.vae.com"; _myContext.SaveChanges(); } public IActionResult Select() { var list = _myContext.Blogs.ToList(); Console.WriteLine(list); return View(); } } }
和數據庫表字段對應的就是EntityModel,和視圖對應的就是ViewModel
我新建一個EntityModel,以下
public class Student { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDay { get; set; } }
能夠看到,學生有兩個名字,還有出生年月日,可是我頁面上只想看到一個名字和年齡,這就須要處理一下了,ViewModel以下
public class HomeIndexViewModel { public string Name { get; set; } public int Age { get; set; } }
轉化以下:
private readonly StudentService _studentService=new StudentService(); public IActionResult Index() { List<Student> studentList = _studentService.getStudentList(); var vms = studentList.Select(x => new HomeIndexViewModel { Name = x.FirstName + x.LastName, Age = DateTime.Now.Subtract(x.BirthDay).Days / 365 }); return View(vms); }
視圖使用以下:
@model IEnumerable<StudyNetCore.ViewModels.HomeIndexViewModel> @{ ViewData["Title"] = "Index"; } <h1>個人第一個.Net Core Web項目</h1> <h1>Student的ViewModel數據展現</h1> <ul> @foreach (var item in Model) { <li>@item.Name - @item.Age</li> } </ul>
@model是指令,只是爲了讓@Model有智能提示
結果以下:
前端HTML輸入Model傳給後臺我知道,待補充
待補充
而後頁面重複刷新會形成post的重複提交,解決辦法就是post提交一次以後就當即重定向,以下
return RedirectToAction(nameof(Detail));
一個輸入的表單,例如我輸入用戶的姓名,年齡,手機號,郵箱,密碼等等,這個對應的Model須要作數據驗證
可能有人會問,前端我驗證不就得了,前端數據填寫不合法的時候就報錯,後端的Model還驗證什麼呢?我之前也是這麼想的
直到我知道了PostMan.....
有不少方法能夠繞過你的前端驗證的,若是你沒加後端驗證,有人直接傳入非法數據就不安全了
因此,數據驗證.先後端都須要作,雙重保險
相似這樣
public class Student { [Required] public int Id { get; set; } [StringLength(20)] public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)] public DateTime BirthDay { get; set; } }
下面列了一些,更多的用到再更新
[Required]//必須數據
[StringLenght(100)]//最大長度100
[Range(0,999)]//取值範圍是0-999
[DateType(DataType.Date)]//要求此數據必爲日期類型
[CreaitCard]//信用卡
[Phone]//電話號碼
[EmailAddress]//郵箱地址
[DataType(DataType.Password)] //密碼
[Url]//必須是url連接
[Compare]//比較數據是否相同
例子
public class Movie { public int ID { get; set; } [StringLength(60, MinimumLength = 3)] [Required] public string Title { get; set; } [Display(Name = "Release Date")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Range(1, 100)] [DataType(DataType.Currency)] [Column(TypeName = "decimal(18, 2)")] public decimal Price { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")] [Required] [StringLength(30)] public string Genre { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")] [StringLength(5)] [Required] public string Rating { get; set; } }
Required
和 MinimumLength
特性表示屬性必須有值;但用戶可輸入空格來知足此驗證。RegularExpression
特性用於限制可輸入的字符。 在上述代碼中,即「Genre」(分類):
RegularExpression
「Rating」(分級):
Range
特性將值限制在指定範圍內。StringLength
特性使你可以設置字符串屬性的最大長度,以及可選的最小長度。這個是母版頁,有兩個地方須要說明
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> </body> </html>
第一個@ViewBag.Title 子頁面能夠這樣寫,這樣就標題映射過去了
@{ ViewBag.Title="Index"; }
下面的@RenderBody()就是你子頁面展示的地方
還能夠寫一個母版頁節點,讓子頁面去加載,例如
@RenderSection("Footer",required:false)
這樣子頁面就可使用
@section Footer{ <h3>你們好,我是腳</h3> }
required:false是否是每一個頁面必須的,若是不寫,每一個子頁面都得加載Footer節點
這個是每一個頁面加載以前都必須先加載的頁面,一般直接放在Views的文件夾下面,這樣就能夠對全部的頁面起做用了.若是把_ViewStart.cshtml放在了Home文件夾下面,那麼僅僅對Home文件夾下的頁面起做用
例如,咱們能夠把每一個頁面都有的東西放在_ViewStart.cshtml裏面
@{ Layout = "~/Views/Shared/_Layout.cshtml"; } <h1>都得先加載我</h1>
有時候咱們會在視圖裏面用到本身寫的類,這個時候咱們一般是直接寫全引用,或者在頁面上寫@using
可是,每個頁面都寫重複的@using這樣就很差了,能夠統一的在_ViewImports.cshtml裏寫@using
這樣每一個頁面直接寫類的名稱就能夠了,好比
@using StudyNetCore.Models;
一般放在Views文件夾下
這個是分部視圖,多個頁面都用到的HTML能夠放到這裏面,一般放在Share文件夾下
@model IEnumerable<HomeIndexViewModel> <h1>我是分部視圖</h1> <ul> @foreach (var item in Model) { <li>@item.Name</li> <li>@item.Age</li> } </ul>
調用的時候也很簡單,輸入名字傳入Model就能夠了
@Html.Partial("_PartialView",Model) <partial name="_PartialView" for="HomeIndexViewModel" />
這兩種方式均可以,可是推薦使用TagHelper方式
缺點,分部視圖PartialView的缺點就是,這個Model必須是調用者傳過來的,不能本身去查詢加載數據,下面的ViewComponents就很好的解決了這個問題
暫時不寫,感受略麻煩,感受可使用ViewBag代替
兩個主要的類先了解一下
UserManager
SignInManager
在_ViewImports.cshtml裏面加上
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
而後頁面就能夠直接調用,如
<a asp-controller="Home" asp-action="data">仍是點我,TagHelper類型的</a>
爲何使用TagHelper?由於改變路由以後這些會自動映射
若是返回的是字符類型,那麼可使用 $ 符號進行拼接,仍是不錯的挺好用,
HtmlEncoder.Default.Encode這個東西是防止js注入的,可是我嘗試中文也顯示的被編碼過了,我不知道怎麼不讓中文被編碼
public string Index(string name, int age=17) { return HtmlEncoder.Default.Encode($"Hello {name},you age is {age}"); }
Model和數據庫的字段都是一一對應的,可是我有一個這樣的名字,以下
public DateTime ReleaseDate { get; set; }
ReleaseDate這個名字顯然不合適,加一個空格就好多了,能夠這樣寫
[Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; }
前端顯示就使用@Html.DisplayNameFor,以下
<th> @Html.DisplayNameFor(model => model.ReleaseDate) </th>
這樣一來顯示的就是有空格的名字了
這樣講一下@Html.DisplayNameFor就是現實名字,@Html.DisplayFor就是顯示數據
我後臺傳入的是一個List,以下
public IActionResult Test() { return View(_context.Movie.ToList()); }
前端接受使用model,而後處理的時候可使用以下
@model IEnumerable<DomainModels.Movie> @{ ViewData["Title"] = "Test"; } <h1>我就是Movie下面的一個Test頁面</h1> <div> <ul> @foreach (var item in Model) { <li> 名字是: @Html.DisplayNameFor(model => model.Title) | 內容是: @Html.DisplayFor(modelItem => item.Title) 類別是: @Html.DisplayNameFor(model => model.Genre) | 內容是: @Html.DisplayFor(modelItem => item.Genre) 價格是: @Html.DisplayNameFor(model => model.Price) | 內容是: @Html.DisplayFor(modelItem => item.Price) </li> } </ul> </div>
講一下標題可使用model => model.Title
內容的話必須使用modelItem => item.Title,其中前面的modelItem 隨意寫,後面的 item.Title是必須使用的
這個WebAPI和MVC有什麼區別呢?其實他倆乍一看很像,都是控制器加Action的模式
可是我以爲最大的區別就在於,MVC是有視圖的,這個框架包含了不少東西
WebAPI呢根本就沒有視圖這個東西,因此內容比較純淨,就是單純的操做數據
很明顯的區別就是一個繼承的是Controller,一個繼承的是ApiController
這個實在沒什麼講的,直接新建便可,使用EF Core來操做數據庫,貼幾個代碼看看
using System.Collections.Generic; using System.Linq; using DB; using DomainModels; using Microsoft.AspNetCore.Mvc; namespace WebAPI.Controllers { [Route("api/[controller]")] [ApiController] public class TodoItemController : ControllerBase { private readonly MyContext _context; public TodoItemController(MyContext context) { _context = context; } // GET: api/TodoItem [HttpGet] public IEnumerable<TodoItem> Get() { return _context.TodoItem.ToList(); } // GET: api/TodoItem/5 [HttpGet("{id}", Name = "Get")] public TodoItem Get(int id) { return _context.TodoItem.FirstOrDefault(m => m.Id == id); } // POST: api/TodoItem [HttpPost] public void Post(TodoItem todoItem) { _context.TodoItem.Add(todoItem); _context.SaveChanges(); } [HttpPut("{id}")] public IActionResult Put(int id, TodoItem todoItem) { if (id != todoItem.Id) { return BadRequest(); } _context.TodoItem.Update(todoItem); _context.SaveChanges(); return Ok("ok"); } // DELETE: api/ApiWithActions/5 [HttpDelete("{id}")] public IActionResult Delete(int id) { TodoItem todoItem = _context.TodoItem.Find(id); if (todoItem == null) { return NotFound(); } _context.TodoItem.Remove(todoItem); _context.SaveChanges(); return Ok("ok"); } } }
MVC能夠直接輸入控制器測試,反正有視圖能夠看,可是WebAPI這樣的須要使用PostMan進行測試
我暫時是直接運行着項目測試的,稍後再講部署的問題
這個稍微講一下,參數是json格式的,因此必須選擇json,默認是Text格式的,不改爲json就會失敗
還有一個地方就是禁止Postman的SSL證書,不然也會失敗
禁用SSL證書以下
這個真的是太好用了,把你的項目設置爲啓動項目,而後 工具>NuGet包管理器>程序包管理器控制檯
在程序包管理器控制檯輸入如下
Scaffold-DbContext "Server=192.168.111.111;Database=VaeDB;uid=sa;pwd=123456789;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
這個很簡單能夠理解,你的數據庫鏈接字符串,最後是導出的位置是Models,也就是你這個啓動項目的Models文件夾下
運行一下,Models就直接生成了
//防止CSRF攻擊 services.AddAntiforgery(options => { //使用cookiebuilder屬性設置cookie屬性。 options.FormFieldName = "AntiforgeryKey_Censtry"; options.HeaderName = "X-CSRF-TOKEN-CENSTRY"; options.SuppressXFrameOptionsHeader = false; });
headers: { "X-CSRF-TOKEN-yilezhu": $("input[name='AntiforgeryKey_yilezhu']").val() }
點擊你的項目發佈,而後你在發佈的文件夾內能夠看到一個項目名.dll,直接右鍵打開控制檯輸入
dotnet Web.dll --urls=http://localhost:8099
固然也能夠不指定訪問端口號,不寫 --urls後面的便可
首先要知道Swagger是什麼,學了有什麼用
就是一個自動生成API文檔的框架
你編寫了一個API的後端程序,須要給其餘人調用,總得寫個文檔吧,否則別人怎麼知道有哪些API接口,分別是幹嗎的
你固然能夠選擇本身一個一個的寫,也能夠選擇使用Swagger自動生成而後喝杯茶
使用Nuget引入Swagger的包,就是這個:Swashbuckle.AspNetCore
Startup裏面有兩個地方須要配置,首先是須要注入一下服務,而後在中間件那裏啓用一下
首先,服務注入,在ConfigureServices方法添加Swagger
//Swagger services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { //只列舉了幾個,還有不少沒寫,詳細可查官方文檔 Title = "My API", Version = "v1", Description="個人API的說明文檔" }); });
而後在中間件的方法Configure中,啓用Swagger
app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
運行你的Web API項目,而後路由輸入swagger,便可
以上簡簡單單的導入包,注入服務,啓用中間件就能夠看到簡單的文檔了,如圖
一目瞭然,接口,還能夠測試接口,像postman同樣
上面簡單的實現了Swagger,如今來進階一下,增長几個功能
先解決第一個問題,運行項目以後首頁直接就是Swagger,這個要在launchSettings.json裏面設定,在Properties下找到launchSettings.json,修改launchUrl
有兩個地方,一個是profiles下的launchUrl,還有一個是項目下的launchUrl
"launchUrl": "swagger",
第二個問題,就是API註釋,找到你的項目,右鍵屬性,來到生成這裏,勾選下面的輸出XML文檔文件,我這裏是項目的根目錄,位置本身隨便選,而後選定以後從新編譯一下項目便可生成xml文件,此時,你會發現出現了很是多的警告,能夠在上面的錯誤和警告那裏加一個 ;1591 這樣就不會有那麼多警告了
在你的API接口上加上註釋,如
/// <summary> /// TodoItem根據Id獲取 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}", Name = "Get")] public TodoItem Get(int id) { return _context.TodoItem.FirstOrDefault(m => m.Id == id); }
這樣運行項目就能夠有註釋了,如圖
雖然新建了一個xml,可是裏面什麼咱們都沒加,你如今能夠點進去看看,發現xml裏面多了好多內容,就是咱們剛纔寫的接口的註釋
若是某些接口不但願別人看到的話,能夠這樣寫
/// <summary> /// TodoItem獲取全部 /// </summary> /// <returns></returns> [HttpGet] [ApiExplorerSettings(IgnoreApi = true)] public IEnumerable<TodoItem> Get() { return _context.TodoItem.ToList(); }
加一個[ApiExplorerSettings(IgnoreApi = true)]就能夠了
第三個問題權限