- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进.
授权、验证、异常处理和日志记录等横切关注点是每个系统的基本组成部分,它们对于确保系统的安全和良好运行至关重要.
实现横切关注点会导致应用中的很多地方出现重复代码。此外,一次授权或验证检查缺失可能会导致整个系统崩溃.
ABP框架的 主要目标之一 是使你的应用“不要重复自己”(DRY),ASP.NET Core已经为一些跨领域的问题提供了一个良好的基础设施,但ABP进一步实现了自动化,让使用更加容易.
本章探讨了ABP的基础设施:
认证和授权是安全中的两个主要概念。身份验证是识别当前用户的过程,授权用于允许或禁止用户执行应用的特定操作.
ASP.NET Core 系统本身提供了一种高级而灵活的认证和授权,ABP框架的认证授权与 ASP.NET Core 100%兼容,并进行了一定的扩展,它允许将权限授予角色和用户,它还允许在客户端进行权限检查.
最简单的场景,只允许登录的用户执行特定操作。 [Authorize] 属性不带任何参数,只检查当前用户是否已通过身份验证(登录).
请参见以下控制器(MVC):
public class ProductController : Controller {
public async Task GetListAsync(){}
[Authorize]
public async Task CreateAsync(ProductCreationDto input){}
[Authorize]
public async Task DeleteAsync(Guid id){}
}
在本例中, CreateAsync 和 DeleteAsync 操作仅允许通过身份验证的用户使用,假设匿名用户(尚未登录的用户)尝试执行这些操作, ASP.NET Core 向客户端返回授权错误响应。而 GetListAsync 方法对每个人都可用,甚至对匿名用户也是如此.
Authorize 可在 Controller 级别,用于授权内部的所有 Actions 操作。如果想允许匿名用户执行特定操作,可以配置 [AllowAnonymous] 属性。如以下代码块所示:
[Authorize]
public class ProductController : Controller {
[AllowAnonymous]
public async Task> GetListAsync(){}
public async Task CreateAsync(ProductCreationDto input) {}
public async Task DeleteAsync(Guid id){}
}
在这里,我在类 ProductController 的顶部使用了 [Authorize] 属性,在 GetListAsync 方法使用 [AllowAnonymous] 属性,这使得尚未登录的用户也可以访问 GetListAsync 方法.
虽然无参数的 [Authorize] 属性有一些适用场景,但是如果我们想要定义特定的权限(或策略),使得所有经过身份验证的用户具有不同的权限.
ABP框架对 ASP.NET Core 最重要的扩展是权限系统。权限是为特定用户或角色授予或禁止的策略,它与应用功能进行关联,并在用户尝试使用该功能时进行检查。如果当前用户已被授予权限,则该用户可以使用功能。否则,用户无法使用该功能.
ABP提供了在应用中定义、授予和检查权限的功能.
在使用权限之前需要先定义权限,首先创建从 PermissionDefinitionProvider 类继承的类。创建新的ABP解决方案时,会有一个空的权限定义提供程序类(在 Application.Contracts 项目中)。请参见以下示例:
public class ProductManagementPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup("ProductManagement");
myGroup.AddPermission("ProductManagement.ProductCreation");
myGroup.AddPermission"ProductManagement.ProductDeletion");
}
}
ABP框架在应用启动时调用 Define 方法。在本例中,我创建了一个名为 ProductManagement 的权限组,并在其中定义了两个权限,用于对用户界面(UI)上的权限进行分组,通常每个模块都要定义其权限组。组和权限名称是任意 string 字符串值(建议定义 const 常量字段).
这是一个最小的配置,您还可以将显示名称指定本地化字符串,并指定权限名称,以便在UI上以用户友好的方式显示它们。以下代码块使用本地化系统指定显示名称,同时定义组和权限:
public class ProductManagementPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup("ProductManagement",L("ProductManagement"));
myGroup.AddPermission("ProductManagement.ProductCreation",L("ProductCreation"));
myGroup.AddPermission("ProductManagement.ProductDeletion",L("ProductDeletion"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create(name);
}
}
我定义了一个 L 方法来简化本地化。(第8章“使用ABP的功能和服务”中将详细介绍本地化系统) 。
对于多租户应用程序,可以为 AddPermission 方法指定 multiTenancySide 参数,以定义仅限主机或仅限租户的权限。(第16章“实现多租户”中将详细介绍多租户).
定义完权限后,下一次应用启动后,该权限就可以使用了(在“权限管理”对话框中).
默认情况下,可以为用户或角色授予权限。假设您创建了一个经理角色(manager),并希望为该角色授予产品权限。程序启动后,我们导航到管理|身份管理|角色页面。然后创建经理角色(如果之前没有创建),请单击权限操作按钮,如图所示 。
单击权限按钮后将打开一个对话框,如下所示:
在图中,您可以在左侧看到权限组,而该组中的权限在右侧可用。权限组和我们定义的权限已经可以使用,无需进行任何额外操作.
具有经理角色的用户都继承该角色的权限。用户可以有多个角色,并且继承所有分配角色的 所有权限的联合 。您还可以在“用户管理”页面上直接向用户授予权限,以获得更大的灵活性.
我们已经定义了权限并将其分配给了角色。下一步是检查当前用户是否具有请求的权限.
[Authorize]
属性 您可以使用 [Authorize] 属性以声明的方式检查权限,也可以使用 IAuthorizationService 以编程方式检查权限.
我们可以重写上面的 ProductController 类,以授予产品创建和删除权限,如下所示:
public class ProductController : Controller
{
public async Task<List<ProductDto>> GetListAsync(){}
[Authorize("ProductManagement.ProductCreation")]
public async Task CreateAsync(ProductCreationDto input){}
[Authorize("ProductManagement.ProductDeletion")]
public async Task DeleteAsync(Guid id){}
}
[Authorize] 属性将字符串参数作为 策略名称 。ABP将权限定义为自动策略,您可以在需要指定策略名称的任何位置使用权限名称.
IAuthorizationService
声明式授权易于使用,建议尽可能使用。但是,当您想要有条件地检查权限或执行未授权案例的逻辑时,它是有限的。对于这种情况,可以注入并使用 IAuthorizationService ,如下例所示 。
public class ProductController : Controller
{
private readonly IAuthorizationService _authorizationService;
public ProductController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public async Task CreateAsync(ProductCreationDto input)
{
if (await _authorizationService.IsGrantedAsync("ProductManagement.ProductCreation"))
{
// TODO: Create the product
}
else
{
// TODO: Handle unauthorized case
}
}
}
IsGrantedAsync 方法检查给定的权限,如果当前用户(或用户的角色)已被授予权限,则返回 true 。如果您有自定义逻辑的权限要求,这将非常有用。但是,如果您只想检查权限并对未经授权的情况抛出异常, CheckAsync 方法更实用:
public async Task CreateAsync(ProductCreationDto input)
{
await _authorizationService.CheckAsync("ProductManagement.ProductCreation");
//TODO: Create the product
}
如果用户没有该操作的权限, CheckAsync 方法会引发 AbpAuthorizationException 异常,该异常由ABP框架处理,并向客户端返回HTTP响应。 IsGrantedAsync 和 CheckAsync 方法是ABP框架定义的有用的扩展方法.
[warning] 提示:从 AbpController 继承 。
建议从 AbpController 类而不是标准 Controller 类派生。因为它内部做了扩展,定义了一些有用的属性。比如,它有 AuthorizationService 属性(属于 IAuthorizationService 类型),您可以直接使用它,无需手动注入 IAuthorizationService 接口.
服务器上的权限检查是一种常见的方法。但是,您可能还需要检查客户端的权限.
ABP公开了一个标准HTTP API,其URL为 /api/abp/application-configuration ,返回包含本地化文本、设置、权限等的JSON数据。客户端可以使用该API来检查权限或在客户端执行本地化.
不同的客户端类型可能会提供不同的服务来检查权限。例如,在 MVC/Razor Pages 中,可以使用 abp.auth JavaScript API检查权限,如下所示:
abp.auth.isGranted('ProductManagement.ProductCreation');
这是一个全局函数,如果当前用户具有给定的权限,则返回 true 。否则,返回 false 。 在Blazor应用程序中,可以重用相同的 [Authorize] 属性和 IAuthorizationService 。 我们将在第4部分“用户界面和API开发”中详细介绍客户端权限检查.
在复杂的应用中,可能需要创建一些依赖于其父权限的子权限。当父权限被授予时,子权限才能正常工作.
角色管理权限具有一些子权限,如创建、编辑和删除。角色管理权限用于授权用户进入角色管理页面。如果用户无法进入该页面,那么授予角色创建权限就没有意义,因为不进入该页面几乎不可能创建新角色.
在权限定义类中, AddPermission 方法返回创建的权限,并将其分配给变量,变量使用 AddChild 方法创建子权限,如下代码块所示 。
public override void Define(IpermissionDefinitionContext context)
{
var myGroup = context.AddGroup("ProductManagement",L("ProductManagement"));
var parent = myGroup.AddPermission("MyParentPermission");
parent.AddChild("MyChildPermission");
}
在本例,我们创建了一个名为 MyParentPermission 的父权限,然后创建了另一个名为 MyChildPermission 的子权限。 子权限也可以具有子权限,比如我们可以把 parent.AddChild 的返回值赋予一个变量,然后调用它 AddChild 方法继续添加子权限.
通过开/关策略授权来定义和使用权限,显得简单而强大,然而,ASP.NET Core允许创建完整的自定义逻辑来定义策略.
ASP.NET Core基于策略的授权机制允许您授权应用中的某些操作,就像使用权限一样。但这一次,使用代码表示的自定义逻辑,实际上是ABP框架提供的一种简单且自动化的策略.
首先需要定义一个创建产品的权限需求(我们可以在应用层中定义这些类),稍后检查,代码段:
public class ProductCreationRequirement : IAuthorizationRequirement { }
ProductCreationRequirement 是一个空类,仅实现 IAuthorizationRequirement 接口。然后,为该需求定义一个授权处理程序 ProductCreationRequirementHandler ,如下所示:
public class ProductCreationRequirementHandler : AuthorizationHandler<ProductCreationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,ProductCreationRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "productManager"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
处理程序必须派生自 AuthorizationHandler<T> ,其中 T 是 ProductCreationRequirement 类型。在本例中,我只是检查了当前用户是否拥有 productManager 声明,这是我的自定义声明(声明是存储在身份验证票据中的值)。您可以构建自定义逻辑。如果允许当前用户拥有创建产品需求,你要做的就是调用 context.Succeed 上下文.
定义权限需求和处理程序后,需要在模块类的 ConfigureServices 方法中注册它们,如下所示:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AuthorizationOptions>(options =>
{
options.AddPolicy("ProductManagement.ProductCreation",
policy => policy.Requirements.Add(new ProductCreationRequirement()));
});
context.Services.AddSingleton<IAuthorizationHandler,ProductCreationRequirementHandler>();
}
我使用 AuthorizationOptions 定义了一个名为 ProductManagement.ProductCreation 的策略。然后,我将 ProductCreationRequirementHandler 注册为单例服务.
现在,假设我对 Controller 或 Action 使用 [Authorize("ProductManagement.ProductCreation")] 属性,或者使用 IAuthorizationService 检查策略,我的自定义授权处理程序就可以进行授权逻辑处理了.
一旦实现了自定义策略,就不能使用“权限管理”对话框向用户和角色授予权限,因为它不是一个简单的启用/禁用权限。然而,客户端策略检查仍然有效, 因为ABP很好地集成到ASP.NET Core的政策体系.
如果您只需要开/关方式的策略,ABP的权限系统很容易很强大,而自定义策略允许您使用自定义逻辑动态检查策略.
ASP.NET Core的授权系统比本文介绍的功能更多。基于资源的授权是一种允许您基于对象(如实体)控制策略的功能。例如,您可以控制删除特定产品的访问权限,而不是对所有产品拥有共同的删除权限。ABP与ASP.NET Core完全兼容。建议你查看ASP.NET Core的 文档 ,以了解有关授权的更多信息。 到目前为止,我们已经在MVC控制器上看到了 [Authorize] 属性的用法。但是,此属性和 IAuthorizationService 不限于控制器.
ASP.NET Core允许您对Razor页面、Razor组件和Web层中的一些地方使用 [Authorize] 和 IAuthorizationService .
ABP框架更进一步,允许对服务类和方法使用 [Authorize] 属性,而不依赖于Web层,即使在非Web应用程序中也是如此。因此,这种用法完全有效,如下所示:
public class ProductAppService : ApplicationService, IProductAppService
{
[Authorize("ProductManagement.ProductCreation")]
public Task CreateAsync(ProductCreationDto input)
{
// TODO
}
}
只有当前用户拥有 ProductManagement.ProductCreation (产品创建)权限/策略时,才能执行 CreateAsync 方法。实际上, [Authorize] 在任何注册为依赖注入(DI)的类中都是可用的。然而,由于授权被认为是应用层的一个功能,因此建议在应用层而不是领域层使用授权.
ABP使用使用拦截器的动态代理来完成方法调用的授权检查。如果通过类引用(而不是接口引用)注入服务,动态代理系统将使用动态继承技术。在这种情况下,必须使用 virtual 关键字定义方法,以允许动态代理系统覆盖它并执行授权检查.
验证可确保数据的安全性和一致性,并帮助应用程序正常运行。验证话题很广,有一些常见的验证类别:
关于 ASP.NET Core 的验证系统: ASP.NET Core 为验证提供了许多选项。本书重点介绍ABP框架添加的功能.
本节重点介绍服务端验证,以及验证过程和验证异常处理的方法。 让我们从最简单的数据注释特性验证开始:
public class ProductAppService : ApplicationService, IProductAppService
{
public Task CreateAsync(ProductCreationDto input)
{
// TODO
}
}
public class ProductCreationDto {
[Required]
[StringLength(100)]
public string Name { get; set; }
[Range(0, 999.99)]
public decimal Price { get; set; }
[Url]
public string PictureUrl { get; set; }
public bool IsDraft { get; set; }
}
ProductAppService 是应用服务,它的入参 ProductCreationDto 在ABP框架中自动验证,就像 ASP.NET Core MVC 框架一样.
ProductCreationDto 有三个验证属性,采用的是 ASP.NET Core 有内置的验证属性,此外 ASP.NET Core 还有其他内置验证属性:
[Required]
: 非空验证 [StringLength]
: 字符串长度大小验证 [Range]
: 范围验证 [Url]
: Url格式验证 [RegularExpression]
: 正则表达式(regex)验证 [EmailAddress]
: 电子邮件验证 ASP.NET Core 还允许您通过继承 ValidationAttribute 类并重写 IsValid 方法来自定义验证.
注释验证简单易用,推荐在DTO和模型上使用。但不适用自定义逻辑验证(会受到限制) 。
IValidatableObject
自定义验证 模型或DTO对象可以实现 IValidatableObject 接口,实现自定义代码块验证。请参见以下示例:
public class ProductCreationDto : IValidatableObject
{
...
[Url]
public string PictureUrl { get; set; }
public bool IsDraft { get; set; }
public IEnumerable Validate(ValidationContext context)
{
if (IsDraft == false && string.IsNullOrEmpty(PictureUrl))
{
yield return new ValidationResult("Picture must be provided to publish a product",new []{ nameof(PictureUrl) });
}
}
}
在本例中, ProductCreationDto 有一个自定义规则:如果 IsDraft 为 false ,并且图片路径为控,则提示需要上传图片。 如果需要从DI系统解析服务,可以使用 context.GetRequiredService 方法。例如,如果我们想本地化错误消息,我们可以重写 Validate 方法,如下代码块所示:
public IEnumerable Validate(ValidationContext context)
{
if (IsDraft == false && string.IsNullOrEmpty(PictureUrl))
{
var localizer = context.GetRequiredService<IStringLocalizer<ProductManagementResource>();
yield return new ValidationResult(localizer["PictureIsMissingErrorMessage"],new []{ nameof(PictureUrl) });
}
}
这里,我们从DI解析 IStringLocalizer<ProductManagementResource> 实例,并用它向客户端返回本地化错误消息。(我们将在第8章详细介绍本地化系统) 。
作为最佳实践,只在DTO/Model类中实现正式验证。然而,在应用或领域层服务中的业务逻辑验证,例如,检查数据库中是否已经存在给定的产品名称,则不要在 Validate 方法中验证.
如果用户输入无效,ABP框架会自动抛出 AbpValidationException 类型的异常。以下情况会引发异常:
Model.IsValid
。 在这些情况下,ABP不会调用您的服务方法(或Controller Action)。要想正确执行,必须确保输入不为 null 而且有效.
如果在服务内部执行其他验证,并希望引发与验证相关的异常,还可以引发 AbpValidationException ,如以下代码段所示:
public async Task CreateAsync(ProductCreationDto input) {
if (await HasExistingProductAsync(input.Name)){
throw new AbpValidationException(new List<ValidationResult>{new ValidationResult("Product name is already in use!", new[] {nameof(input.Name)})});
}
}
这里,我们假设 HasExistingProductAsync 在存在产品时返回 true 。我们通过指定验证错误来抛出 AbpValidationException 。 ValidationResult 表示验证错误;它的第一个构造函数参数是验证错误消息,第二个参数(可选)是DTO属性的名称.
一旦您或ABP验证系统抛出 AbpValidationException 异常,ABP异常处理系统将捕获并处理它.
可以使用 [DisableValidation] 在方法或类级别绕过ABP验证系统,如下例所示:
[DisableValidation]
public async Task CreateAsync(ProductCreationDto input) { }
在本例中, CreateAsync 方法用 [DisableValidation] 修饰,因此ABP不会对输入对象执行任何自动验证。 如果对类使用 [DisableValidation] ,则该类的所有方法的验证都将被禁用。在这种情况下,可以对某个方法使用 [EnableValidation] ,以便仅对该特定方法启用验证.
当禁用方法的自动验证时,仍然可以执行自定义验证逻辑并抛出 AbpValidationException ,如前一节所述.
除了对 Controller Actions 和 Razor Page handlers 执行验证,ABP还允许为应用中的任何类启用自动验证功能。您只需实现 IValidationEnabled 接口,如下例所示:
public class SomeServiceWithValidation : IValidationEnabled, ITransientDependency { ... }
然后,ABP使用本章介绍的验证系统自动验证所有输入.
ABP使用使用拦截器的动态代理来完成方法调用的验证。如果通过类引用(而不是接口引用)注入服务,动态代理系统将使用动态继承技术。在这种情况下,必须使用 virtual 关键字定义方法,以允许动态代理系统覆盖它并执行验证.
到目前为止, 我们已经介绍了与ASP.NET Core兼容的ABP验证系统。最后我们将介绍 FluentValidation 库集成,它允许您将验证逻辑与验证对象分离.
大多数情况,内置的验证系统就足够了,而且它很容易定义验证规则,我个人认为它没有任何问题,在DTO/model类中嵌入数据验证逻辑是完全可行的。然而,一些开发人员认为DTO/model类内部嵌入验证逻辑是一种糟糕的做法。在这种情况下,ABP提供了一个与流行的 FluentValidation 库的集成包,它将验证逻辑与DTO/model类分离,并提供了比标准注释验证方法更强大的功能.
要使用 FluentValidation 库,首先需要将其安装到项目中。可以使用ABP命令行界面(ABP CLI)的 add-package 命令为项目安装它,如下所示:
abp add-package Volo.Abp.FluentValidation
安装完软件包后,可以创建验证类并设置验证规则,如下代码块所示:
public class ProductCreationDtoValidator : AbstractValidator
{
public ProductCreationDtoValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
RuleFor(x => x.Price).ExclusiveBetween(0, 1000);
//...
}
}
具体请参阅 FluentValidation文档 ,了解如何定义更高级的验证规则:. 。
ABP自动发现验证类,并将它们集成到验证过程中。这意味着您甚至可以将标准验证逻辑与 FluentValidation 验证类混合使用.
一个系统最重要的质量指标之一是:它如何响应错误和异常情况。它应该积极处理错误,并向客户端返回正确的响应,并优雅地将问题告知用户.
在Web开发中,如果每个客户端请求异常都要处理一遍,对开发人员来说就显得重复而繁琐.
ABP框架完全自动化了程序中各方面的错误处理。大多数情况下,您无需在代码中编写任何 try-catch 语句,因为它会执行以下操作:
当ABP异常系统支持向客户端返回用户友好的消息或特定错误代码(业务).
UserFriendlyException
ABP提供了一些预定义的异常类来定制错误处理行为。其中之一是 UserFriendlyException 类.
首先,要了解 UserFriendlyException 使用场景,先要了解服务端API是什么异常。以下是自定义异常范例:
Public async Task ExampleAsync() { throw new Exception("my error message..."); }
假设浏览器客户端通过AJAX请求 ExampleAsync 方法。它将向用户显示以下错误消息:
如图所示,ABP显示了内部异常的标准消息,实际的错误消息会写入日志系统。对于此类一般性错误,服务器会向客户端返回HTTP 500状态代码,因为向用户显示原始异常消息是没有用的,甚至可能是危险的,因为它可能包含内部系统的一些敏感信息,例如数据库表名和字段.
但是,对于某些特定情况,您可能希望向用户返回一条用户友好、信息丰富的自定义错误消息。对于这种情况,可以使用 UserFriendlyException 异常,如下代码块所示:
public async Task ExampleAsync() { throw new UserFriendlyException("This message is available to the user!"); }
此时,ABP不会隐藏错误消息:
UserFriendlyException 不是唯一的,任何继承自 UserFriendlyException 或实现 IUserFriendlyException 接口的异常类都可返回用户友好的异常消息.
当您抛出用户友好的异常时,ABP会向客户端返回HTTP 403(禁止)状态码。(有关HTTP状态码映射,请参阅末尾的“控制HTTP状态码”部分) 。
[success] UserFriendlyException 是一种特殊类型的 业务异常 ,您可以直接向用户返回消息.
BusinessException
当请求的操作不满足系统业务些规则时,需要抛出异常。ABP中的业务异常是ABP框架识别和处理的特殊异常类型。 在最简单的情况下,可以直接使用 BusinessException 类抛出业务异常。请参见 EventHub 项目示例 。
public class EventRegistrationManager : DomainService
{
public async Task RegisterAsync(Event @event, AppUser user)
{
if (Clock.Now > @event.EndTime)
{
throw new BusinessException(EventHubErrorCodes.CantRegisterOrUnregisterForAPastEvent);
}
...
}
}
EventRegistrationManager 是一个领域服务,用于执行事件注册的业务规则。 RegisterAsync 是检查事件时间,如果是注册到过去的事件则引发业务异常.
BusinessException 的构造函数接受几个参数,所有参数都是可选的:
code
: 自定义错误码。客户端可以在处理异常时进行检查、跟踪错误类型。不同的异常,通常使用不同的错误码。错误码还支持本地化。 message
: 异常消息 details
: 详细消息 innerException
: 内部异常。如果缓存了一个业务异常,则可以传递到这里。 logLevel
: 异常日志级别,它是 LogLevel
类型的枚举,默认值是 LogLevel.Warning
如果使用 UserFriendlyException ,则必须自己对消息进行本地化,因为异常消息将要显示给用户。 如果抛出 BusinessException ,ABP不会向用户显示异常消息,除非 显式地将其本地化 。为此,它使用了 错误代码名称空间 .
假设您使用了 EventHub:CantRegisterOrUnregisterForAPastEvent 作为错误代码。这里, EventHub 通过使用冒号成为错误代码命名空间。我们必须将错误代码名称空间映射到本地化资源,这样ABP就可以知道这些错误消息使用哪个本地化资源:
Configure(options => { options.MapCodeNamespace("EventHub",typeof(EventHubResource)); });
在这个代码片段中,我们将 EventHub 错误代码命名空间映射到 EventHubResource 本地化资源。现在,您可以在本地化文件(包括名称空间)中将错误代码定义为 key ,如下所示:
{"culture": "en", "texts": { "EventHub:CantRegisterOrUnregisterForAPastEvent": "You can not register to or unregister from an event in the past, sorry!" } }
配置完成后,每当您抛出带有该错误代码的 BusinessException 异常时,ABP都会向用户显示本地化消息.
在某些情况下,您可能希望在错误消息中包含一些附加数据。请参阅以下代码片段:
throw new BusinessException(EventHubErrorCodes.OrganizationNameAlreadyExists).WithData("Name", name);
在这里,我们使用 WithData 扩展方法将组织名称包含在错误消息中。然后,我们可以定义本地化字符串,如以下代码段所示:
"EventHub:OrganizationNameAlreadyExists": "The organization {Name} already exists. Please use another name."
在本例中, {Name} 是组织名称的占位符。ABP会自动将其替换为给定的名称.
我们已经看到了如何抛出 BusinessException 异常。如果要创建自定义异常类呢?
还可以创建自定义异常类,而不是直接引发 BusinessException 异常。在这种情况下,您可以创建一个继承自 BusinessException 的新类,如下代码块所示 。
public class OrganizationNameAlreadyExistsException : BusinessException
{
public string Name { get; private set; }
public OrganizationNameAlreadyExistsException(string name) : base(EventHubErrorCodes.OrganizationNameAlreadyExists)
{
Name = name; WithData("Name", name);
}
}
在本例中, OrganizationNameAlreadyExistsException 是一个自定义业务异常类。它在构造函数中使用组织的名称。抛出这个异常非常简单:
throw new OrganizationNameAlreadyExistsException(name);
这种用法比使用自定义数据引发 BusinessException 异常 更简单 ,因为开发人员可能会忘记设置自定义数据。当您在多个位置抛出相同的异常时,它还可以 减少代码重复 .
如异常处理开头所述,ABP会自动记录所有异常:业务异常、授权和验证异常以警告级别( Warning 级别),其他错误的警告级别默认是 Error 级别。 我们可以实现 IHasLogLevel 接口,为异常类设置不同的日志级别:
public class MyException : Exception, IHasLogLevel {
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
//...
}
MyException 类实现了具有 Warning 级别的 IHasLogLevel 接口。如果抛出 MyException 异常,ABP支持写入警告日志.
还可以为异常写入其他日志,您可以实现 IExceptionWithSelfLogging 接口来编写其他日志,如下所示:
public class MyException : Exception, IExceptionWithSelfLogging {
public void Log(ILogger logger) {
//...log additional info
}
}
ABP尽最大努力为已知的异常类型返回正确的HTTP状态码,如下所示:
401
(unauthorized-未经授权) :用户尚未登录, 对应 AbpAuthorizationException
403
(forbidden-禁止) :用户已登录, 对应 AbpAuthorizationException
400
(bad request-错误请求) 对应 AbpValidationException
404
(not found-未找到) 对应 EntityNotFoundException
403
(forbidden-禁止) 对应 UserFriendlyException/BusinessException
501
(not implemented-未实现) 对应 NotImplementedException
500
(internal server error-服务器内部错误) 对应其他异常 如果要为异常返回自定义一个HTTP状态码,可以将错误代码映射到HTTP状态代码,如以下配置所示:
services.Configure(options => {options.Map(EventHubErrorCodes.OrganizationNameAlreadyExists,HttpStatusCode.Conflict); });
建议在解决方案的Web或HTTP API层中进行配置.
在本章中,我们探讨了业务应用中实现的横切关注点,包括授权,验证和异常处理。下一章将介绍一些ABP的基本功能,如自动审计日志和数据过滤.
最后此篇关于探索ABP基础架构的横切关注点的文章就讲到这里了,如果你想了解更多关于探索ABP基础架构的横切关注点的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
接上篇 通过一个示例形象地理解C# async await 非并行异步、并行异步、并行异步的并发量控制 前些天写了两篇关于C# async await异步的博客, 第一篇博客看的人多,点
前言 在 SwiftUI 中,我们可以通过添加不同的交互来使我们的应用程序更具交互性,这些交互可以响应我们的点击,点击和滑动。 今天,我们将回顾SwiftUI基本手势:
今年我一直在想,2022年我想做些什么,做哪方面的改变,这周末在家终于想到了! 2021 轻描淡写 年底就一直想对2021年写一篇总结的,起码不得写个千八百字,可是思来想去不知道怎么写,直到最后都没想
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
在 Eclipse 中使用 Java 进行开发时,它非常方便:您可以像自己一样附加源代码并探索核心 Java 代码。在 Visual Studio 中,我知道只有在调试时才能查看 .net 源代码(我
我正在尝试创建自己的字符串数据类型,谁能告诉我 typedef 和初始化做错了什么。 #include #include typedef char string[10]; int main(){
我期待开发一些东西来分析在服务器上运行的应用程序的 JVM 线程,要求如下: 访问在单独应用程序中运行的所有线程 打印线程栈 了解事件的详细信息 - 记录执行时间和方法详细信息(在特定线程中执行) 我
是否可以探索 Android 内部存储?我需要这个用于调试目的,以帮助我的开发工作。 最佳答案 您可以在模拟器上,或在 Root设备上。只是 adb shell 连接设备,然后从那里导航。 关于and
我有一个使用大量外键的 innoDB 表,但我们只想从中查找一些基本信息。 我做了一些研究,但还是迷路了。 如何判断我的主机是否有 Sphinx已经安装了吗?我没看到作为表格存储的选项方法(即 inn
我有一个创建列表的 GWT 代码(作为结果的网格),我将样式设置为 CSS 类,如 .test tr { height: 26px; } 现在...如果在渲染未完成或网格没有元素时我需要从代码
我需要使用 Javascript 和 HTML 为 Rally 敏捷工具开发一个 View 。我没有处理过在我作为开发人员的新职业中经常使用的网络语言。 我只是在探索他们的 API,但不知道如何探索他
我想了解 Hadoop 而不是一个黑盒子。我想探索 Hadoop 代码本身。我怎样才能不从主干下载 bundle ,我应该从哪里开始?任何帮助都会很有帮助谢谢舒佳特 最佳答案 Hadoop 代码在 S
想象一下这样的情况。您获得了一些遗留代码或获得了一些新框架。您需要尽快调查并了解如何使用此代码。没有机会向以前的开发人员寻求帮助。什么是最佳实践/方法/方式/步骤/工具(首选 .NET Framewo
我注意到我的 git 存储库中的某些 makefile 缺少变量定义的问题,我想搜索所有提交历史以查找我的变量 TESTDIR 在变更集中出现的位置 我该怎么做? 干杯 最佳答案 你可以使用 git
有什么方法可以探索 GO 包吗? 在 java 中,我使用“javap java.lang.String”命令来查看类内部的方法。像这样,有没有命令是他们用 GO 语言写的? 我在谷歌中搜索了相同的内
我注意到 docker 我需要了解容器内发生了什么或其中存在哪些文件。一个示例是从 docker 索引下载图像 - 您不知道图像包含什么,因此无法启动应用程序。 理想的情况是能够通过 ss
近日,华为 分析服务 6.9.0版本发布,正式上线 探索能力 。开发者可自由定义与配置分析模型,支持报告实时预览,数据洞察体验更加灵活与便捷. 新上线的探索能力中,有漏斗分析、事件归因、会话路径分析
我有一个 4 列的 excel 2010 电子表格。 A 列:我销售的产品的 UPC 代码列表。大约300行。 B 列:公式(稍后会详细介绍) C 列:另一个 UPC 代码列表。这些 UPC 代码大约
我有 3 个表格如下: CREATE TABLE USER_STATUS ("UID" varchar2(7), "STAT_ID" varchar2(11)) ; INSERT ALL IN
有什么方法可以探索 java 脚本对象(如 telerik 菜单或任何其他第 3 方对象)的属性和/或功能?我可以通过调试和破坏然后在 watch 中添加对象或在 VS 中使用智能感知来实现。 我使用
我是一名优秀的程序员,十分优秀!