ABP vNext-localisation et gestion des exceptions

Prenez l'habitude d'écrire ensemble ! C'est le 10ème jour de ma participation au "Nuggets Daily New Plan·April Update Challenge", cliquez pour voir les détails de l'événement

1. Localisation

1. Téléchargez le package de localisation

Le système de localisation d'ABP est Microsoft.Extensions.Localizationparfaitement intégré, il ajoute quelques fonctions et améliorations utiles, et est plus facile à appliquer dans le développement réel. Volo.Abp.Localization est le package de base du système de localisation. Utilisez la console du gestionnaire de packages pour installer dans le projet, puis ajoutez la dépendance AbpLocalizationModule au module :

Install-Package Volo.Abp.Localization
复制代码
using Volo.Abp.Modularity;
using Volo.Abp.Localization;
​
namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpLocalizationModule))]
    public class MyModule : AbpModule
    {
        //...
    }
}
复制代码

2. Créer des ressources localisées

Les ressources de localisation sont utilisées pour regrouper les chaînes de localisation associées et les séparer des autres chaînes de localisation de l'application. Généralement, un module définit ses propres ressources de localisation. Une ressource de localisation est une classe normale.

public class TestResource
{
}
复制代码

Il doit ensuite être AbpLocalizationOptionsajouté qui suit :

[DependsOn(typeof(AbpLocalizationModule))]
public class MyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            // "YourRootNameSpace" 是项目的根命名空间名字. 如果你的项目的根命名空间名字为空,则无需传递此参数.
            options.FileSets.AddEmbedded<MyModule>("YourRootNameSpace");
        });
​
        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Add<TestResource>("en")
                .AddVirtualJson("/Localization/Resources/Test");
        });
    }
}
复制代码
  • Ajout d'une nouvelle ressource de localisation, utilisant "en" (anglais) comme localisation par défaut.
  • Stockez les chaînes localisées dans des fichiers JSON.
  • Intégrez des fichiers JSON dans des assemblages à l'aide d'un système de fichiers virtuel.

Le fichier JSON se trouve dans le dossier projet "/Localization/Resources/Test", le contenu du fichier de localisation :

{
  "culture": "en",
  "texts": {
    "HelloWorld": "Hello World!"
  }
}
复制代码
  • Chaque fichier de localisation doit définir un culturecode (de culture) (par exemple "en" ou "en-US").
  • textsPartie d'une collection clé-valeur contenant uniquement des chaînes localisées (les clés peuvent également contenir des espaces).

Peut être AbpLocalizationOptions.DefaultResourceTypedéfini sur le type de ressource, utilisé lorsqu'aucune ressource localisée n'est spécifiée :

Configure<AbpLocalizationOptions>(options =>
{
    options.DefaultResourceType = typeof(TestResource);
});
复制代码

3. Noms courts des ressources

Les ressources localisées peuvent également être utilisées côté client (JavaScript). La définition d'un nom court pour la ressource localisée facilite la localisation du texte. Par exemple :

[LocalizationResourceName("Test")]
public class TestResource
{
}
复制代码

Les ressources peuvent hériter d'autres ressources, ce qui permet de réutiliser des chaînes localisées existantes sans référencer des ressources existantes.

[InheritResource(typeof(AbpValidationResource))]
public class TestResource
{
}
复制代码

Il peut également être AbpLocalizationOptionsconfiguré :

services.Configure<AbpLocalizationOptions>(options =>
{
    options.Resources
        .Add<TestResource>("en") //Define the resource by "en" default culture
        .AddVirtualJson("/Localization/Resources/Test") //Add strings from virtual json files
        .AddBaseTypes(typeof(AbpValidationResource)); //Inherit from an existing resource
});
复制代码
  • Une ressource peut hériter de plusieurs ressources.
  • 如果新的本地化资源定义了相同的本地化字符串, 那么它会覆盖该字符串

4.扩展现有资源

继承资源可以创建新的资源, 无需修改现有的资源. 但是在某些情况下, 你可能不想创建新资源,而是直接扩展现有资源.

services.Configure<AbpLocalizationOptions>(options =>
{
    options.Resources
        .Get<TestResource>()
        .AddVirtualJson("/Localization/Resources/Test/Extensions");
});
复制代码
  • 如果扩展文件定义了相同的本地化字符串, 那么它会覆盖该字符串.
public class MyService
{
    private readonly IStringLocalizer<TestResource> _localizer;
​
    public MyService(IStringLocalizer<TestResource> localizer)
    {
        _localizer = localizer;
    }
​
    public void Foo()
    {
        var str = _localizer["HelloWorld"];
    }
}
复制代码

格式参数可以在本地化Key参数后传递,如果你的消息是 Hello {0}, welcome!,可以将 {0} 传递给localizer,例如: _localizer["HelloMessage", "John"].

5.在Razor视图/Page中简单的用法

@inject IHtmlLocalizer<TestResource> Localizer
​
<h1>@Localizer["HelloWorld"]</h1>
复制代码

ABP提供了JavaScript服务, 可以在客户端使用相同的本地化文本.

abp.localization.getResource 函数用于获取本地化资源:

var testResource = abp.localization.getResource('Test');

var str = testResource('HelloWorld');
复制代码

abp.localization.localize 函数用于获取本地化文本,你可以传递本地化Key和资源名称:

var str = abp.localization.localize('HelloWorld', 'Test');
复制代码

HelloWorld 是本地化文本的Key, Test 是本地化资源的名称.

如果未指定本地化资源名称,它使用 AbpLocalizationOptions 中定义的默认本地化资源(参见上面的默认资源部分). 例:

var str = abp.localization.localize('HelloWorld'); //uses the default resource
复制代码

如果本地化字符串包含参数, 例如 Hello {0}, welcome!. 你可以将参数传递给本地化方法. 例:

var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
复制代码

二、异常处理

ABP提供了用于处理Web应用程序异常的标准模型.

  • 自动 处理所有异常 .如果是API/AJAX请求,会向客户端返回一个标准格式化后的错误消息 .
  • 自动隐藏 内部详细错误 并返回标准错误消息.
  • 为异常消息的 本地化 提供一种可配置的方式.
  • 自动为标准异常设置 HTTP状态代码 ,并提供可配置选项,以映射自定义异常.

当满足下面任意一个条件时,AbpExceptionFilter 会处理此异常:

  • controller action方法返回类型是object result(而不是view result)并有异常抛出时.
  • 当一个请求为AJAX(Http请求头中X-Requested-WithXMLHttpRequest)时.
  • 当客户端接受的返回类型为application/json(Http请求头中acceptapplication/json)时.

如果异常被处理过,则会自动记录日志并将格式化的JSON消息返回给客户端.

1.错误消息格式

每个错误消息都是RemoteServiceErrorResponse 类的实例.最简单的错误JSON只有一个 Message 属性

{
  "error": {
    "message": "This topic is locked and can not add a new message"
  }
}
复制代码

其它可选字段可以根据已发生的异常来填充.错误 代码(code) 是异常信息中一个有唯一值并可选的字符串值.抛出的异常应实现IHasErrorCode 接口来填充该字段.示例JSON如下:

{
  "error": {
    "code": "App:010042",
    "message": "This topic is locked and can not add a new message"
  }
}
复制代码

2.错误详细信息

错误的 详细信息(Details) 是可选属性.抛出的异常应实现IHasErrorDetails 接口来填充该字段.

{
  "error": {
    "code": "App:010042",
    "message": "This topic is locked and can not add a new message",
    "details": "A more detailed info about the error..."
  }
}
复制代码

当抛出的异常实现IHasValidationErrors 接口时,validationErrors是一个可被填充的标准字段:

{
  "error": {
    "code": "App:010046",
    "message": "Your request is not valid, please correct and try again!",
    "validationErrors": [{
      "message": "Username should be minimum length of 3.",
      "members": ["userName"]
    },
    {
      "message": "Password is required",
      "members": ["password"]
    }]
  }
}
复制代码

AbpValidationException已经实现了IHasValidationErrors接口,当请求输入无效时,框架会自动抛出此错误. 因此,除非你有自定义的验证逻辑,否则不需要处理验证错误,被捕获的异常会被自动记录到日志中,默认记录异常级别为Error .可以通过实现IHasLogLevel 接口来指定日志的级别

public class MyException : Exception, IHasLogLevel
{
    public LogLevel LogLevel { get; set; } = LogLevel.Warning;

    //...
}
复制代码

某些异常类型可能需要记录额外日志信息.可以通过实IExceptionWithSelfLogging 接口来记录指定日志

public class MyException : Exception, IExceptionWithSelfLogging
{
    public void Log(ILogger logger)
    {
        //...log additional info
    }
}
复制代码

扩展方法ILogger.LogException 用来记录异常日志. 在需要时可以使用相同的扩展方法.

3.业务异常

大多数异常都是业务异常.可以通过使用IBusinessException 接口来标记异常为业务异常.

BusinessException 除了实现IHasErrorCode,IHasErrorDetails ,IHasLogLevel 接口外,还实现了IBusinessException 接口.其默认日志级别为Warning.

throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);
复制代码

QaErrorCodes.CanNotVoteYourOwnAnswer 是一个字符串常量. 建议使用下面的错误代码格式:

<code-namespace>:<error-code>
复制代码

code-namespace,应在指定的模块/应用层中保证其唯一.Volo.Qa在这是作为code-namespace. code-namespace 同样可以在 本地化 异常信息时使用.

Volo.Qa:010002
复制代码
  • 可以直接抛出一个 BusinessException 异常,或者需要时可以从该类派生自己的Exception类型.
  • 对于BusinessException 类型,其所有属性都是可选的.但是通常会设置ErrorCodeMessage属性.

这里有个问题,就是如何在发送错误消息到客户端时,对错误消息进行本地化.ABP提供了2个模型.

如果异常实现了 IUserFriendlyException 接口,那么ABP不会修改 MessageDetails属性,而直接将它发送给客户端.UserFriendlyException 类是内建的 IUserFriendlyException 接口的实现

throw new UserFriendlyException(
    "Username should be unique!"
);
复制代码

采用这种方式是不需要本地化的.如果需要本地化消息,则可以注入string localizer

throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]);
复制代码
{
  "culture": "en",
  "texts": {
    "UserNameShouldBeUniqueMessage": "Username should be unique!"
  }
}
复制代码

string localizer 支持参数化信息.例如

throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]);
复制代码

其本地化文本如下:

"UserNameShouldBeUniqueMessage": "Username should be unique! '{0}' is already taken!"
复制代码
  • IUserFriendlyException接口派生自IBusinessException,而 UserFriendlyException类派生自BusinessException类.

UserFriendlyException很好用,但是在一些高级用法里面,它存在以下问题:

  • 在抛出异常的地方必须注入string localizer 来实现本地化 .
  • 但是,在某些情况下,可能注入不了string localizer(比如,在静态上下文或实体方法中)

那么这时就可以通过使用 错误代码 的方式来处理本地化,而不是在抛出异常的时候.

首先,在模块配置代码中将 code-namespace 映射至 本地化资源:

services.Configure<AbpExceptionLocalizationOptions>(options =>
{
    options.MapCodeNamespace("Volo.Qa", typeof(QaResource));
});
复制代码

然后Volo.Qa命名空间下的所有异常都将被对应的本地化资源进行本地化处理. 本地化资源中应包含对应错误代码的文本. 例如:

{
  "culture": "en",
  "texts": {
    "Volo.Qa:010002": "You can not vote your own answer!"
  }
}
复制代码

最后就可以抛出一个包含错误代码的业务异常了:

throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);
复制代码
  • 抛出所有实现IHasErrorCode 接口的异常都具有相同的行为.因此,对错误代码的本地化,并不是BusinessException类所特有的.
  • 为错误消息定义本地化文本并不是必须的. 如果未定义,ABP会将默认的错误消息发送给客户端. 而不使用异常的Message属性. 如果你想要发送异常的Message,使用UserFriendlyException(或使用实现IUserFriendlyException接口的异常类型)

4.使用消息的格式化参数

如果有参数化的错误消息,则可以使用异常的Data属性进行设置.例如:

throw new BusinessException("App:010046")
{
    Data =
    {
        {"UserName", "john"}
    }
};

复制代码

另外有一种更为快捷的方式:

throw new BusinessException("App:010046")
    .WithData("UserName", "john");
复制代码

下面就是一个包含UserName 参数的错误消息:

{
  "culture": "en",
  "texts": {
    "App:010046": "Username should be unique. '{UserName}' is already taken!"
  }
}
复制代码
  • WithData 支持有多个参数的链式调用 (如.WithData(...).WithData(...)).

5.HTTP状态代码映射

ABP尝试按照以下规则,自动映射常见的异常类型的HTTP状态代码:

  • 对于 AbpAuthorizationException:
    • 用户没有登录,返回 401 (未认证).
    • 用户已登录,但是当前访问未授权,返回 403 (未授权).
  • 对于 AbpValidationException 返回 400 (错误的请求) .
  • 对于 EntityNotFoundException返回 404 (未找到).
  • 对于 IBusinessExceptionIUserFriendlyException (它是IBusinessException的扩展) 返回403 (未授权) .
  • 对于 NotImplementedException 返回 501 (未实现) .
  • 对于其他异常 (基础架构中未定义的) 返回 500 (服务器内部错误) .

IHttpExceptionStatusCodeFinder 是用来自动判断HTTP状态代码.默认的实现是DefaultHttpExceptionStatusCodeFinder.可以根据需要对其进行更换或扩展.

可以重写HTTP状态代码的自动映射,示例如下:

services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
{
    options.Map("Volo.Qa:010002", HttpStatusCode.Conflict);
});
复制代码

6.内置的异常

框架会自动抛出以下异常类型:

  • Levée lorsque l'utilisateur n'a pas l'autorisation d'effectuer une opération AbpAuthorizationException.
  • Levé si l'entrée de la requête actuelle n'est pas valide AbpValidationException 异常.
  • EntityNotFoundExceptionLève une exception si l'entité demandée n'existe pas .

Les exceptions peuvent être envoyées au client via la propriété de la AbpExceptionHandlingOptionsclasse :SendExceptionsDetailsToClients

services.Configure<AbpExceptionHandlingOptions>(options =>
{
    options.SendExceptionsDetailsToClients = true;
});
复制代码

Je suppose que tu aimes

Origine juejin.im/post/7084796188175630373
conseillé
Classement