[開源]WebApi 快速構建示例

来源:https://www.cnblogs.com/MeetYan/archive/2019/04/02/WebApi.html
-Advertisement-
Play Games

1. 實現代碼: " MasterChief.DotNet.ProjectTemplate.WebApi " 2. Demo Code: 3. Nuget : Install Package MasterChief.DotNet.ProjectTemplate.WebApi 4. 實現WebApi開 ...


  1. 項目代碼:MasterChief.DotNet.ProjectTemplate.WebApi
  2. 示例代碼:https://github.com/YanZhiwei/MasterChief.ProjectTemplate.WebApiSample
  3. Nuget : Install-Package MasterChief.DotNet.ProjectTemplate.WebApi
  4. 實現WebApi開發中諸如授權驗證,緩存,參數驗證,異常處理等,方便快速構建項目而無需過多關心技術細節;
  5. 歡迎Star,歡迎PR;

目錄

Created by gh-md-toc

授權

  1. 授權介面,通過該介面自定義授權實現,項目預設實現基於Jwt授權

    /// <summary>
    ///     WebApi 授權介面
    /// </summary>
    public interface IApiAuthorize
    {
        /// <summary>
        ///     檢查請求簽名合法性
        /// </summary>
        /// <param name="signature">加密簽名字元串</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <param name="appConfig">應用接入配置信息</param>
        /// <returns>CheckResult</returns>
        CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig);
    
    
        /// <summary>
        ///     創建合法用戶獲取訪問令牌介面數據
        /// </summary>
        /// <param name="identityUser">IdentityUser</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns>IdentityToken</returns>
        ApiResult<IdentityToken> CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig);
    }
  2. 基於Jwt授權實現

    /// <summary>
    ///     基於Jwt 授權實現
    /// </summary>
    public sealed class JwtApiAuthorize : IApiAuthorize
    {
        /// <summary>
        ///     檢查請求簽名合法性
        /// </summary>
        /// <param name="signature">加密簽名字元串</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <param name="appConfig">應用接入配置信息</param>
        /// <returns>CheckResult</returns>
        public CheckResult CheckRequestSignature(string signature, string timestamp, string nonce, AppConfig appConfig)
        {
            ValidateOperator.Begin()
                .NotNullOrEmpty(signature, "加密簽名字元串")
                .NotNullOrEmpty(timestamp, "時間戳")
                .NotNullOrEmpty(nonce, "隨機數")
                .NotNull(appConfig, "AppConfig");
            var appSecret = appConfig.AppSecret;
            var signatureExpired = appConfig.SignatureExpiredMinutes;
            string[] data = {appSecret, timestamp, nonce};
            Array.Sort(data);
            var signatureText = string.Join("", data);
            signatureText = Md5Encryptor.Encrypt(signatureText);
    
            if (!signature.CompareIgnoreCase(signatureText) && CheckHelper.IsNumber(timestamp))
                return CheckResult.Success();
            var timestampMillis =
                UnixEpochHelper.DateTimeFromUnixTimestampMillis(timestamp.ToDoubleOrDefault());
            var minutes = DateTime.UtcNow.Subtract(timestampMillis).TotalMinutes;
    
            return minutes > signatureExpired ? CheckResult.Fail("簽名時間戳失效") : CheckResult.Success();
        }
    
        /// <summary>
        ///     創建合法用戶獲取訪問令牌介面數據
        /// </summary>
        /// <param name="identityUser">IdentityUser</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns>IdentityToken</returns>
        public ApiResult<IdentityToken> CreateIdentityToken(IdentityUser identityUser, AppConfig appConfig)
        {
            ValidateOperator.Begin()
                .NotNull(identityUser, "IdentityUser")
                .NotNull(appConfig, "AppConfig");
            var payload = new Dictionary<string, object>
            {
                {"iss", identityUser.UserId},
                {"iat", UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds}
            };
            var identityToken = new IdentityToken
            {
                AccessToken = CreateIdentityToken(appConfig.SharedKey, payload),
                ExpiresIn = appConfig.TokenExpiredDay * 24 * 3600
            };
            return ApiResult<IdentityToken>.Success(identityToken);
        }
    
        /// <summary>
        ///     創建Token
        /// </summary>
        /// <param name="secret">密鑰</param>
        /// <param name="payload">負載數據</param>
        /// <returns>Token令牌</returns>
        public static string CreateIdentityToken(string secret, Dictionary<string, object> payload)
        {
            ValidateOperator.Begin().NotNull(payload, "負載數據").NotNullOrEmpty(secret, "密鑰");
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
            return encoder.Encode(payload, secret);
        }
    }

鑒權

  1. Token令牌鑒定介面,通過該介面可以自定義擴展實現方式,項目預設實現基於Jwt鑒權

    /// <summary>
    ///     webApi 驗證系統基本介面
    /// </summary>
    public interface IApiAuthenticate
    {
        #region Methods
    
        /// <summary>
        ///     驗證Token令牌是否合法
        /// </summary>
        /// <param name="token">令牌</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns>CheckResult</returns>
        ApiResult<string> CheckIdentityToken(string token, AppConfig appConfig);
    
        #endregion Methods
    }
  2. 基於Jwt鑒權實現

    /// <summary>
    ///     基於Jwt 授權驗證實現
    /// </summary>
    public sealed class JwtApiAuthenticate : IApiAuthenticate
    {
        /// <summary>
        ///     檢查Token是否合法
        /// </summary>
        /// <param name="token">用戶令牌</param>
        /// <param name="appConfig">AppConfig</param>
        /// <returns></returns>
        public ApiResult<string> CheckIdentityToken(string token, AppConfig appConfig)
        {
            ValidateOperator.Begin()
                .NotNullOrEmpty(token, "Token")
                .NotNull(appConfig, "AppConfig");
            try
            {
                var tokenText = ParseTokens(token, appConfig.SharedKey);
                if (string.IsNullOrEmpty(tokenText))
                    return ApiResult<string>.Fail("用戶令牌Token為空");
    
                dynamic root = JObject.Parse(tokenText);
                string userid = root.iss;
                double iat = root.iat;
                var validTokenExpired =
                    new TimeSpan((int) (UnixEpochHelper.GetCurrentUnixTimestamp().TotalSeconds - iat))
                        .TotalDays > appConfig.TokenExpiredDay;
                return validTokenExpired
                    ? ApiResult<string>.Fail($"用戶ID{userid}令牌失效")
                    : ApiResult<string>.Success(userid);
            }
            catch (FormatException)
            {
                return ApiResult<string>.Fail("用戶令牌非法");
            }
            catch (SignatureVerificationException)
            {
                return ApiResult<string>.Fail("用戶令牌非法");
            }
        }
    
        /// <summary>
        ///     轉換Token
        /// </summary>
        /// <param name="token">令牌</param>
        /// <param name="secret">密鑰</param>
        /// <returns>Token以及負載數據</returns>
        private string ParseTokens(string token, string secret)
        {
            ValidateOperator.Begin()
                .NotNullOrEmpty(token, "令牌")
                .NotNullOrEmpty(secret, "密鑰");
    
            IJsonSerializer serializer = new JsonNetSerializer();
            IDateTimeProvider provider = new UtcDateTimeProvider();
            IJwtValidator validator = new JwtValidator(serializer, provider);
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
            return decoder.Decode(token, secret, true);
        }
    }

授權與鑒權使用

  1. 授權使用,通過Controller構造函數方式,代碼如下

    /// <summary>
    ///     Api授權
    /// </summary>
    public abstract class AuthorizeController : ApiBaseController
    {
        #region Constructors
    
        /// <summary>
        ///     構造函數
        /// </summary>
        /// <param name="apiAuthorize">IApiAuthorize</param>
        /// <param name="appCfgService">IAppConfigService</param>
        protected AuthorizeController(IApiAuthorize apiAuthorize, IAppConfigService appCfgService)
        {
            ValidateOperator.Begin()
                .NotNull(apiAuthorize, "IApiAuthorize")
                .NotNull(appCfgService, "IAppConfigService");
            ApiAuthorize = apiAuthorize;
            AppCfgService = appCfgService;
        }
    
        #endregion Constructors
    
        #region Fields
    
        /// <summary>
        ///     授權介面
        /// </summary>
        protected readonly IApiAuthorize ApiAuthorize;
    
        /// <summary>
        ///     請求通道配置信息,可以從文件或者資料庫獲取
        /// </summary>
        protected readonly IAppConfigService AppCfgService;
    
        #endregion Fields
    
        #region Methods
    
        /// <summary>
        ///     創建合法用戶的Token
        /// </summary>
        /// <param name="userId">用戶Id</param>
        /// <param name="passWord">用戶密碼</param>
        /// <param name="signature">加密簽名字元串</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <param name="appid">應用接入ID</param>
        /// <returns>OperatedResult</returns>
        protected virtual ApiResult<IdentityToken> CreateIdentityToken(string userId, string passWord,
            string signature, string timestamp,
            string nonce, Guid appid)
        {
            #region  參數檢查
    
            var checkResult = CheckRequest(userId, passWord, signature, timestamp, nonce, appid);
    
            if (!checkResult.State)
                return ApiResult<IdentityToken>.Fail(checkResult.Message);
    
            #endregion
    
            #region 用戶鑒權
    
            var getIdentityUser = GetIdentityUser(userId, passWord);
    
            if (!getIdentityUser.State) return ApiResult<IdentityToken>.Fail(getIdentityUser.Message);
    
            #endregion
    
            #region 請求通道檢查
    
            var getAppConfig = AppCfgService.Get(appid);
    
            if (!getAppConfig.State) return ApiResult<IdentityToken>.Fail(getAppConfig.Message);
            var appConfig = getAppConfig.Data;
    
            #endregion
    
            #region 檢查請求簽名檢查
    
            var checkSignatureResult = ApiAuthorize.CheckRequestSignature(signature, timestamp, nonce, appConfig);
            if (!checkSignatureResult.State) return ApiResult<IdentityToken>.Fail(checkSignatureResult.Message);
    
            #endregion
    
            #region 生成基於Jwt Token
    
            var getTokenResult = ApiAuthorize.CreateIdentityToken(getIdentityUser.Data, getAppConfig.Data);
            if (!getTokenResult.State) return ApiResult<IdentityToken>.Fail(getTokenResult.Message);
    
            return ApiResult<IdentityToken>.Success(getTokenResult.Data);
    
            #endregion
        }
    
    
        /// <summary>
        ///     檢查用戶的合法性
        /// </summary>
        /// <param name="userId">用戶Id</param>
        /// <param name="passWord">用戶密碼</param>
        /// <returns>UserInfo</returns>
        protected abstract CheckResult<IdentityUser> GetIdentityUser(string userId, string passWord);
    
        private CheckResult CheckRequest(string userId, string passWord, string signature, string timestamp,
            string nonce, Guid appid)
        {
            if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(passWord))
                return CheckResult.Fail("用戶名或密碼為空");
    
            if (string.IsNullOrEmpty(signature))
                return CheckResult.Fail("請求簽名為空");
    
            if (string.IsNullOrEmpty(timestamp))
                return CheckResult.Fail("時間戳為空");
    
            if (string.IsNullOrEmpty(nonce))
                return CheckResult.Fail("隨機數為空");
    
            if (appid == Guid.Empty)
                return CheckResult.Fail("應用接入ID非法");
    
            return CheckResult.Success();
        }
    
        #endregion Methods
    }
  2. 鑒權使用,通過AuthorizationFilterAttribute形式,標註請求是否需要鑒權

    /// <summary>
     ///     WebApi 授權驗證實現
     /// </summary>
     [AttributeUsage(AttributeTargets.Method)]
     public abstract class AuthenticateAttribute : AuthorizationFilterAttribute
     {
         #region Constructors
    
         /// <summary>
         ///     構造函數
         /// </summary>
         /// <param name="apiAuthenticate">IApiAuthenticate</param>
         /// <param name="appCfgService">appCfgService</param>
         protected AuthenticateAttribute(IApiAuthenticate apiAuthenticate, IAppConfigService appCfgService)
         {
             ValidateOperator.Begin()
                 .NotNull(apiAuthenticate, "IApiAuthenticate")
                 .NotNull(appCfgService, "IAppConfigService");
             ApiAuthenticate = apiAuthenticate;
             AppCfgService = appCfgService;
         }
    
         #endregion Constructors
    
         #region Fields
    
         /// <summary>
         ///     授權驗證介面
         /// </summary>
         protected readonly IApiAuthenticate ApiAuthenticate;
    
         /// <summary>
         ///     請求通道配置信息,可以從文件或者資料庫獲取
         /// </summary>
         protected readonly IAppConfigService AppCfgService;
    
         #endregion Fields
    
         #region Methods
    
         /// <summary>
         ///     驗證Token令牌是否合法
         /// </summary>
         /// <param name="token">令牌</param>
         /// <param name="appid">應用ID</param>
         /// <returns>CheckResult</returns>
         protected virtual ApiResult<string> CheckIdentityToken(string token, Guid appid)
         {
             #region 請求參數檢查
    
             var checkResult = CheckRequest(token, appid);
    
             if (!checkResult.State)
                 return ApiResult<string>.Fail(checkResult.Message);
    
             #endregion
    
             #region 請求通道檢查
    
             var getAppConfig = AppCfgService.Get(appid);
    
             if (!getAppConfig.State) return ApiResult<string>.Fail(getAppConfig.Message);
             var appConfig = getAppConfig.Data;
    
             #endregion
    
             return ApiAuthenticate.CheckIdentityToken(token, appConfig);
         }
    
         private CheckResult CheckRequest(string token, Guid appid)
         {
             if (string.IsNullOrEmpty(token))
                 return CheckResult.Fail("用戶令牌為空");
             return Guid.Empty == appid ? CheckResult.Fail("應用ID非法") : CheckResult.Success();
         }
    
         #endregion Methods
     }

基於請求緩存處理

  1. 通過ICacheProvider介面,可以擴展緩存數據方式;

  2. 通過配置DependsOnIdentity參數,可以配置是否依賴Token令牌進行緩存;

  3. 通過配置CacheMinutes參數,可以指定具體介面緩存時間,當設置0的時候不啟用緩存;

  4. 通過實現ControllerCacheAttribute,可以在不同項目快速達到介面緩存功能;

    public class RequestCacheAttribute : ControllerCacheAttribute
    {
        public RequestCacheAttribute(int cacheMinutes) : this(cacheMinutes, true, new LocalCacheProvider())
        {
        }
    
        public RequestCacheAttribute(int cacheMinutes, bool dependsOnIdentity, ICacheProvider cacheProvider) : base(
            cacheMinutes, dependsOnIdentity, cacheProvider)
        {
        }
    
        protected override bool CheckedResponseAvailable(HttpActionContext context, string responseText)
        {
            return !string.IsNullOrEmpty(responseText) && context != null;
        }
    
        protected override string GetIdentityToken(HttpActionContext actionContext)
        {
            return actionContext.Request.GetUriOrHeaderValue("Access_token").ToStringOrDefault(string.Empty);
        }
    }

異常處理

  1. 通過實現ControllerExceptionAttribute,可以輕鬆簡單構建介面請求時候異常發生,並通過HttpRequestRaw requestRaw參數,可以獲取非常詳盡的請求信息;

    public sealed class ExceptionLogAttribute : ControllerExceptionAttribute
    {
        public override void OnActionExceptioning(HttpActionExecutedContext actionExecutedContext, string actionName,
            HttpStatusCode statusCode,
            HttpRequestRaw requestRaw)
        {
            var response = new HttpResponseMessage
            {
                Content = new StringContent("發生故障,請稍後重試!"),
                StatusCode = statusCode
            };
            actionExecutedContext.Response = response;
        }
    }

參數驗證

  1. 通過實現ValidateModelAttribute,以及DataAnnotations快速構建請求參數驗證

  2. 請求參數只需要DataAnnotations標註即可;

    public sealed class ArticleRequest
    {
        [Required(ErrorMessage = "缺少文章ID")]
        public int Id
        {
            get;
            set;
        }
    
    }
  3. 項目實現ValidateModelAttribute,可以自定義構建參數處理方式

    /// <summary>
    /// 請求參數
    /// </summary>
    public sealed class ValidateRequestAttribute : ValidateModelAttribute
    {
        public override void OnParameterIsNulling(HttpActionContext actionContext)
        {
            actionContext.Response =
                actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult<string>.Fail("請求參數非法。"));
        }
    
        public override void OnParameterInvaliding(HttpActionContext actionContext, ValidationFailedResult result)
        {
            var message = result.Data.FirstOrDefault()?.Message;
            actionContext.Response =
                actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, OperatedResult<string>.Fail(message));
        }
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文地址:http://www.entityframeworktutorial.net/code-first/configure-classes-in-code-first.aspx 在前面的章節中,我們學習了Code-First預設的約定。Code-First使用預設的約定,從你的領域類中生成概念 ...
  • 以SqlServer為例子說明ServiceStack實現多租戶,在SqlServer中創建4個Database:TMaster、T1,T2,T3,為了安全起見 每個Database不用sa賬號,而是用獨立的資料庫的賬號和密碼,為了方便演示這密碼設置成一樣 租戶TMaster Database:TM ...
  • 在日常的資料庫運維過程中,有時候需要將Select查詢出來的數據集寫入到另一個數據表中,其中一種方式是通過存儲過程迴圈寫入數據,另一種簡便的方式是直接使用Insert Into語句後面跟上Select結果查詢語句即可將數據寫入。通過Insert Into和Select語句連用可以很快的將一個表的數據 ...
  • 在Sqlserver資料庫中,備份數據的方式有很多種,可以使用整個資料庫備份,也可使用導出包含數據和架構的腳本文件的方式來進行單表或多表數據的備份,其實還有一種Select Into的方式可以快速備份單張數據表,Select Into將自動創建備份存儲使用的新表。 Sqlserver中使用Selec ...
  • 測試代碼: 改為給Control添加擴展方法。 上述代碼,起作用的根本原因在於Component 內有 受保護的欄位Events ,該欄位記錄了附加到該控制項的所有的事件處理函數。 而wpf 控制項中沒有該成員,所以無法使用該方法判斷。但是可以使用UIElement類提供的 AddHandler 和 R ...
  • 筆記:xml序列化 /// <summary> /// xml序列化 /// </summary> /// <param name="root"></param> /// <param name="dic"></param> /// <returns></returns> private stati ...
  • 【前言】 上一篇完成了Asp.Net Core 2.2全新的管道處理模型解析,“俄羅斯套娃”式的委托嵌套和傳遞,組建了擴展性無與倫比的管道模型!與此同時,委托嵌套過於複雜,使用起來並不友好,然後多種擴展使用方式出現了,一起來看看吧! 【Run終結式擴展】 IApplicationBuilder里的U ...
  • 【前言】 上一篇完成了Asp.Net Core 2.2項目的一個最簡單功能的添加,從控制器-視圖-實體輕鬆交互了一下,感覺跟之前的MVC沒啥差別!但這些都是在組件封裝的基礎上完成的,在Core裡面,其實更多的東西是靠訂製的,而不是實現固定的,包括管道處理模型!本文將跟大家探討一下,Asp.Net C ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...