OS type:mac Software:vscode Dotnet core version:2.0/3.1
dotnet sdk
下載地址:https://dotnet.microsoft.com/download/dotnet-core/2.0html
先到上面提供的下載地址,下載對應平臺的dotnet
裝上,而後在命令行窗口輸入dotnet --version
查看輸出是否安裝成功。c++
而後,安裝visual studio code
,安裝以後還須要安裝C#
拓展,要否則每次打開cs
文件都會報錯。git
新建一個空目錄,例如mvc-test
。github
使用命令dotnet new
查看能夠新建的項目類型:web
第一次嘗試,使用ASP.NET Core Empty
就能夠,代號是web
,使用命令dotnet new web
就能夠新建一個空項目,項目的名稱就是當前目錄的名字mvc-test
。編程
目錄主要結構和文件功能以下:json
Program.cs
是程序的主類,Main
函數在這裏定義,內容大體能夠這麼理解:c#
CreateDefaultBuilder
函數會使用默認的方法載入配置,例如經過讀取launchSettings.json
肯定當前的發佈環境:瀏覽器
webhost
經過ASPNETCORE_ENVIRONMENT
讀取發佈環境,而後會讀取對應的配置文件,Development
對應appsettings.Development.json
,Production
對應appsettings.json
。mvc
appsettings
文件是整個web應用的配置文件,若是web應用須要使用某個全局變量,能夠配置到這個文件裏面去。
webhost
在運行前會經過Startup
類,進行一些中間件的配置和註冊,以及進行客戶端的響應內容設置:
注:
dotnet core 3
版本里,取消了WebHost
,使用Host
以更通用的方式進行程序託管。
dotnet core 3 Program.cs
public static Void Main(string[] args) { Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(builder => { builder.UseStartup<Startup>(); }).Build().Run(); }
修改launingSettings.json
中設置的發佈環境對應的配置文件,例如appsetttings.Delelopment.json
內容,添加一個Welcome
字段配置項,以下:
{ "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } }, "Welcome": "Hello from appsettings.json!!" }
修改Startup.cs
文件,添加IConfiguration config
參數,.net core
內部會將配置文件內容映射到這個變量:
/// <summary> /// 註冊應用程序所需的服務 /// </summary> public void ConfigureServices(IServiceCollection services) { } /// <summary> /// 註冊管道中間件 /// </summary> public void Configure(IApplicationBuilder app, IHostingEnvironment env, IConfiguration config) { // 開發環境,使用開發者異常界面 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var welcome = config["Welcome"]; // Run通常放在管道末尾,運行完畢以後直接終止請求,因此在其後註冊的中間件,將不會被執行 app.Run(async (context) => { await context.Response.WriteAsync(welcome); }); }
在終端中使用命令dotnet run
能夠運行這個web應用:
瀏覽器訪問http://localhost:5000
,能夠看到已經成功獲取到Welcome
配置項的值:
經過ILogger
實現控制檯日誌的打印:
public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure( IApplicationBuilder app, IHostingEnvironment env, IConfiguration config, ILogger<Startup> logger) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var welcome = config["Welcome"]; logger.LogInformation(welcome); app.Run(async (context) => { await context.Response.WriteAsync(welcome); }); }
ILogger
使用的時候須要指定打印日誌的類名Startup
,最終打印效果以下:
上面的IConfiguration
能夠直接使用,是由於IConfiguration
服務已經自動註冊過了。
對於自定義的服務,能夠在ConfigureServices
中註冊,例如自定義一個服務WelcomeService
,項目目錄下新建兩個文件IWelcomeService.cs
和WelcomeService.cs
,內容以下:
/* IWelcomeService.cs * * 該接口類定義了一個getMessage方法。 */ namespace mvc_test { public interface IWelcomeService { string getMessage(); } }
/* WelcomeService.cs * * 該類實現了getMessage方法。 */ namespace mvc_test { public class WelcomeService : IWelcomeService { int c = 0; public string getMessage() { c++; return "Hello from IWelcomeService Interface!!!" + c.ToString(); } } }
而後在ConfigureServices
中註冊服務:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IWelcomeService, WelcomeService>(); }
而後在Configure
中使用的時候須要傳參:
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IConfiguration config, ILogger<Startup> logger, IWelcomeService welcomeService) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //var welcome = config["Welcome"]; var welcome = welcomeService.getMessage(); logger.LogInformation(welcome); // Run通常放在管道末尾,運行完畢以後直接終止請求,因此在其後註冊的中間件,將不會被執行 app.Run(async (context) => { await context.Response.WriteAsync(welcome); }); }
運行後結果:
這個例子中,註冊服務使用的函數是AddSingleton
,服務的生命週期除了Singleton
,還有其餘兩個模式:Scoped
和Transient
。
這三個模式的區別:
- Transient:瞬態模式,服務在每次請求時被建立,它最好被用於輕量級無狀態服務;
- Scoped:做用域模式,服務在每次請求時被建立,整個請求過程當中都貫穿使用這個建立的服務。好比Web頁面的一次請求;
- Singleton:單例模式,服務在第一次請求時被建立,其後的每次請求都用這個已建立的服務;
參考資料:
初始學習使用AddSingleton
就好了。
中間件是一種用來處理請求和響應的組件,一個web應用能夠有多箇中間件,這些中間件共同組成一個管道,每次請求消息進入管道後都會按中間件順序處理對應請求數據,而後響應結果原路返回:
參考資料:
新建一個目錄wwwroot
,目錄下新建index.html
文件:
<html> <head> <title>TEST</title> </head> <body> <h1>Hello from index.html!!!</h1> </body> </html>
使用以前的代碼,dotnet run
運行以後訪問http://localhost:5000/index.html
,發現仍是以前的結果,並無訪問到index.html
。
這時候須要使用中間件StaticFiles
來處理靜態文件的請求,修改Startup.cs
的部份內容以下:
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IConfiguration config, ILogger<Startup> logger, IWelcomeService welcomeService) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); //var welcome = config["Welcome"]; app.Run(async (context) => { var welcome = welcomeService.getMessage(); logger.LogInformation(welcome); await context.Response.WriteAsync(welcome); }); }
從新啓動後可正常訪問到index.html
:
前面講到請求進入管道以後是安裝中間件添加順序處理的請求,若是當前中間件不能處理,纔會交給下一個中間件,因此能夠嘗試一下將上面的代碼調整一下順序:
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IConfiguration config, ILogger<Startup> logger, IWelcomeService welcomeService) { if (env.IsDevelopment()) {å app.UseDeveloperExceptionPage(); } //var welcome = config["Welcome"]; app.Run(async (context) => { var welcome = welcomeService.getMessage(); logger.LogInformation(welcome); await context.Response.WriteAsync(welcome); }); app.UseStaticFiles(); }
能夠看到StaticFiles
放到了最後,這樣的話由於index.html
請求會先到Run
的地方,直接返回了,因此不能進入到StaticFiles
裏,訪問獲得的內容就是:
經過StaticFiles
能夠成功訪問到index.html
,可是若是想要index.html
成爲默認網站主頁,須要使用中間件DefaultFiles
,修改上面代碼爲:
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IConfiguration config, ILogger<Startup> logger, IWelcomeService welcomeService) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseDefaultFiles(); app.UseStaticFiles(); //var welcome = config["Welcome"]; app.Run(async (context) => { var welcome = welcomeService.getMessage(); logger.LogInformation(welcome); await context.Response.WriteAsync(welcome); }); }
DefaultFiles
內部會自動將/
修改成index.html
而後交給其餘中間件處理,因此須要放在StaticFiles
的前面。
使用FileServer
也能夠實現一樣的效果:
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IConfiguration config, ILogger<Startup> logger, IWelcomeService welcomeService) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseFileServer(); //var welcome = config["Welcome"]; app.Run(async (context) => { var welcome = welcomeService.getMessage(); logger.LogInformation(welcome); await context.Response.WriteAsync(welcome); }); }
除了使用內置的中間件以外,還能夠用如下幾種方式註冊中間件:
Use
和UseWhen
註冊的中間件在執行完畢以後能夠回到原來的管道上;
Map
和MapWhen
能夠在新的管道分支上註冊中間件,不能回到原來的管道上;
When
的方法能夠經過context
作更多的中間件執行的條件;
Run
用法和Use
差很少,只不過不須要接收next
參數,放在管道尾部;
例如實現返回對應路徑內容:
/// <summary> /// 註冊應用程序所需的服務 /// </summary> public void ConfigureServices(IServiceCollection service) { } /// <summary> /// 註冊管道中間件 /// </summary> public void Configure(IApplicationBuilder app, IHostEnvironment env) { // 開發環境,添加開發者異常頁面 if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // Use 方式 app.Use(async (context, next) => { if(context.Request.Path == new PathString("/use")) { await context.Response.WriteAsync($"Path: {context.Request.Path}"); } await next(); }); // UseWhen 方式 app.UseWhen(context => context.Request.Path == new PathString("/usewhen"), a => a.Use(async (context, next) => { await context.Response.WriteAsync($"Path: {context.Request.Path}"); await next(); })); // Map 方式 app.Map(new PathString("/map"), a => a.Use(async (context, next) => { // context.request.path 獲取不到正確的路徑 //await context.Response.WriteAsync($"Path: {context.Request.Path}"); await context.Response.WriteAsync($"PathBase: {context.Request.PathBase}"); foreach(var item in context.Request.Headers) { await context.Response.WriteAsync($"\n{item.Key}: {item.Value}"); } })); // MapWhen 方式 app.MapWhen(context => context.Request.Path == new PathString("/mapwhen"), a => a.Use(async (context, next) => { await context.Response.WriteAsync($"Path: {context.Request.Path}"); await next(); })); // Run 放在最後,無關緊要,主要爲了驗證是否能夠回到原來的管道上繼續執行 app.Run(async (context)=> { await context.Response.WriteAsync("\nCongratulation, return to the original pipe."); }); }
能夠看到只有/use
和/usewhen
能夠執行到Run
。
注:這裏碰到一個問題,就是訪問
/map
路徑的時候獲取到的context.Request.Path
爲空,其餘字段獲取都挺正常,神奇。不過,可使用context.Request.PathBase
獲取到。
對於上面註冊中間件的幾種方式,好比Use
內部若是寫太多的代碼也不合適,因此能夠本身封裝中間件,封裝完成以後能夠像內置中間件同樣使用UseXxxx
的方式註冊。
本例目標要完成一箇中間件能夠檢測HTTP
請求方法,僅接受GET
、HEAD
方法,步驟以下:
新建一個文件夾mymiddleware
,新建文件HttpMethodCheckMiddleware.cs
,中間件封裝須要實現兩個方法:
HttpMethodCheckMiddleware
: 構造函數,參數類型爲RequestDelegate
;Invoke
: 中間件調度函數,參數類型爲HttpContext
,返回類型爲Task
;文件內容以下:
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace middleware.mymiddleware { /// <summary> /// 請求方法檢查中間件,僅處理HEAD和GET方法 /// </summary> public class HttpMethodCheckMiddleware { private readonly RequestDelegate _next; /// <summary> /// 構造方法,必須有的 /// </summary> /// <param name="requestDelegate">下一個中間件</param> public HttpMethodCheckMiddleware(RequestDelegate requestDelegate) { this._next = requestDelegate; } /// <summary> /// 中間件調度方法 /// </summary> /// <param name="context">HTTP上下文</param> /// <returns>TASK任務狀態</returns> public Task Invoke(HttpContext context) { // 若是符合條件,則將httpcontext傳給下一個中間件處理 if(context.Request.Method.ToUpper().Equals(HttpMethods.Head) || context.Request.Method.ToUpper().Equals(HttpMethods.Get)) { return _next(context); } // 不然直接返回處理完成 context.Response.StatusCode = 400; context.Response.Headers.Add("X-AllowedHTTPVerb", new[] {"GET,HEAD"}); context.Response.ContentType = "text/plain;charset=utf-8"; // 防止中文亂碼 context.Response.WriteAsync("只支持GET、HEAD方法"); return Task.CompletedTask; } } }
這樣就能夠直接在Startup
中使用了:
app.UseMiddleware<HttpMethodCheckMiddleware>();
還能夠編寫一個擴展類,封裝成相似內置中間件的方式UseXxx
。新建CustomMiddlewareExtension.cs
文件,內容以下:
using Microsoft.AspNetCore.Builder; namespace middleware.mymiddleware { /// <summary> /// 封裝中間件的擴展類 /// </summary> public static class CustomMiddlewareExtension { /// <summary> /// 添加HttpMethodCheckMiddleware中間件的擴展方法 /// </summary> public static IApplicationBuilder UseHttpMethodCheckMiddleware(this IApplicationBuilder app) { return app.UseMiddleware<HttpMethodCheckMiddleware>(); } } }
如今就能夠直接調用UseHttpMethodCheckMiddleware
註冊中間件了.
執行結果截圖省略。
疑問:那個CustomMiddlewareExtension
也沒見引用,怎麼就能夠直接使用app.UseHttpMethodCheckMiddleware
方法了?
有的可能和我同樣,c#都沒有學明白就直接開始擼dotnet了,看到這一臉懵逼,不過通過一番搜索,原來這是c#中對已有類或接口進行方法擴展的一種方式,參考C#編程指南。
這一節先當了解,暫時用處不大,學完也會忘掉
先簡單看一下ASP.NET core
內置的路由方式(直接上startup.cs代碼內容):
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; namespace routing { public class Startup { public void ConfigureServices(IServiceCollection servcies) { } public void Configure(IApplicationBuilder app) { // 新建一個路由處理器 var trackPackageRouteHandler = new RouteHandler(context => { var routeValues = context.GetRouteData().Values; return context.Response.WriteAsync($"Hello! Route values: {string.Join(", ", routeValues)}"); }); var routeBuilder = new RouteBuilder(app, trackPackageRouteHandler); // 經過MapRoute添加路由模板 routeBuilder.MapRoute("Track Package Route", "package/{opration}/{id:int}"); routeBuilder.MapGet("hello/{name}", context => { var name = context.GetRouteValue("name"); return context.Response.WriteAsync($"Hi, {name}!"); }); var routes = routeBuilder.Build(); app.UseRouter(routes); } } }
從代碼中可知,須要先建立一個路由處理器trackPackageRouteHandler
,而後經過RouteBuilder
將app
和trackPackageRouteHandler
綁定,並且須要添加一個匹配模板,最後將生成的路由器添加到app中。
其中添加路由匹配模板是使用了不一樣的方法:
trackPackageRouteHandler
;GET
請求方式,而且第二個參數能夠指定處理請求的邏輯;上面設置路由的方式過於複雜,因此通常狀況下一般使用MVC
將對應的URL請求路由到Controller
中處理,簡化路由規則。
在開始MVC
路由以前,先來學習一下Controller
和Action
他們的關係以及如何建立。
Controller
通常是一些public
類,Action
對應Controller
中的public
函數,因此他們的關係也很明瞭:一個Controller
能夠有多個Action
。
Controller
如何建立,默認狀況下知足下面的條件就能夠做爲一個Controller
:
Controllers
中Controller
結尾並繼承自Controller
,或被[Controller]
標記的類[NotController]
被標記例如一個Contoller
的經常使用模式以下:
using Microsoft.AspNetCore.Mvc; public class HomeController : Controller { //... }
而Action
就不須要許多條條框框了,只要寫在Controller
中的方法函數都會被當成Action
對待,若是不想一個函數被當作Action
則須要添加[NotAction]
標記。
留待測試:
- 若是同時添加
[Controller]
和[NotController]
會發生什麼情況?是誰在最後誰生效嗎仍是報錯?- 是否是隻須要知足
Controller
後綴就能夠了,不必定非得繼承Controller
,繼承他只是爲了使用一些已經打包好的父類函數。
首先建立一個HomeController
測試路由用,須要建立到Controllers
目錄下:
using Microsoft.AspNetCore.Mvc; namespace routing.Controllers { public class HomeController: Controller { public string Index() { return "Hello from HomeController.Index"; } } }
.net core 2.0
和.net core 3.0
建立路由的方式有所不一樣,如今分開說一下,先說一下舊的方式。
先在ConfigureServices
中註冊MVC
服務,而後Configure
中配置路由模板:
public void ConfigureServices(IServiceCollection service) { // 註冊服務 service.AddMvc(); } public void Configure(IApplicationBuilder app, IHostEnvironment env) { if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // 路由模板 app.UseMvc(routes => { routes.MapRoute(template: "{controller}/{action}/{id?}", defaults: new {controller = "Home", action = "Index"}); }); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
可是放到dotnet3
裏面是會報錯的:
MVCRouteStartup.cs(23,13): warning MVC1005: Using 'UseMvc' to configure MVC is not supported while using Endpoint Routing. To continue using 'UseMvc', please set 'MvcOptions.EnableEndpointRouting = false' inside 'ConfigureServices'.
提示UseMvc
不支持Endpoint Routing
,經過查資料(stackoverflow)找到緣由,說的很清楚:2的時候MVC
路由基於IRoute
,3改爲Endpoint
了,官方推薦將UseMVC
使用UseEndpoiont
替換:
app.UseRouting(); // 必須寫,若是使用了UseStaticFiles要放在他以前 app.UseEndpoints(endpoionts => { endpoionts.MapControllerRoute(name: "MVC TEST ROUTE", pattern: "{controller}/{action}/{id?}", defaults: new {controller = "Home", action = "Index"}); });
ConfigureServices
中註冊MVC
也有兩種方式:
services.AddMVC();
或
service.AddControllersWithViews(); service.AddRazorPages();
固然,若是不想把UseMap
去掉,那麼能夠按照報錯的提示在AddMVC
的時候配置一下參數禁用EndpointRoute
:
services.AddMvc(options => options.EnableEndpointRouting = false);
而後就能夠跑起來了:
好,扯了半天報錯,仍是回到mvc路由上,上面是簡單演示了一下在Startup
中如何建立路由,其實mvc路由有兩種定義方式:
Startup
中配置;[Route]
直接對controller
或action
進行標記;修改HomeController
加上路由標記:
using Microsoft.AspNetCore.Mvc; namespace routing.Controllers { [Route("h")] [Route("[controller]")] public class HomeController: Controller { [Route("")] [Route("[action]")] public string Index() { return "Hello from HomeController.Index"; } } }
經過[controller]
和[action]
就能夠動態的指代home
和index
(路徑不區分大小寫),這樣若是路由會隨着類名或方法名稱的改變自動調整。
而且能夠看出,能夠多個[Route]
標記重疊使用,例如訪問/h
和/home/index
效果同樣:
經過實驗能夠看出,特性路由會覆蓋掉約定路由。
先總結這些吧,忽然發現asp.net core
這個東西仍是挺先進的,好比依賴注入,Startup
中的函數多數都是interface
,爲何直接對接口操做就能夠改變一些東西或者讓咱們能夠本身註冊一箇中間件到app上,而後爲何都不須要引用或者實例化就能夠直接用app調用了,這都和依賴注入有關係吧,還有接口的設計理念也好像和其餘語言的不太同樣,神奇了。
放到了github上,部分代碼好像丟失了,不過應該沒關係。