紅花還得綠葉襯托。vue前端開發離不開數據,這數據正來源於請求web api。爲何採用.net core web api呢?由於考慮到跨平臺部署的問題。即便眼下部署到window平臺,那之後也能夠部署到Linux下。前端
.net core web api與mvc的web api相似。我把遇到的問題概括下:vue
一、部署問題web
都說.net core web api,後面我簡稱api。它有兩種部署方式,一個是在iis上部署,另一個是自託管,相似控制檯,經過dotnet run 命令啓動的。json
1.1 自託管部署api
dotnet myapp.dll
網上說,經過hosting.json安全
{ "server.urls": "http://localhost:60000;http://localhost:60001" }
這種方式有個問題,在配置了urls,並無走配置。mvc
public static void Main(string[] args) { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("hosting.json", optional: true) .Build(); var host = new WebHostBuilder() .UseKestrel() .UseConfiguration(config) .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); }
不過人家是說在Linux環境下的部署,我在window下測試是不行的,不知道是哪的問題,後面能夠再研究。app
1.二、iis上部署async
必須首先安裝AspNetCoreModule,搜索這個模塊,它的描述以下:ide
The ASP.NET Core Module allows ASP.NET Core apps to run in an IIS worker process (in-process) or behind IIS in a reverse proxy configuration (out-of-process). IIS provides advanced web app security and manageability features.
這句話大意:api有兩種運行模式,一種是運行在iis工做進程中(In-process hosting model),另一種是經過反向代理配置,運行在外(Out-of-process hosting model)。具體,可參考官方文檔。
這是文檔中 In-process hosting model圖,咱們能夠看出,http請求首先到達kernel-mode HTTP.sys driver,http監聽器,監聽器把請求給iis,首先是Asp.NET Core Module接受,而後傳遞給IISHttpServer,它把請求轉換爲託管代碼,進入.net core middelware pipline,最後纔是咱們的api代碼。換句話說,Asp.NET Core Module相似中間件的做用,它先處理的一部分事情。這是咱們項目中採起的部署方案,另一種模式可能比較複雜,你們閱讀官方文檔。
二、全局異常處理
咱們知道mvc中,有兩種異常處理:
使用Global.asax的Application_Error事件進行全局異常處理以及使用HandleErrorAttribute特性捕獲全局異常
.net core api中能夠編寫異常處理的中間件,以下:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Xml.Serialization; namespace ElectronInfoApi.Business { public class GlobalExceptionMiddleware { private readonly RequestDelegate next; public GlobalExceptionMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context) { try { await next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private async Task HandleExceptionAsync(HttpContext context, Exception exception) { if (exception == null)return; await WriteExceptionAsync(context, exception).ConfigureAwait(false); } private async Task WriteExceptionAsync(HttpContext context, Exception exception) { //記錄日誌 this.Log().Error($"系統發生了異常:{exception.Message}, {exception.StackTrace}"); //返回友好的提示 var response = context.Response; //狀態碼 if (exception is UnauthorizedAccessException) response.StatusCode = (int)HttpStatusCode.Unauthorized; else if (exception is Exception) response.StatusCode = (int)HttpStatusCode.BadRequest; response.ContentType = context.Request.Headers["Accept"]; response.ContentType = "application/json"; await response.WriteAsync(JsonConvert.SerializeObject(new {state=400,message="出現未知異常"})).ConfigureAwait(false); } } public static class VisitLogMiddlewareExtensions { public static IApplicationBuilder UseGlobalException(this IApplicationBuilder builder) { return builder.UseMiddleware<GlobalExceptionMiddleware>(); } } }
在startup>Configure中添加
app.UseGlobalException();
官網有文檔,是這麼定義中間件的:
Middleware is software that's assembled into an app pipeline to handle requests and responses
三、安全驗證
接口驗證,是爲了安全性考慮,採用Jwt(Json web token)。
第一步,添加包引用:
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
第二步,配置:
"Issuer": "ElectronInfo", "Audience": "ElectronInfo", "SecretKey": "ElectronInfo is a web of shanxi dianzi qingbao weiyuanhui"
第三步,在Startup>ConfigureServices中添加受權服務:
var jwtSettings = new JwtSettings(){ Issuer=AppSetting.GetConfig("Issuer"), Audience=AppSetting.GetConfig("Audience"), SecretKey=AppSetting.GetConfig("SecretKey"), }; services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(o => { o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidIssuer = jwtSettings.Issuer, ValidAudience = jwtSettings.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)), ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; });
第四步:在Startup>Configure中添加
app.UseAuthentication();
第五步:給整個Controller或者須要接口驗證的action中添加
[Authorize]
附:AppSetting類,讀取appsettings.json,以下:
using System.IO; using Microsoft.Extensions.Configuration; namespace ElectronInfoApi.Business { public class AppSetting { private static readonly object objLock = new object(); private static AppSetting instance = null; private IConfigurationRoot Config {get; } private AppSetting() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional:false, reloadOnChange:true); Config = builder.Build(); } public static AppSetting GetInstance() { if (instance == null) { lock (objLock) { if (instance == null) { instance = new AppSetting(); } } } return instance; } public static string GetConfig(string name) { return GetInstance().Config.GetSection(name).Value; } }}
四、日誌log4
.net core中原本就支持console輸出日誌。不過今天我要說的是log4,在傳統的.net中廣泛使用。
第一步,添加包引用:
<PackageReference Include="log4net" Version="2.0.8" />
第二步,添加配置文件log4net.config:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <!-- This section contains the log4net configuration settings --> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout" value="%date [%thread] %-5level %logger - %message%newline" /> </appender> <!--<appender name="FileAppender" type="log4net.Appender.FileAppender"> <file value="log-file.log" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> --> <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="logfile/" /> <appendToFile value="true" /> <rollingStyle value="Composite" /> <staticLogFileName value="false" /> <datePattern value="yyyyMMdd'.log'" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="1MB" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> <!-- Setup the root category, add the appenders and set the default level --> <root> <level value="ALL" /> <appender-ref ref="ConsoleAppender" /> <!--<appender-ref ref="FileAppender" />--> <appender-ref ref="RollingLogFileAppender" /> </root> </log4net> </configuration>
第三步,包裝以及擴展log4,爲了更方便使用:
首先定義一個接口IMLog:
using System; namespace ElectronInfoApi.Business { public interface IMLog { // Methods void Debug(string message); void Error(string message, Exception exception); void Error(string message); void Fatal(string message); void Info(string message); void Warn(string message); } public interface IMLog < T > { }
再定義包裝器Log4NetWapper:
using System; using log4net; using log4net.Core; namespace ElectronInfoApi.Business { public class Log4NetWapper:IMLog, IMLog < Log4NetWapper > { private ILog _logger; public Log4NetWapper(string loggerName) { this._logger = LogManager.GetLogger(Startup.repository.Name, loggerName); } public void Debug(string message) { _logger.Debug(message); } public void Error(string message, Exception exception) { _logger.Error(message, exception); } public void Error(string message) { _logger.Error(message); } public void Fatal(string message) { _logger.Fatal(message); } public void Info(string message) { _logger.Info(message); } public void Warn(string message) { _logger.Warn(message); } } }
最後定義擴展方法 LogExtensions:
using System.Collections.Concurrent; namespace ElectronInfoApi.Business { public static class LogExtensions { // Fields private static readonly ConcurrentDictionary < string, IMLog > _dictionary = new ConcurrentDictionary < string, IMLog > (); // Methods public static IMLog Log(this string objectName) { if ( ! _dictionary.ContainsKey(objectName)) { IMLog log = new Log4NetWapper(objectName); _dictionary.TryAdd(objectName, log); } return _dictionary[objectName]; } public static IMLog Log < T > (this T type) { return typeof(T).FullName.Log(); } }}
第四步,在Startup中使用:
public static ILoggerRepository repository {get; set; } public Startup(IConfiguration configuration) { repository = LogManager.CreateRepository("NETCoreRepository"); XmlConfigurator.Configure(repository, new FileInfo("log4net.config")); Configuration = configuration; } public IConfiguration Configuration {get; }
五、.對net core中startup理解,見官方文檔
好了,關於.net core api也是第一次正式使用,就總結到這裏。