為ABP框架添加基礎集成服務
定義一個特性標記
這個標記用于標記一個枚舉代表的信息。
在 AbpBase.Domain.Shared
項目,創(chuàng)建 Attributes
目錄,然后創(chuàng)建一個 SchemeNameAttribute
類,其內(nèi)容如下:
/// <summary> /// 標記枚舉代表的信息 /// </summary> [AttributeUsage(AttributeTargets.Field)] public class SchemeNameAttribute : Attribute { public string Message { get; set; } public SchemeNameAttribute(string message) { Message = message; } }
全局統(tǒng)一消息格式
為了使得 Web 應用統(tǒng)一響應格式以及方便編寫 API 時有一個統(tǒng)一的標準,我們需要定義一個合適的模板。
在 AbpBase.Domain.Shared
創(chuàng)建一個Apis
目錄。
Http 狀態(tài)碼
為了適配各種 HTTP 請求的響應狀態(tài),我們定義一個識別狀態(tài)碼的枚舉。
在 Apis
目錄,創(chuàng)建一個 HttpStateCode.cs
文件,其內(nèi)容如下:
namespace AbpBase.Domain.Shared.Apis { /// <summary> /// 標準 HTTP 狀態(tài)碼 /// <para>文檔地址<inheritdoc cref="https://www.runoob.com/http/http-status-codes.html"/></para> /// </summary> public enum HttpStateCode { Status412PreconditionFailed = 412, Status413PayloadTooLarge = 413, Status413RequestEntityTooLarge = 413, Status414RequestUriTooLong = 414, Status414UriTooLong = 414, Status415UnsupportedMediaType = 415, Status416RangeNotSatisfiable = 416, Status416RequestedRangeNotSatisfiable = 416, Status417ExpectationFailed = 417, Status418ImATeapot = 418, Status419AuthenticationTimeout = 419, Status421MisdirectedRequest = 421, Status422UnprocessableEntity = 422, Status423Locked = 423, Status424FailedDependency = 424, Status426UpgradeRequired = 426, Status428PreconditionRequired = 428, Status429TooManyRequests = 429, Status431RequestHeaderFieldsTooLarge = 431, Status451UnavailableForLegalReasons = 451, Status500InternalServerError = 500, Status501NotImplemented = 501, Status502BadGateway = 502, Status503ServiceUnavailable = 503, Status504GatewayTimeout = 504, Status505HttpVersionNotsupported = 505, Status506VariantAlsoNegotiates = 506, Status507InsufficientStorage = 507, Status508LoopDetected = 508, Status411LengthRequired = 411, Status510NotExtended = 510, Status410Gone = 410, Status408RequestTimeout = 408, Status101SwitchingProtocols = 101, Status102Processing = 102, Status200OK = 200, Status201Created = 201, Status202Accepted = 202, Status203NonAuthoritative = 203, Status204NoContent = 204, Status205ResetContent = 205, Status206PartialContent = 206, Status207MultiStatus = 207, Status208AlreadyReported = 208, Status226IMUsed = 226, Status300MultipleChoices = 300, Status301MovedPermanently = 301, Status302Found = 302, Status303SeeOther = 303, Status304NotModified = 304, Status305UseProxy = 305, Status306SwitchProxy = 306, Status307TemporaryRedirect = 307, Status308PermanentRedirect = 308, Status400BadRequest = 400, Status401Unauthorized = 401, Status402PaymentRequired = 402, Status403Forbidden = 403, Status404NotFound = 404, Status405MethodNotAllowed = 405, Status406NotAcceptable = 406, Status407ProxyAuthenticationRequired = 407, Status409Conflict = 409, Status511NetworkAuthenticationRequired = 511 } }
常用的請求結果
在相同目錄,創(chuàng)建一個 CommonResponseType
枚舉,其內(nèi)容如下:
/// <summary> /// 常用的 API 響應信息 /// </summary> public enum CommonResponseType { [SchemeName("")] Default = 0, [SchemeName("請求成功")] RequstSuccess = 1, [SchemeName("請求失敗")] RequstFail = 2, [SchemeName("創(chuàng)建資源成功")] CreateSuccess = 4, [SchemeName("創(chuàng)建資源失敗")] CreateFail = 8, [SchemeName("更新資源成功")] UpdateSuccess = 16, [SchemeName("更新資源失敗")] UpdateFail = 32, [SchemeName("刪除資源成功")] DeleteSuccess = 64, [SchemeName("刪除資源失敗")] DeleteFail = 128, [SchemeName("請求的數(shù)據(jù)未能通過驗證")] BadRequest = 256, [SchemeName("服務器出現(xiàn)嚴重錯誤")] Status500InternalServerError = 512 }
響應模型
在 Apis
目錄,創(chuàng)建一個 ApiResponseModel`.cs
泛型類文件,其內(nèi)容如下:
namespace AbpBase.Domain.Shared.Apis { /// <summary> /// API 響應格式 /// <para>避免濫用,此類不能實例化,只能通過預定義的靜態(tài)方法生成</para> /// </summary> /// <typeparam name="TData"></typeparam> public abstract class ApiResponseModel<TData> { public HttpStateCode StatuCode { get; set; } public string Message { get; set; } public TData Data { get; set; } /// <summary> /// 私有類 /// </summary> /// <typeparam name="TResult"></typeparam> private class PrivateApiResponseModel<TResult> : ApiResponseModel<TResult> { } } }
StatuCode:用于說明此次響應的狀態(tài);
Message:響應的信息;
Data:響應的數(shù)據(jù);
可能你會覺得這樣很奇怪,先不要問,也不要猜,照著做,后面我會告訴你為什么這樣寫。
然后再創(chuàng)建一個類:
using AbpBase.Domain.Shared.Helpers; using System; namespace AbpBase.Domain.Shared.Apis { /// <summary> /// Web 響應格式 /// <para>避免濫用,此類不能實例化,只能通過預定義的靜態(tài)方法生成</para> /// </summary> public abstract class ApiResponseModel : ApiResponseModel<dynamic> { /// <summary> /// 根據(jù)枚舉創(chuàng)建響應格式 /// </summary> /// <typeparam name="TEnum"></typeparam> /// <param name="code"></param> /// <param name="enumType"></param> /// <returns></returns> public static ApiResponseModel Create<TEnum>(HttpStateCode code, TEnum enumType) where TEnum : Enum { return new PrivateApiResponseModel { StatuCode = code, Message = SchemeHelper.Get(enumType), }; } /// <summary> /// 創(chuàng)建標準的響應 /// </summary> /// <typeparam name="TEnum"></typeparam> /// <typeparam name="TData"></typeparam> /// <param name="code"></param> /// <param name="enumType"></param> /// <param name="Data"></param> /// <returns></returns> public static ApiResponseModel Create<TEnum>(HttpStateCode code, TEnum enumType, dynamic Data) { return new PrivateApiResponseModel { StatuCode = code, Message = SchemeHelper.Get(enumType), Data = Data }; } /// <summary> /// 請求成功 /// </summary> /// <param name="code"></param> /// <param name="Data"></param> /// <returns></returns> public static ApiResponseModel CreateSuccess(HttpStateCode code, dynamic Data) { return new PrivateApiResponseModel { StatuCode = code, Message = "Success", Data = Data }; } /// <summary> /// 私有類 /// </summary> private class PrivateApiResponseModel : ApiResponseModel { } } }
同時在項目中創(chuàng)建一個 Helpers 文件夾,再創(chuàng)建一個 SchemeHelper
類,其內(nèi)容如下:
using AbpBase.Domain.Shared.Attributes; using System; using System.Linq; using System.Reflection; namespace AbpBase.Domain.Shared.Helpers { /// <summary> /// 獲取各種枚舉代表的信息 /// </summary> public static class SchemeHelper { private static readonly PropertyInfo SchemeNameAttributeMessage = typeof(SchemeNameAttribute).GetProperty(nameof(SchemeNameAttribute.Message)); /// <summary> /// 獲取一個使用了 SchemeNameAttribute 特性的 Message 屬性值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="type"></param> /// <returns></returns> public static string Get<T>(T type) { return GetValue(type); } private static string GetValue<T>(T type) { var attr = typeof(T).GetField(Enum.GetName(type.GetType(), type)) .GetCustomAttributes() .FirstOrDefault(x => x.GetType() == typeof(SchemeNameAttribute)); if (attr == null) return string.Empty; var value = (string)SchemeNameAttributeMessage.GetValue(attr); return value; } } }
上面的類到底是干嘛的,你先不要問。
全局異常攔截器
在 AbpBase.Web
項目中,新建一個 Filters
文件夾,添加一個 WebGlobalExceptionFilter.cs
文件,其文件內(nèi)容如下:
using AbpBase.Domain.Shared.Apis; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Newtonsoft.Json; using System.Threading.Tasks; namespace ApbBase.HttpApi.Filters { /// <summary> /// Web 全局異常過濾器,處理 Web 中出現(xiàn)的、運行時未處理的異常 /// </summary> public class WebGlobalExceptionFilter : IAsyncExceptionFilter { public async Task OnExceptionAsync(ExceptionContext context) { if (!context.ExceptionHandled) { ApiResponseModel model = ApiResponseModel.Create(HttpStateCode.Status500InternalServerError, CommonResponseType.Status500InternalServerError); context.Result = new ContentResult { Content = JsonConvert.SerializeObject(model), StatusCode = StatusCodes.Status200OK, ContentType = "application/json; charset=utf-8" }; } context.ExceptionHandled = true; await Task.CompletedTask; } } }
然后 在 AbpBaseWebModule
模塊的 ConfigureServices
函數(shù)中,加上:
Configure<MvcOptions>(options => { options.Filters.Add(typeof(WebGlobalExceptionFilter)); });
這里我們還沒有將寫入日志,后面再增加這方面的功能。
先說明一下
前面我們定義了 ApiResponseModel 和其他一些特性還有枚舉,這里解釋一下原因。
ApiResponseModel 是抽象類
ApiResponseModel<T>
和 ApiResponseModel
是抽象類,是為了避免開發(fā)者使用時,直接這樣用:
ApiResponseModel mode = new ApiResponseModel { Code = 500, Message = "失敗", Data = xxx };
首先這個 Code 需要按照 HTTP 狀態(tài)的標準來填寫,我們使用 HttpStateCode 枚舉來標記,代表異常時,使用 Status500InternalServerError
來標識。
我非常討厭一個 Action 的一個返回,就寫一次消息的。
if(... ...) return xxxx("請求數(shù)據(jù)不能為空"); if(... ...) return xxxx("xxx 要大于 10"); ... ..
這樣每個地方一個消息說明,十分不統(tǒng)一,也不便于修改。
直接使用一個枚舉來代表消息,而不能直接寫出來,這樣就可以達到統(tǒng)一了。
使用抽象類,可以避免開發(fā)者直接 new 一個,強制要求一定的消息格式來響應。后面可以進行更多的嘗試,來體會我這樣設計的便利性。
跨域請求
這里我們將配置 Web 全局允許跨域請求。
在 AbpBaseWebModule
模塊中:
添加一個靜態(tài)變量
private const string AbpBaseWebCosr = "AllowSpecificOrigins";
創(chuàng)建一個配置函數(shù):
/// <summary> /// 配置跨域 /// </summary> /// <param name="context"></param> private void ConfigureCors(ServiceConfigurationContext context) { context.Services.AddCors(options => { options.AddPolicy(AbpBaseWebCosr, builder => builder.AllowAnyHeader() .AllowAnyMethod() .AllowAnyOrigin()); }); }
在 ConfigureServices
函數(shù)中添加:
// 跨域請求 ConfigureCors(context);
在 OnApplicationInitialization
中添加:
app.UseCors(AbpBaseWebCosr); // 位置在 app.UseRouting(); 后面
就這樣,允許全局跨域請求就完成了。
配置 API 服務
你可以使用以下模塊來配置一個 API 模塊服務:
Configure<AbpAspNetCoreMvcOptions>(options => { options .ConventionalControllers .Create(typeof(AbpBaseHttpApiModule).Assembly, opts => { opts.RootPath = "api/1.0"; }); });
我們在 AbpBase.HttpApi
中將其本身用于創(chuàng)建一個 API 服務,ABP 會將繼承了 AbpController
、ControllerBase
等的類識別為 API控制器。上面的代碼同時將其默認路由的前綴設置為 api/1.0
。
也可以不設置前綴:
Configure<AbpAspNetCoreMvcOptions>(options => { options.ConventionalControllers.Create(typeof(IoTCenterWebModule).Assembly); });
由于 API 模塊已經(jīng)在自己的 ConfigureServices
創(chuàng)建了 API 服務,因此可以不在 Web
模塊里面編寫這部分代碼。當然,也可以統(tǒng)一在 Web
中定義所有的 API 模塊。
統(tǒng)一 API 模型驗證消息
創(chuàng)建前
首先,如果我們這樣定義一個 Action:
public class TestModel { [Required] public int Id { get; set; } [MaxLength(11)] public int Iphone { get; set; } [Required] [MinLength(5)] public string Message { get; set; } } [HttpPost("/T2")] public string MyWebApi2([FromBody] TestModel model) { return "請求完成"; }
使用以下參數(shù)請求:
{ "Id": "1", "Iphone": 123456789001234567890, "Message": null }
會得到以下結果:
{ "errors": { "Iphone": [ "JSON integer 123456789001234567890 is too large or small for an Int32. Path 'Iphone', line 3, position 35." ] }, "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "|af964c79-41367b2145701111." }
這樣的信息閱讀起來十分不友好,前端對接也會有一定的麻煩。
這個時候我們可以統(tǒng)一模型驗證攔截器,定義一個友好的響應格式。
創(chuàng)建方式
在 AbpBase.Web
的項目 的 Filters
文件夾中,創(chuàng)建一個 InvalidModelStateFilter
文件,其文件內(nèi)容如下:
using AbpBase.Domain.Shared.Apis; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System.Linq; namespace AbpBase.Web.Filters { public static class InvalidModelStateFilter { /// <summary> /// 統(tǒng)一模型驗證 /// <para>控制器必須添加 [ApiController] 才能被此過濾器攔截</para> /// </summary> /// <param name="services"></param> public static void GlabalInvalidModelStateFilter(this IServiceCollection services) { services.Configure<ApiBehaviorOptions>(options => { options.InvalidModelStateResponseFactory = actionContext => { if (actionContext.ModelState.IsValid) return new BadRequestObjectResult(actionContext.ModelState); int count = actionContext.ModelState.Count; ValidationErrors[] errors = new ValidationErrors[count]; int i = 0; foreach (var item in actionContext.ModelState) { errors[i] = new ValidationErrors { Member = item.Key, Messages = item.Value.Errors?.Select(x => x.ErrorMessage).ToArray() }; i++; } // 響應消息 var result = ApiResponseModel.Create(HttpStateCode.Status400BadRequest, CommonResponseType.BadRequest, errors); var objectResult = new BadRequestObjectResult(result); objectResult.StatusCode = StatusCodes.Status400BadRequest; return objectResult; }; }); } /// <summary> /// 用于格式化實體驗證信息的模型 /// </summary> private class ValidationErrors { /// <summary> /// 驗證失敗的字段 /// </summary> public string Member { get; set; } /// <summary> /// 此字段有何種錯誤 /// </summary> public string[] Messages { get; set; } } } }
在 ConfigureServices
函數(shù)中,添加以下代碼:
// 全局 API 請求實體驗證失敗信息格式化 context.Services.GlabalInvalidModelStateFilter();
創(chuàng)建后
讓我們看看增加了統(tǒng)一模型驗證器后,同樣的請求返回的消息。
請求:
{ "Id": "1", "Iphone": 123456789001234567890, "Message": null }
返回:
{ "statuCode": 400, "message": "請求的數(shù)據(jù)未能通過驗證", "data": [ { "member": "Iphone", "messages": [ "JSON integer 123456789001234567890 is too large or small for an Int32. Path 'Iphone', line 3, position 35." ] } ] }
說明我們的統(tǒng)一模型驗證響應起到了作用。
但是有些驗證會直接報異常而不會流轉到上面的攔截器中,有些模型驗證特性用錯對象的話,他會報錯異常的。例如上面的 MaxLength ,已經(jīng)用錯了,MaxLength 是指定屬性中允許的數(shù)組或字符串數(shù)據(jù)的最大長度,不能用在 int 類型上。大家測試一下請求下面的 json,會發(fā)現(xiàn)報異常。
{ "Id": 1, "Iphone": 1234567900, "Message": "nullable" }
以下是一些 ASP.NET Core 內(nèi)置驗證特性,大家記得別用錯:
[CreditCard]
:驗證屬性是否具有信用卡格式。 需要 JQuery 驗證其他方法。[Compare]
:驗證模型中的兩個屬性是否匹配。[EmailAddress]
:驗證屬性是否具有電子郵件格式。[Phone]
:驗證屬性是否具有電話號碼格式。[Range]
:驗證屬性值是否在指定的范圍內(nèi)。[RegularExpression]
:驗證屬性值是否與指定的正則表達式匹配。[Required]
:驗證字段是否不為 null。 有關此屬性的行為的詳細信息[StringLength]
:驗證字符串屬性值是否不超過指定長度限制。[Url]
:驗證屬性是否具有 URL 格式。[Remote]
:通過在服務器上調用操作方法來驗證客戶端上的輸入。[MaxLength ]
MaxLength 是指定屬性中允許的數(shù)組或字符串數(shù)據(jù)的最大長度
參考:https://docs.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=netcore-3.1
本系列第二篇到此,接下來第三篇會繼續(xù)添加一些基礎服務。
補充:為什么需要統(tǒng)一格式
首先,你看一下這樣的代碼:
在每個 Action 中,都充滿了這種寫法,每個相同的驗證問題,在每個 Action 返回的文字都不一樣,沒有規(guī)范可言。一個人寫一個 return,就加上一下自己要表達的 文字
,一個項目下來,多少 return
?全是這種代碼,不堪入目。
通過統(tǒng)一模型驗證和統(tǒng)一消息返回格式,就可以避免這些情況。
源碼地址:https://github.com/whuanle/AbpBaseStruct
本教程結果代碼位置:https://github.com/whuanle/AbpBaseStruct/tree/master/src/2/AbpBase
到此這篇關于為ABP框架添加基礎集成服務的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
System.Diagnostics.Metrics .NET 6 全新指標API講解
本文詳細講解了.NET 6全新指標System.Diagnostics.Metrics,文中通過示例代碼介紹的非常詳細。對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-11-11使用vs2022在.net6中調試帶typescript的靜態(tài)頁面
這篇文章介紹了使用vs2022在.net6中調試帶typescript的靜態(tài)頁面,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-12-12.Net創(chuàng)建型設計模式之原型模式(Prototype)
這篇文章介紹了.Net設計模式之原型模式(Prototype),文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05.Net創(chuàng)建型設計模式之抽象工廠模式(Abstract?Factory)
這篇文章介紹了.Net設計模式之抽象工廠模式(Abstract?Factory),文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05