vue前端開發那些事——後端接口.net core web api

  紅花還得綠葉陪襯。vue前端開發離不開數據,這數據正來源於請求web api。爲什麼採用.net core web api呢?因爲考慮到跨平臺部署的問題。即使眼下部署到window平臺,那以後也可以部署到Linux下。

  .net core web api與mvc的web api類似。我把遇到的問題歸納下:

1、部署問題

都說.net core web api,後面我簡稱api。它有兩種部署方式,一個是在iis上部署,另外一個是自託管,類似控制檯,通過dotnet  run 命令啓動的。

 1.1 自託管部署

dotnet myapp.dll

網上說,通過hosting.json

{
  "server.urls": "http://localhost:60000;http://localhost:60001"
}

這種方式有個問題,在配置了urls,並沒有走配置。

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下測試是不行的,不知道是哪的問題,後面可以再研究。

1.2、iis上部署

必須首先安裝AspNetCoreModule,搜索這個模塊,它的描述如下:

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類似中間件的作用,它先處理的一部分事情。這是我們項目中採取的部署方案,另外一種模式可能比較複雜,大家閱讀官方文檔。

2、全局異常處理

我們知道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>();
        }
    }
}
View Code

在startup>Configure中添加

app.UseGlobalException();

官網有文檔,是這麼定義中間件的:

Middleware is software that's assembled into an app pipeline to handle requests and responses

3、安全驗證

接口驗證,是爲了安全性考慮,採用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; 
    }
}}
View Code

4、日誌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>
View Code

第三步,包裝以及擴展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 >  {
    
}
View Code

再定義包裝器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); 
        }
    }


}
View Code

最後定義擴展方法 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(); 
    }
}}
View Code

第四步,在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; }

5、.對net core中startup理解,見官方文檔

好了,關於.net core api也是第一次正式使用,就總結到這裏。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章