為ABP框架添加基礎(chǔ)集成服務(wù)
定義一個特性標(biāo)記
這個標(biāo)記用于標(biāo)記一個枚舉代表的信息。
在 AbpBase.Domain.Shared 項目,創(chuàng)建 Attributes目錄,然后創(chuàng)建一個 SchemeNameAttribute 類,其內(nèi)容如下:
/// <summary>
/// 標(biāo)記枚舉代表的信息
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class SchemeNameAttribute : Attribute
{
public string Message { get; set; }
public SchemeNameAttribute(string message)
{
Message = message;
}
}全局統(tǒng)一消息格式
為了使得 Web 應(yīng)用統(tǒng)一響應(yīng)格式以及方便編寫 API 時有一個統(tǒng)一的標(biāo)準(zhǔn),我們需要定義一個合適的模板。
在 AbpBase.Domain.Shared 創(chuàng)建一個Apis 目錄。
Http 狀態(tài)碼
為了適配各種 HTTP 請求的響應(yīng)狀態(tài),我們定義一個識別狀態(tài)碼的枚舉。
在 Apis 目錄,創(chuàng)建一個 HttpStateCode.cs 文件,其內(nèi)容如下:
namespace AbpBase.Domain.Shared.Apis
{
/// <summary>
/// 標(biāo)準(zhǔn) 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
}
}常用的請求結(jié)果
在相同目錄,創(chuàng)建一個 CommonResponseType 枚舉,其內(nèi)容如下:
/// <summary>
/// 常用的 API 響應(yīng)信息
/// </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("服務(wù)器出現(xiàn)嚴(yán)重錯誤")] Status500InternalServerError = 512
}響應(yīng)模型
在 Apis 目錄,創(chuàng)建一個 ApiResponseModel`.cs 泛型類文件,其內(nèi)容如下:
namespace AbpBase.Domain.Shared.Apis
{
/// <summary>
/// API 響應(yīng)格式
/// <para>避免濫用,此類不能實例化,只能通過預(yù)定義的靜態(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:用于說明此次響應(yīng)的狀態(tài);
Message:響應(yīng)的信息;
Data:響應(yīng)的數(shù)據(jù);
可能你會覺得這樣很奇怪,先不要問,也不要猜,照著做,后面我會告訴你為什么這樣寫。
然后再創(chuàng)建一個類:
using AbpBase.Domain.Shared.Helpers;
using System;
namespace AbpBase.Domain.Shared.Apis
{
/// <summary>
/// Web 響應(yīng)格式
/// <para>避免濫用,此類不能實例化,只能通過預(yù)定義的靜態(tài)方法生成</para>
/// </summary>
public abstract class ApiResponseModel : ApiResponseModel<dynamic>
{
/// <summary>
/// 根據(jù)枚舉創(chuàng)建響應(yī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)建標(biāo)準(zhǔn)的響應(yī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)的、運(yù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)的標(biāo)準(zhǔn)來填寫,我們使用 HttpStateCode 枚舉來標(biāo)記,代表異常時,使用 Status500InternalServerError 來標(biāo)識。
我非常討厭一個 Action 的一個返回,就寫一次消息的。
if(... ...)
return xxxx("請求數(shù)據(jù)不能為空");
if(... ...)
return xxxx("xxx 要大于 10");
... ..這樣每個地方一個消息說明,十分不統(tǒng)一,也不便于修改。
直接使用一個枚舉來代表消息,而不能直接寫出來,這樣就可以達(dá)到統(tǒng)一了。
使用抽象類,可以避免開發(fā)者直接 new 一個,強(qiáng)制要求一定的消息格式來響應(yīng)。后面可以進(jìn)行更多的嘗試,來體會我這樣設(shè)計的便利性。
跨域請求
這里我們將配置 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 服務(wù)
你可以使用以下模塊來配置一個 API 模塊服務(wù):
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options
.ConventionalControllers
.Create(typeof(AbpBaseHttpApiModule).Assembly, opts =>
{
opts.RootPath = "api/1.0";
});
});我們在 AbpBase.HttpApi 中將其本身用于創(chuàng)建一個 API 服務(wù),ABP 會將繼承了 AbpController 、ControllerBase 等的類識別為 API控制器。上面的代碼同時將其默認(rèn)路由的前綴設(shè)置為 api/1.0。
也可以不設(shè)置前綴:
Configure<AbpAspNetCoreMvcOptions>(options =>
{ options.ConventionalControllers.Create(typeof(IoTCenterWebModule).Assembly);
});由于 API 模塊已經(jīng)在自己的 ConfigureServices 創(chuàng)建了 API 服務(wù),因此可以不在 Web 模塊里面編寫這部分代碼。當(dāng)然,也可以統(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
}會得到以下結(jié)果:
{
"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)一模型驗證攔截器,定義一個友好的響應(yī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++;
}
// 響應(yīng)消息
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)一模型驗證響應(yīng)起到了作用。
但是有些驗證會直接報異常而不會流轉(zhuǎn)到上面的攔截器中,有些模型驗證特性用錯對象的話,他會報錯異常的。例如上面的 MaxLength ,已經(jīng)用錯了,MaxLength 是指定屬性中允許的數(shù)組或字符串?dāng)?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]:驗證屬性值是否與指定的正則表達(dá)式匹配。[Required]:驗證字段是否不為 null。 有關(guān)此屬性的行為的詳細(xì)信息[StringLength]:驗證字符串屬性值是否不超過指定長度限制。[Url]:驗證屬性是否具有 URL 格式。[Remote]:通過在服務(wù)器上調(diào)用操作方法來驗證客戶端上的輸入。[MaxLength ]MaxLength 是指定屬性中允許的數(shù)組或字符串?dāng)?shù)據(jù)的最大長度
參考:https://docs.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=netcore-3.1
本系列第二篇到此,接下來第三篇會繼續(xù)添加一些基礎(chǔ)服務(wù)。
補(bǔ)充:為什么需要統(tǒng)一格式
首先,你看一下這樣的代碼:

在每個 Action 中,都充滿了這種寫法,每個相同的驗證問題,在每個 Action 返回的文字都不一樣,沒有規(guī)范可言。一個人寫一個 return,就加上一下自己要表達(dá)的 文字,一個項目下來,多少 return ?全是這種代碼,不堪入目。
通過統(tǒng)一模型驗證和統(tǒng)一消息返回格式,就可以避免這些情況。
源碼地址:https://github.com/whuanle/AbpBaseStruct
本教程結(jié)果代碼位置:https://github.com/whuanle/AbpBaseStruct/tree/master/src/2/AbpBase
到此這篇關(guān)于為ABP框架添加基礎(chǔ)集成服務(wù)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
利用C#遠(yuǎn)程存取Access數(shù)據(jù)庫
目前,基于數(shù)據(jù)庫服務(wù)器的桌面管理程序和Web程序已經(jīng)有太多的應(yīng)用了,尤其是網(wǎng)絡(luò)的大量普及,孤立地數(shù)據(jù)庫管理系統(tǒng)無法勝任分布式管理應(yīng)用,但是面對基于Access數(shù)據(jù)庫的現(xiàn)有的桌面應(yīng)用我們也無法完全的摒棄。我們利用.Net 遠(yuǎn)程處理功能將連接和存取Access的行為封裝為一個遠(yuǎn)程對象,供網(wǎng)絡(luò)中其它客戶端通過調(diào)用該遠(yuǎn)程對象來存取實際的Access數(shù)據(jù)庫。我們以 C# 2005 為開發(fā)語言來實現(xiàn)上述功能。2008-04-04
asp.net HttpHandler操作Session的函數(shù)代碼
asp.net HttpHandler操作Session的函數(shù)代碼,需要的朋友可以參考下。2011-12-12
.Net創(chuàng)建型設(shè)計模式之建造者、生成器模式(Builder)
這篇文章介紹了.Net設(shè)計模式之建造者、生成器模式(Builder),文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05
ASP.NET中CheckBoxList復(fù)選框列表控件詳細(xì)使用方法
本文主要介紹CheckBoxList幾種常見的用法,并做出范例演示供大家參考,希望對學(xué)習(xí)asp.net的朋友有所幫助。2016-04-04
答你所問 .NET小常識 方便學(xué)習(xí)asp.net的朋友
這篇文章主要介紹了.NET小常識,對于想學(xué)習(xí).net的朋友有個參考,一些問答與基礎(chǔ)介紹,對于剛開始接觸.net的朋友很有幫助,下面大家一起了解下吧2012-05-05

