动态服务结果构建器(异常模式、结果模式、装饰器模式)






3.67/5 (7投票s)
服务方法应该向调用者返回哪种类型的结果?
目录
我们将学习我们不知道的知识,或者加深对已知知识的理解。
引言
![]() | 服务方法应该返回哪种类型的结果给调用者? |
- 调用服务方法时,我们将返回哪种类型?
- 它将包含哪些属性?
- 该方法的预期结果是否足够?
在我们开始为程序设计特殊服务时,这些问题总是萦绕在我们脑海中。在本文中,我们将讨论一些关于 **服务方法** 如何与 **ASP.NET Core 3.1 控制器** 通信的场景。
起初,您可能会认为这个主题很特殊,但在阅读完之后,您会发现您可以在任何需要向调用者返回有意义结果的场景中应用它。
我们过去通常为服务方法返回一种结果,即该函数可能的结果(如 Boolean、Integer、Models 等)。
Notice
但在我们开始之前,我们希望您具备 ASP.NET Core 的丰富经验或熟悉度,以及一些基本的面向对象编程知识(如继承、多态、接口、泛型等),并且您会发现我们使用 C# 8.0 编写了代码。您可以在 此处 阅读更多关于 C# 8.0 的信息。
您会发现,在某些类中,我们删除了该主题不需要的代码,但在主题的末尾,您会找到一个链接来下载我们创建的所有程序。
Using the Code
以下是一个服务示例
public class xxxService : Service, IxxxService
{
/// <summary>
/// Return model
/// </summary>
public IModel GetByID(Guid id)
{
return new xxxModel() { … };
} /// <summary>
/// Return Integer
/// </summary>
public int GetLastIndex()
{
return 0;
} /// <summary>
/// Return Boolean
/// </summary>
public bool Exists(IModel model)
{
return true;
}
}
但在某些 **服务方法** 中,我们发现自己想告诉 **调用者** 一些关于为什么会得到这样的结果的额外信息,例如,如果我们想告诉他发生的错误以及为什么会发生这个错误,返回的数据类型将不足以添加其他解释性信息。
public class xxxService : Service, IxxxService
{
/// <summary>
/// Return model
/// </summary>
public IModel GetByID(Guid id)
{
try{
return new xxxModel() { … };
}catch (Exception){
// Is this result sufficient?
return null;
}
} /// <summary>
/// Return Integer
/// </summary>
public int GetLastIndex()
{
try{
return 1;
}catch (Exception){
// Is this result sufficient?
return -1;
}
}
/// <summary>
/// Return Boolean
/// </summary>
public bool? Exists(IModel model)
{
try{
return true;
}catch (Exception){
// Is this result sufficient?
return null;
}
}
}
我们将使用本文中最著名的例子,即程序用于登录的方法。
服务基类
public interface IService
{
TDTO Map<TEntity, TDTO>(TEntity entity)
where TEntity : class, IEntity, new()
where TDTO : class, IDTO, new();
TEntity Map<TEntity, TDTO>(TDTO dto)
where TEntity : class, IEntity, new()
where TDTO : class, IDTO, new();
}
// This is the base class for all services in our projects
public abstract class Service : IService
{
protected Service(IMapper mapper) => Mapper = mapper;
protected IMapper Mapper { get; set; }
public virtual TDTO Map<TEntity, TDTO>(TEntity entity)
where TEntity : class, IEntity, new()
where TDTO : class, IDTO, new()
=> Mapper.Map<TDTO>(entity);
public virtual TEntity Map<TEntity, TDTO>(TDTO dto)
where TEntity : class, IEntity, new()
where TDTO : class, IDTO, new()
=> Mapper.Map<TEntity>(dto);
}
登录服务
public class LoginService : Service, ILoginService
{
public LoginService(IMapper mapper) : base(mapper) { }
public LoginDto Authenticate(string userName, string pw)
{
try
{
// This is just an example of the return type so we haven't
// listed all the code this function needs to fetch data
// from the database and the validation code.
return userName == "user" && pw == "pw" ? Map<Login, LoginDto>(new Login()
{ UserName = "default user" }) : null;
}
catch
{
// Is this result sufficient?
return null;
}
}
}
控制器类
[ApiController]
[Route("[controller]")]
public class LoginController : ControllerBase
{
private readonly ILoginService _service;
public LoginController(ILoginService service) => _service = service;
[AllowAnonymous]
[HttpPost]
public IActionResult Post([FromBody] LoginModel model)
{
var dto = _service.Authenticate(model.UserName, model.Password);
return dto is null
? (IActionResult)BadRequest("The username or password you entered is incorrect.")
: Ok(dto);
}
}
但有时,**用户名** 和 **密码** 是正确的,但错误的原因是与数据库的连接中断或存在任何其他意外错误,因此我们可以说,此时返回的结果不足以解释失败的原因。
我们如何将这一点告知调用者,以便他也能向用户解释错误?
这个问题的答案是我们今天的主题的重点。
我们将包含一些场景,这些场景将帮助我们返回足够的信息来阐明服务方法,并尝试以简单的方式解释编程过程。
在这篇文章中,我们不会讨论程序所需的其他类设计方法,以及其设计中使用的其他方法(如:仓库模式、工作单元模式、领域驱动设计中的服务等),我们只会讨论 **服务方法** 返回给 **调用者** 的 **结果数据类型**。
场景 1 - 抛出异常或异常模式
我们将首先从一个不需要花费大量时间来应用于我们之前创建的服务之一的场景开始,因为它只需要 **抛出异常**,以便 **调用者** 可以构建更准确的结果,告知用户该过程失败的原因。
为什么我们需要创建自定义异常?
没有具体的答案可以为此目的服务,有些人认为这是额外的负担,而另一些人认为它很有用,根据我的经验,这取决于您在发现异常时需要做什么,例如,如果您只想抛出此异常,那么就没有必要创建它们,但如果您想处理异常并将其返回给 **调用者**,并附带一个更准确的新异常,**调用者** 可以更准确地识别和处理它,那么您就必须创建它们。
- 如果我们找不到任何预先存在的异常类,我们可以指定我们预计会发生的异常。
- 根据异常的类型以及如何抛出异常,来执行一些复杂的操作。
- 异常的层次结构。为了便于捕获错误,我们可以通过捕获 **基类** 来捕获一组异常。
try
{
// ...
}
// Here we can catch all exceptions inherited from ServiceException
catch(ServiceException ex)
{
// ...
}
- 您不会发生冲突。
- 提供有意义的消息。
现在,我们可以为我们之前创建的项目抛出异常,以便 **调用者** 可以收集有关此函数工作流程及其遇到的异常的一些额外信息。
所有异常的基类
/// <summary>
/// Base class for all applications
/// </summary>
public class ServiceException : Exception
{
public ServiceException() { }
public ServiceException(string message) : base(message) { }
public ServiceException(string message, Exception innerException) :
base(message, innerException) { }
}
/// <summary>
/// Base class for ASP.NET WEB API applications
/// </summary>
public class HttpStatusCodeException : ServiceException
{
public HttpStatusCodeException() { }
public HttpStatusCodeException(string message) : base(message) { }
public HttpStatusCodeException(string message,
HttpStatusCode httpStatusCode) : this(message, null, httpStatusCode) { }
public HttpStatusCodeException(string message,
Exception innerException) : this(message, innerException,
HttpStatusCode.InternalServerError) { }
public HttpStatusCodeException(string message,
Exception innerException, HttpStatusCode httpStatusCode) :
base(message, innerException) => HttpStatusCode = httpStatusCode;
public HttpStatusCode HttpStatusCode { get; }
}
起初,我们会注意到 `HttpStatusCodeException` 对于程序的继续进行已经足够了,但如果我们仔细观察,我们会注意到每次我们需要创建此类的一个实例时,都需要传递 `HttpStatusCode`,并且在捕获此异常时,我们必须每次都测试此属性。为了使这更容易,我们将从这个基类创建一些 **特殊类**,以拥有更精确的类并保持程序的连贯性,以免在理解异常捕获计划时出现不一致。
所有其他异常类
public class BadRequestException : HttpStatusCodeException
{
public BadRequestException() { }
public BadRequestException(string message) : base(message, HttpStatusCode.BadRequest) { }
public BadRequestException(string message, Exception innerException) :
base(message, innerException) { }
}
public class EntityNotFoundException : HttpStatusCodeException
{
public EntityNotFoundException() { }
public EntityNotFoundException(string message) : base(message) { }
public EntityNotFoundException(string message, Exception innerException) :
base(message, innerException) { }
public EntityNotFoundException(Type entityType) :
base($"The {entityType?.Name ?? "entity"} does not exist.", HttpStatusCode.NotFound) { }
public EntityNotFoundException(Type entityType, Exception innerException)
: base($"The {entityType?.Name ?? "entity"} does not exist.",
innerException, HttpStatusCode.NotFound) { }
}
public class ForbiddenException : HttpStatusCodeException
{
public ForbiddenException() { }
public ForbiddenException(string message) : base(message, HttpStatusCode.Forbidden) { }
public ForbiddenException(string message, Exception innerException) :
base(message, innerException) { }
}
public class InternalServerErrorException : HttpStatusCodeException
{
public InternalServerErrorException() { }
public InternalServerErrorException(string message) :
base(message, HttpStatusCode.InternalServerError) { }
public InternalServerErrorException(string message, Exception innerException) :
base(message, innerException) { }
}
public class MethodNotAllowedException : HttpStatusCodeException
{
public MethodNotAllowedException() { }
public MethodNotAllowedException(string message) :
base(message, HttpStatusCode.MethodNotAllowed) { }
public MethodNotAllowedException(string message, Exception innerException) :
base(message, innerException) { }
}
public class UnprocessableEntityException : HttpStatusCodeException
{
public UnprocessableEntityException() { }
public UnprocessableEntityException(string message) :
base(message, HttpStatusCode.UnprocessableEntity) { }
public UnprocessableEntityException(string message, Exception innerException) :
base(message, innerException) { }
}
// This is a special class that we will use
// when we want to add a warning to the ServiceResult,
// When we implement the Result Pattern
public class WarningException : HttpStatusCodeException
{
public WarningException(object resul, string message) :
base(message, HttpStatusCode.OK) => Result = resul;
public object Result { get; }
}
在开始实现此服务时,我们将遵循通常的方法来返回预期值,但要考虑到我们之前讨论过的以实现此模式。例如,当找不到所需用户时,我们必须抛出异常而不是返回 `null` 值,依此类推,每当我们想将额外信息发送给 **调用者** 时,我们都会抛出异常,并且我们将重写意外异常,用我们程序中的新有意义异常来处理,以便在错误开始时,我们可以发现新异常的更清晰的形成,依此类推,直到我们看到我们已经覆盖了我们程序中的所有意外异常。
登录服务
public interface ILoginService : IService
{
LoginDto Authenticate(string userName, string pw);
}
public class LoginService : Service, ILoginService
{
public LoginService(IMapper mapper) : base(mapper) { }
public LoginDto Authenticate(string userName, string pw)
{
try
{
// This is just an example of the return type so we haven't
// listed all the code this function needs to fetch data
// from the database and the validation code.
// ===== User with Warning =====
if (userName == "user_a" && pw == "pw")
{
// The ASP.NET Core will treat this warning as an Internal Server Error.
throw new WarningException(
Map<Login, LoginDto>(new Login() { UserName = "Warning user" }),
"For more than ten months you have not changed your password,
we recommend that you change it as soon as possible.");
}
// ===== Valid user =====
if (userName == "user_b" && pw == "pw")
{
return Map<Login, LoginDto>(new Login() { UserName = "Valid user" });
}
// ===== User with Exception =====
if (userName == "user_c" && pw == "pw")
{
throw new ForbiddenException($"User {userName} is currently blocked.");
}
throw new BadRequestException
("The username or password you entered is incorrect.");
}
catch (Exception ex)
{
// Probably one of the exceptions we designed, so we test the exception first.
throw ex as HttpStatusCodeException ??
new InternalServerErrorException(ex.Message, ex);
/*
* ====================================================
* During the troubleshooting process, we can throw other types
* when analyzing this Exception.
* ====================================================
* if (ex is xxxException) {
* throw new A_Custme_Exception(ex.Message, ex);
* }
* ...
*/
}
}
}
在我们的控制器中,我们必须捕获将要抛出的异常,并收集尽可能多的信息以向用户显示清晰的结果。
public class LoginController : AppController<ILoginService>
{
public LoginController(ILoginService service) : base(service) { }
[AllowAnonymous]
[HttpPost]
public IActionResult Authenticate([FromBody] LoginModel model)
{
try
{
var dto = Service.Authenticate(model.UserName, model.Password);
/*
* In this case, we should not test if the result is null,
* because there is a prior agreement between the service and
* the caller to catch bugs, if he wants more details about the exceptions.
*/
return Ok(dto);
}
catch (Exception ex)
{
return GetResult(ex);
}
}
}
优点
特殊异常是理解程序工作方式和预期异常的一种手段,但我们无法列出所有异常,有些异常是无法预料的,因此所有程序都应采用测试驱动设计和手动测试计划。
程序的稳定性是异常的主要优点之一。
缺点
它需要很高的 CPU 时间,因此会降低应用程序的性能,但不足以让人担心,因为异常有助于稳定程序。
额外的负担,因为我们需要为每个异常创建一个新类。
改进设计的想法
要改进此设计,您可以使用 ASP.NET Core 中的一种错误处理技术,例如:异常中间件或异常过滤器。
要阅读有关此网站的信息,您可以访问这些网站
场景 2 – 结果对象或结果模式
如果我们对应用程序应用一些设计原则,如 **关注点分离** 或 **单一职责原则 (SRP)**,以强制执行一些更严格的规则来保持程序的一致性。
我们会发现第一个场景不足以应用这些原则,因此我们必须从另一个方向思考,寻找一种新的方法,通过这种方法我们可以向 **调用者** 返回多个值。
在此场景中,我们将创建一个新对象,通过该对象我们可以扩展 **服务方法** 和 **调用者** 之间的对话。首先想到的是
这个新类将包含哪些属性?
简单来说,有很多属性可以添加。但在我们的主题中,我们将添加最重要的属性,并将其余属性留给您实现的场景需求。
添加的第一个属性当然是我们服务方法的预期结果,第二个属性是有关异常的信息,最后,我们可以添加一个附加属性来提醒用户发生了什么或需要做什么。在某些服务方法中,我们希望返回带警告的结果。例如,在我们的示例中,我们希望提醒用户他必须更改密码,因为仅结果不足以做到这一点,在这些情况下,如果我们想告诉他他尝试登录失败了多少次……等等,我们就使用这个属性。
此类的最终设计将如下所示
/// <summary>
/// To identify the type of result in the Front-End app.
/// </summary>
public enum ResultKinds
{
Success,
Warning,
Exception
}
public class ServiceResult<T> : DTO
{
public ServiceResult(ResultKinds kind, T result, string warning,
ExceptionDescriptions exceptionDescriptions)
{
Result = result;
ExceptionDescriptions = exceptionDescriptions;
Kind = kind;
WarningDescription = warning;
}
public ServiceResult(T result) : this(ResultKinds.Success, result, null, null) { }
public ServiceResult(T result, string warning) :
this(ResultKinds.Warning, result, warning, null) { }
public ServiceResult(ExceptionDescriptions exceptionDescriptions) :
this(ResultKinds.Exception, default, null, exceptionDescriptions) { }
/// <summary>
/// In the Front-End program, we test the value of the Kind property
/// For the object we received via Response,
/// and on the basis of it we build the result for the user.
/// </summary>
public ResultKinds Kind { get; }
/// <summary>
/// The service result
/// </summary>
public T Result { get; }
/// <summary>
/// To show warnings in the in the Front-End app.
/// </summary>
public string WarningDescription { get; }
/// <summary>
/// The exception descriptions
/// </summary>
public ExceptionDescriptions ExceptionDescriptions { get; }
public bool IsSuccess => Kind != ResultKinds.Exception;
public static ServiceResult<T> Success(T result) =>
new ServiceResult<T>(ResultKinds.Success, result, null, null);
public static ServiceResult<T> Warning(T result, string warning) =>
new ServiceResult<T>(ResultKinds.Warning, result, warning, null);
public static ServiceResult<T> Exception(ExceptionDescriptions exceptionDescriptions) =>
new ServiceResult<T>(ResultKinds.Exception, default, null, exceptionDescriptions);
public static implicit operator T(ServiceResult<T> result) => result.Result;
public static explicit operator ExceptionDescriptions(ServiceResult<T> result) =>
result.ExceptionDescriptions;
public static explicit operator Exception(ServiceResult<T> result) =>
result.ExceptionDescriptions.Exception;
}
我们修改了 **Authenticate 方法** 的返回类型。它将不再返回 `LoginDto`,而是返回我们之前讨论过的 `ServiceResult<LoginDto>` 类型,`LoginService` 的最终设计如下所示。
public interface ILoginService : IService
{
ServiceResult<LoginDto> Authenticate(string userName, string pw);
}
public class LoginService : Service, ILoginService
{
public LoginService(IMapper mapper, IHostEnvironment environment) :
base(mapper) => Environment = environment;
protected IHostEnvironment Environment { get; }
public ServiceResult<LoginDto> Authenticate(string userName, string pw)
{
try
{
// This is just an example of the return type so we haven't
// listed all the code this function needs to fetch data
// from the database and the validation code.
// ===== User with Warning =====
if (userName == "user_a" && pw == "pw")
{
return ServiceResult<LoginDto>.Warning(
Map<Login, LoginDto>(new Login() { UserName = "default user" }),
"For more than ten months you have not changed your password,
we recommend that you change it as soon as possible.");
}
// ===== Valid user =====
if (userName == "user_b" && pw == "pw")
{
return ServiceResult<LoginDto>.Success(Map<Login, LoginDto>
(new Login() { UserName = "default user" }));
}
// ===== User with Exception =====
if (userName == "user_c" && pw == "pw")
{
return ServiceResult<LoginDto>.Exception(new ExceptionDescriptions(
new ForbiddenException($"User {userName} is currently blocked."),
MethodBase.GetCurrentMethod().Name,
Environment));
}
}
catch (Exception ex)
{
return ServiceResult<LoginDto>.Exception(new ExceptionDescriptions(
// Probably one of the exceptions we designed,
// so we test the exception first.
ex as HttpStatusCodeException ??
new InternalServerErrorException(ex.Message, ex),
MethodBase.GetCurrentMethod().Name,
Environment));
//
/*
* ====================================================
* During the troubleshooting process, we can throw other types,
* when analyzing this exception 'ex'.
* ====================================================
* if (ex is xxxException) {
* return ServiceResult<LoginDto>.Exception
(nnew ExceptionDescriptions
(ew xxx_Custme_Exception(ex.Message, ex), ...));
* }
* ...
*/
}
return ServiceResult<LoginDto>.Exception(new ExceptionDescriptions(
new BadRequestException
("The username or password you entered is incorrect."),
MethodBase.GetCurrentMethod().Name,
Environment));
}
}
在这里,我们将显示已修改以适应调用此新方法方式的 **控制器**,以及它将如何执行对服务方法返回的结果的 **Kind 属性** 进行测试,以便向用户显示清晰的结果。
public class LoginController : AppController<ILoginService>
{
public LoginController(ILoginService service, IWebHostEnvironment environment) :
base(service, environment) { }
[AllowAnonymous]
[HttpPost]
public IActionResult Authenticate([FromBody] LoginModel model) =>
Service
.Authenticate(model.UserName, model.Password)
.ToActionResult(this);
}
我们会注意到 **控制器** 是干净、清晰的,并且仅负责处理请求。
在这里,我们将列出控制器基类。
public abstract class AppController<TService> : AppController where TService : IService
{
protected AppController(TService service, IWebHostEnvironment environment) :
base(environment) => Service = service;
protected TService Service { get; }
}
[Route("api/[controller]")]
[ApiController]
public abstract class AppController : ControllerBase
{
protected AppController(IWebHostEnvironment environment) => Environment = environment;
public IWebHostEnvironment Environment { get; }
/// <summary>
/// Creates an <see cref="ObjectResult"/> object that produces an
/// <see cref="StatusCodes.Status403Forbidden"/> response.
/// </summary>
/// <returns>The created <see cref="ObjectResult"/> for the response.</returns>
[NonAction]
public virtual ObjectResult Forbidden([ActionResultObjectValue] object value) =>
StatusCode(StatusCodes.Status403Forbidden, value);
/// <summary>
/// Creates an <see cref="ObjectResult"/> object that produces an
/// <see cref="StatusCodes.Status500InternalServerError"/> response.
/// </summary>
/// <returns>The created <see cref="ObjectResult"/> for the response.</returns>
[NonAction]
public virtual ObjectResult InternalServerError([ActionResultObjectValue]
object value) => StatusCode(StatusCodes.Status500InternalServerError, value);
/// <summary>
/// Creates an <see cref="ObjectResult"/> object that produces an
/// <see cref="StatusCodes.Status405MethodNotAllowed"/> response.
/// </summary>
/// <returns>The created <see cref="ObjectResult"/> for the response.</returns>
[NonAction]
public virtual ObjectResult MethodNotAllowed([ActionResultObjectValue]
object value) => StatusCode(StatusCodes.Status405MethodNotAllowed, value);
}
最后,用于将结果处理移至另一个单元的扩展方法。
public static class ServicesResultExtensions
{
public static IActionResult ToActionResult<T>(this ServiceResult<T> result,
AppController controller, [CallerMemberName] string callerName = "") =>
// C# 8.0
// https://docs.microsoft.com/en-us/dotnet/csharp/
// language-reference/operators/switch-expression
// In the Front-End program, we test the value of the Kind property
// For the object we received via Response,
// and on the basis of it we build the result for the user.
result?.Kind switch
{
ResultKinds.Exception =>
result.ExceptionDescriptions?.Exception switch
{
EntityNotFoundException _ => controller.NotFound
(new { result.Kind, KindName = result.Kind.ToString(),
result.ExceptionDescriptions }),
InternalServerErrorException _ => controller.InternalServerError
(new { result.Kind, KindName = result.Kind.ToString(),
result.ExceptionDescriptions }),
MethodNotAllowedException _ => controller.MethodNotAllowed
(new { result.Kind, KindName = result.Kind.ToString(),
result.ExceptionDescriptions }),
UnprocessableEntityException _ => controller.UnprocessableEntity
(new { result.Kind, KindName = result.Kind.ToString(),
result.ExceptionDescriptions }),
BadRequestException _ => controller.BadRequest(new
{ result.Kind, KindName = result.Kind.ToString(),
result.ExceptionDescriptions }),
ForbiddenException _ => controller.Forbidden(new
{ result.Kind, KindName = result.Kind.ToString(),
result.ExceptionDescriptions }),
_ => controller.InternalServerError(new { result.Kind,
KindName = result.Kind.ToString(), result.ExceptionDescriptions }),
},
ResultKinds.Warning => controller.Ok(new { result.Kind,
KindName = result.Kind.ToString(), result.Result, result.WarningDescription }),
ResultKinds.Success => controller.Ok(new { result.Kind,
KindName = result.Kind.ToString(), result.Result }),
_ => controller.InternalServerError(
new
{
Kind = ResultKinds.Exception,
KindName = result.Kind.ToString(),
ExceptionDescriptions = new ExceptionDescriptions
(new InternalServerErrorException(), callerName,
controller.Environment)
})
};
}
在此场景中,我们实现了 **关注点分离**,因为我们确保了错误逻辑包含在实现逻辑中,并且我们还实现了 **单一职责原则 (SRP)**,因为我们注意到控制器负责处理请求并返回明确的响应。
这是 **Postman** 的结果
// Warning
{
"kind": 1,
"kindName": "Warning",
"result": {
"userName": "Warning user"
},
"warningDescription": "For more than ten months you have not changed your password,
we recommend that you change it as soon as possible."
}
// Success
{
"kind": 0,
"kindName": "Success",
"result": {
"userName": "Valid user"
}
}
// blocked User
{
"kind": 2,
"kindName": "Exception",
"exceptionDescriptions": {
"statusCode": 403,
"status": "Forbidden",
"title": "Authenticate",
"detail": "User user_c is currently blocked.",
"targetSite": "ServiceResult.AspectOriented.LoginService.Authenticate",
"stackTrace": [
" at ServiceResult.AspectOriented.LoginService.Authenticate
(String userName, String pw) in
C:\\My Projects\\ServiceResult\\src\\ServiceResult.AspectOriented\\
LoginService.cs:line 20"
],
"innerException": null
}
}
// Bad Request
{
"kind": 2,
"kindName": "Exception",
"exceptionDescriptions": {
"statusCode": 400,
"status": "BadRequest",
"title": "Authenticate",
"detail": "The username or password you entered is incorrect.",
"targetSite": "ServiceResult.AspectOriented.LoginService.Authenticate",
"stackTrace": [
" at ServiceResult.AspectOriented.LoginService.Authenticate
(String userName, String pw) in C:\\My Projects\\ServiceResult\\
src\\ServiceResult.AspectOriented\\LoginService.cs:line 66"
],
"innerException": null
}
}
场景 3 – 全部使用面向切面编程或装饰器模式
**面向切面编程 (AOP)**:我们不会讨论这种设计的全部优点,但我们会讨论最突出的特点。它是一种将程序工作划分为易于领导、维护和开发的部分的有效方法,而不会损害程序 **模块化** 的其他部分。*核心思想是在不更改现有代码的情况下为其添加新行为。* 新代码应该是公开的,以便可以应用于任何对象,并且对象应该对该行为一无所知。AOP 还允许开发人员实现 **横切关注点的分离**,并促进 **单一职责原则 (SRP)**。
在这里,我们将讨论我们修改过的部分,正如我们在主题开头所说的那样,您可以下载本文所有已开发程序的全部内容。
首先,我们稍微修改了服务以返回预期值,并创建了该方法的异步版本以测试异步编程。
public interface ILoginService : IService
{
Task<LoginDto> AuthenticateAsync(string userName, string pw);
LoginDto Authenticate(string userName, string pw);
}
public class LoginService : Service, ILoginService
{
public LoginService(IMapper mapper) : base(mapper) { }
[DebuggerHidden]
public Task<LoginDto> AuthenticateAsync(string userName, string pw) =>
Task.Run(() => Authenticate(userName, pw));
[DebuggerHidden]
public LoginDto Authenticate(string userName, string pw)
{
try
{
// This is just an example of the return type so we haven't
// listed all the code this function needs to fetch data
// from the database and the validation code.
// ===== User with Warning =====
if (userName == "user_a" && pw == "pw")
{
// To follow the 'warning convention' between AOP and the service.
throw new WarningException(
Map<Login, LoginDto>(new Login() { UserName = "Warning user" }),
"For more than ten months you have not changed your password,
we recommend that you change it as soon as possible.");
}
// ===== Valid user =====
if (userName == "user_b" && pw == "pw")
{
return Map<Login, LoginDto>(new Login() { UserName = "Valid user" });
}
// ===== User with Exception =====
if (userName == "user_c" && pw == "pw")
{
throw new ForbiddenException($"User {userName} is currently blocked.");
}
}
catch (Exception ex)
{
// Probably one of the exceptions we designed, so we test the exception first.
throw ex as HttpStatusCodeException ?? new InternalServerErrorException
(ex.Message, ex);
//
/*
* ====================================================
* During the troubleshooting process, we can throw other types,
* when analyzing this exception 'ex'.
* ====================================================
* if (ex is xxxException) {
* throw xxx_Custme_Exception(ex.Message, ex), ...));
* }
* ...
*/
}
throw new BadRequestException("The username or password you entered is incorrect.");
}
}
然后,我们创建了一个继承自 `DispatchProxy` 的新类。该类型自平台开始就存在于 .NET Core 中,并提供了一种创建 **代理对象** 和处理其 **派遣方法** 的机制。可以使用以下代码从中创建 **代理对象**。
var proxy = DispatchProxy.Create<Interface, Proxy>();
现在,程序中的重要部分是实现 `GenericDecorator` 以便在所有程序中使用。代码稍后会出现,它是一个 `DispatchProxy` 的子类,然后我们创建一个新的子类来匹配我们要添加到服务中的新行为。在 AOP 中,这称为 **Aspect**。
Aspect 是应用程序中跨越多个实体基本关注点的部分,因此违反了其关注点分离,该关注点试图封装不相关的函数。
泛型装饰器或泛型代理
public class GenericDecorator<T> : DispatchProxy
{
protected T Decorated { get; private set; }
/// <summary>
/// Whenever any method on the generated proxy type is called,
/// this method is invoked before the target method to dispatch control.
/// </summary>
/// <remarks>Use this interceptor method to initialize your plan
/// before inject any behavior into the proxy.</remarks>
/// <param name="targetMethod">The method the caller invoked.</param>
/// <param name="args">The arguments the caller passed to the method.</param>
/// <param name="methodKind">The kind of method.</param>
protected virtual void BeforeInvoke(MethodInfo targetMethod, object[] args,
MethodKind methodKind) { }
/// <summary>
/// Whenever any method on the generated proxy type is called,
/// this method is invoked after the target method to dispatch control.
/// </summary>
/// <remarks>Use this interceptor method to inject behavior into the proxy.</remarks>
/// <param name="targetMethod">The method the caller invoked.</param>
/// <param name="args">The arguments the caller passed to the method.</param>
/// <param name="methodKind">The kind of method.</param>
/// <param name="result">The object returned from the target method.</param>
/// <returns>The object to return to the caller, or null for void methods.</returns>
protected virtual object AfterInvoke(MethodInfo targetMethod, object[] args,
MethodKind methodKind, object result) => result;
/// <summary>Executed when an exception occurred.</summary>
/// <param name="exception">The exception that occurred.</param>
/// <param name="methodInfo">The function that executed and issued this exception.
/// </param>
/// <param name="handled">TReturn true when handled the exception,
/// otherwise false</param>
/// <returns>The object to return to the caller, or null for void methods.</returns>
protected virtual object OnException(Exception exception, MethodInfo methodInfo,
out bool handled)
{
handled = false;
return null;
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
var getAwaiterMethod = targetMethod.ReturnType.GetMethod(nameof(Task.GetAwaiter));
try
{
object result = null;
var methodKind = targetMethod.GetKind();
BeforeInvoke(targetMethod, args, methodKind);
// Check if method is async
if (getAwaiterMethod != null)
{
if (targetMethod.ReturnType.IsGenericType)
{
dynamic awaitable = targetMethod.Invoke(Decorated, args);
result = awaitable.GetAwaiter().GetResult();
//Task.Run(() => { }).GetAwaiter();
result = AfterInvoke(targetMethod, args, methodKind, result);
result = CreateTask(targetMethod, result);
}
else
{
dynamic awaitable = targetMethod.Invoke(Decorated, args);
awaitable.GetAwaiter().GetResult();
result = Task.CompletedTask;
}
}
else
{
if (targetMethod.ReturnType == typeof(void))
{
targetMethod.Invoke(Decorated, args);
}
else
{
result = targetMethod.Invoke(Decorated, args);
result = AfterInvoke(targetMethod, args, methodKind, result);
}
}
return result;
}
catch (Exception ex)
{
// Check if ex is TargetInvocationException or AggregateException.
ex = ex.InnerException ?? ex;
var result = OnException(ex, targetMethod, out var handled);
result = getAwaiterMethod is null ? result : CreateTask(targetMethod, result);
return handled ? result : throw ex;
}
}
protected object CreateTask(MethodInfo targetMethod, object result) =>
CreateTask(GetMethodReturnType(targetMethod), result);
/// <summary>
/// Return the type from the method, void if the method was Task,
/// T if the method was Task<T>.
/// </summary>
protected Type GetMethodReturnType(MethodInfo targetMethod)
{
//if (targetMethod.ReturnType == typeof(void)) { return null; }
if (typeof(Task).IsAssignableFrom(targetMethod.ReturnType))
{
return targetMethod.ReturnType.IsGenericType
? targetMethod.ReturnType.GetGenericArguments()[0]
: typeof(void);
}
return targetMethod.ReturnType;
}
protected object CreateTask(Type genericType, object result)
{
var fromResult = typeof(Task).GetMethod(nameof(Task.FromResult),
BindingFlags.Public | BindingFlags.Static);
return fromResult.MakeGenericMethod(genericType).Invoke(null, new object[]
{ result });
//return Task.FromResult((dynamic)result);
}
protected virtual void SetParameters(T original) =>
Decorated = original ?? throw new ArgumentNullException(nameof(original));
}
但此场景存在一个问题,即它在并发应用程序(多线程)中不安全,因为我们将“对象状态”保存在此对象的全局变量中,因此我们将不实现它,因为我们会面临风险。
- 另一个我们将实现的场景:在运行时创建一个继承原始对象的新类型,以获得服务方法的预期结果,并将其插入其中。
此场景的缺点是它有点困难,需要一些 `System.Reflection.Emit` 经验,或者我们可以使用 **Roslyn 进行代码生成**。在我们的主题中,我们使用了 `System.Reflection.Emit`。
我们将在此处放置代码,但不会进行解释,因为它是一个很大的主题,超出了我们主题的范围,但如果您想了解更多关于 `System.Reflection.Emit` 的信息,请查看此 **链接**。
我们将解释如何使用我们创建的 **对象构建器**,以便将来对其进行开发并将其转移到其他项目中。
此场景的另一个缺点是,我们无法获得 `Null` 结果,因为我们总是返回在运行时创建的新对象。但是,我们添加了一个新属性,以便我们可以查看原始结果是 `Null` 还是非 `Null`。
至于我们获得的优势
- 并发(多线程)没有问题。
- 关注点分离,通过将创建运行时对象的责任转移到另一个对象。
- **单一职责原则 (SRP)**:`ResultPatternAspect` 仅负责处理结果并创建适合我们正在实现此场景的新结果。
- 通过应用以上两个原则,我们将程序划分为小部分,换句话说,我们接近于在程序中实现模块化。
结果模式 Aspect
通过应用 **面向接口编程而非实现** 的原则,您会注意到我们大量依赖接口来使我们的程序基于抽象而非具体对象。程序行为也可以在运行时更改,它还有助于我们编写在维护方面好得多的程序。为了让对象只处理它所需的方法,这是 **SOLID** 原则之一,即 **接口隔离 (ISP)**。
/// <summary>
/// To distinguish the services that we have created via the ResultPatternAspect
/// </summary>
public interface IResultPatternService { }
public interface IResultPatternAspect<TService> : IResultPatternService
{
IResultPatternAspect<TService>
Initialize(TService service, IObjectBuilder objectBuilder,
IHostEnvironment environment);
}
public class ResultPatternAspect<TService> :
GenericDecorator<TService>, IResultPatternService, IResultPatternAspect<TService>
{
private IObjectBuilder _objectBuilder;
private IHostEnvironment _hostEnvironment;
// protected override void BeforeInvoke(MethodInfo targetMethod,
object[] args, MethodKind methodKind) =>
base.BeforeInvoke(targetMethod, args, methodKind);
protected override object AfterInvoke(MethodInfo targetMethod,
object[] args, MethodKind methodKind, object result)
{
if (methodKind != MethodKind.Method)
{
return result;
}
var dynamicResult = CreateProxyToObject(targetMethod, result);
dynamicResult.Kind = ResultKinds.Success;
dynamicResult.WarningDescription = null;
dynamicResult.ExceptionDescriptions = null;
return dynamicResult;
}
protected override object OnException(Exception exception,
MethodInfo methodInfo, out bool handled)
{
var warningException = exception as WarningException;
handled = true;
var dynamicResult = CreateProxyToObject(methodInfo, warningException?.Result);
dynamicResult.Kind = warningException is null ?
ResultKinds.Exception : ResultKinds.Warning;
dynamicResult.WarningDescription = warningException?.Message;
dynamicResult.ExceptionDescriptions = new ExceptionDescriptions
(exception as ServiceException ?? new InternalServerErrorException
(exception.Message, exception), methodInfo.Name, _hostEnvironment);
return dynamicResult;
}
private IResultPatternProxy
CreateProxyToObject(MethodInfo targetMethod, object result)
{
var returnType = GetMethodReturnType(targetMethod);
var dynamicResult = _objectBuilder.CreateObject(CreateTypeName(returnType),
null, returnType, new[] { typeof(IResultPatternProxy) }, true)
as IResultPatternProxy;
if (result != null)
{
var mapperConfiguration = new AutoMapper.MapperConfiguration
(config => config.CreateMap(returnType, dynamicResult.GetType()));
var mapper = new AutoMapper.Mapper(mapperConfiguration);
mapper.Map(result, dynamicResult);
dynamicResult.IsNull = false;
var hh = returnType.IsAssignableFrom(dynamicResult.GetType());
}
dynamicResult.IsNull = true;
return dynamicResult;
}
private static string CreateTypeName(Type type) =>
$"_PROXY_RESULT_PATTERN_{type.Name.ToUpper()}_";
IResultPatternAspect<TService> IResultPatternAspect<TService>.Initialize
(TService service, IObjectBuilder objectBuilder, IHostEnvironment environment)
{
SetParameters(service);
_objectBuilder = objectBuilder;
_hostEnvironment = environment;
return this;
}
}
为了便于创建 `ResultPatternAspect` 对象,我们应用了 **工厂模式** 来简化创建此 Aspect 的策略。
public interface IResultPatternAspecFactory
{
TService Create<TService>(TService service);
}
public class ResultPatternAspecFactory : IResultPatternAspecFactory
{
public ResultPatternAspecFactory(IServiceProvider serviceProvider) =>
ServiceProvider = serviceProvider;
public IServiceProvider ServiceProvider { get; }
public TService Create<TService>(TService service)
{
var objectBuilder = ServiceProvider.GetService<IObjectBuilder>();
var environment = ServiceProvider.GetService<IHostEnvironment>();
var proxy = DispatchProxy.Create<TService,
ResultPatternAspect<TService>>() as IResultPatternAspect<TService>;
proxy.Initialize(service, objectBuilder, environment);
return proxy is TService serviceProxy ? serviceProxy : service;
}
}
稍后我们将讨论如何在 **ASP.NET Core 3.1 依赖注入** 中注册对象。
动态对象构建器
**构建器模式** 允许我们逐步创建复杂的对象。它还允许我们使用相同的构建代码来生成对象的不同类型和表示。
现在让我们稍微谈谈如何使用 `SimpleDynamicObjectBuilder`,首先我们展示它的代码。
public class BuilderPropertyInfo
{
public string Name { get; set; }
public Type Type { get; set; }
public bool IsInterfaceImplementation { get; set; }
}
public interface IObjectBuilder
{
object CreateObject(string name, BuilderPropertyInfo[] properties = null,
Type baseClass = null, Type[] interfaces = null,
bool autoGenerateInterfaceproperties = false);
TInterface CreateObject<TBase, TInterface>(string name) where TBase : class, new();
TInterface CreateObject<TBase, TInterface>(string name,
BuilderPropertyInfo[] properties = null) where TBase : class, new();
TInterface CreateObject<TInterface>(string name);
TBase CreateObject<TBase>(string name, BuilderPropertyInfo[] properties = null)
where TBase : class, new();
TBase CreateObject<TBase>(string name, BuilderPropertyInfo[] properties = null,
Type[] interfaces = null, bool autoGenerateInterfaceproperties = false)
where TBase : class, new();
}
/// <summary>
/// This is a special builder for our scenario,
/// but we can do more on System.Reflection.Emit.
/// For more details see
/// <a href="https://livebook.manning.com/book/
/// metaprogramming-in-dot-net/about-this-book/">Metaprogramming
/// in .NET Book</a>.
/// </summary>
public class SimpleDynamicObjectBuilder : IObjectBuilder
{
private readonly AssemblyBuilder _assemblyBuilder;
private readonly ModuleBuilder _moduleBuilder;
public SimpleDynamicObjectBuilder(string assemblyName)
{
_assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly
(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
_moduleBuilder = _assemblyBuilder.DefineDynamicModule("MainModule");
}
public SimpleDynamicObjectBuilder() : this(Guid.NewGuid().ToString()) { }
public TInterface CreateObject<TInterface>(string name)
{
var interfaceType = typeof(TInterface);
if (!interfaceType.IsInterface) { return default; }
return CreateObject(name, null, null, new Type[]
{ interfaceType }, true) is TInterface @interface ? @interface : default;
}
public TInterface CreateObject<TBase, TInterface>(string name)
where TBase : class, new() => CreateObject<TBase, TInterface>(name, null);
public TInterface CreateObject<TBase, TInterface>(string name,
BuilderPropertyInfo[] properties = null) where TBase : class, new()
{
var interfaceType = typeof(TInterface);
if (!interfaceType.IsInterface) { return default; }
return CreateObject(name, properties, typeof(TBase),
new Type[] { interfaceType }, true) is TInterface @interface ?
@interface : default;
}
public TBase CreateObject<TBase>(string name,
BuilderPropertyInfo[] properties = null) where TBase : class,
new() => CreateObject<TBase>(name, properties);
public TBase CreateObject<TBase>(string name,
BuilderPropertyInfo[] properties = null, Type[] interfaces = null,
bool autoGenerateInterfaceproperties = false) where TBase : class, new() =>
CreateObject(name, properties, typeof(TBase), interfaces,
autoGenerateInterfaceproperties) as TBase;
public object CreateObject(string name, BuilderPropertyInfo[] properties = null,
Type baseClass = null, Type[] interfaces = null,
bool autoGenerateInterfaceproperties = false)
{
// To avoid creating the class again.
var definedType = Array.Find(_moduleBuilder.GetTypes(), x => x.Name == name);
if (definedType != null)
{
return Activator.CreateInstance(definedType);
}
var dynamicClass = DefineType(name, baseClass, interfaces);
CreateDefaultConstructor(dynamicClass);
if (properties?.Length > 0)
{
foreach (var property in properties)
{
CreateProperty(dynamicClass, property);
}
}
if (interfaces?.Length > 0 && autoGenerateInterfaceproperties)
{
foreach (var property in interfaces
.SelectMany(x => x.GetProperties())
.Select(x => new BuilderPropertyInfo()
{
Name = x.Name,
Type = x.PropertyType,
IsInterfaceImplementation = true
})
.ToArray())
{
CreateProperty(dynamicClass, property);
}
}
return Activator.CreateInstance(dynamicClass.CreateType());
}
private TypeBuilder DefineType(string name, Type baseClass = null,
Type[] interfaces = null) => _moduleBuilder.DefineType(name,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
baseClass == typeof(void) ? null : baseClass,
interfaces);
private ConstructorBuilder CreateDefaultConstructor(TypeBuilder typeBuilder) =>
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public |
MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
private PropertyBuilder CreateProperty(TypeBuilder typeBuilder,
BuilderPropertyInfo propertyInfo)
{
var fieldBuilder = typeBuilder.DefineField("_" +
propertyInfo.Name, propertyInfo.Type, FieldAttributes.Private);
var propertyBuilder = typeBuilder.DefineProperty(propertyInfo.Name,
PropertyAttributes.HasDefault, propertyInfo.Type, null);
var methodAttributes =
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig |
(propertyInfo.IsInterfaceImplementation ? MethodAttributes.Virtual : 0);
// get => _privateField;
var getPropMthdBldr = typeBuilder.DefineMethod("get_" +
propertyInfo.Name, methodAttributes, propertyInfo.Type, Type.EmptyTypes);
var getIl = getPropMthdBldr.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
// set => _privateField = value;
var setPropMthdBldr = typeBuilder.DefineMethod("set_" +
propertyInfo.Name, methodAttributes, null, new[] { propertyInfo.Type });
var setIl = setPropMthdBldr.GetILGenerator();
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Ret);
// Set get and set method to property
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
return propertyBuilder;
}
}
要在运行时创建动态实例,我们只需创建一个 `DynamicObjectBuilder` 的实例,然后按照以下简单步骤操作。
var dynamicObjectBuilder = new DynamicObjectBuilder("Domain_Assembly");
在这里,我们创建了一些函数来在运行时创建动态对象。
public class User
{
public string Name { get; set; }
}
public interface IInterface
{
public string Property1 { get; set; }
}
private static string CreateTypeName(Type type) =>
$"_PROXY_RESULT_PATTERN_{type.Name.ToUpper()}_";
private static void ObjectWithInterface(DynamicObjectBuilder dynamicObjectBuilder)
{
var dynamicProxy = dynamicObjectBuilder.CreateObject<IInterface>
(CreateTypeName(typeof(IInterface)));
// Test the object.
if (dynamicProxy is IInterface resultPattern)
{
resultPattern.Property1 = "Object With Interface";
}
foreach (var property in dynamicProxy.GetType().GetProperties())
{
Console.WriteLine($"{property.Name}: {property.GetValue(dynamicProxy)}");
}
}
private static void ObjectWithBaseClassAndInterfaceAndCustomProperty
(DynamicObjectBuilder dynamicObjectBuilder)
{
var dynamicProxy = dynamicObjectBuilder.CreateObject<User, IInterface>
(
CreateTypeName(typeof(User)),
new[]
{
new BuilderPropertyInfo {Name = "Test_Custom_Property",
Type = typeof(int), IsInterfaceImplementation = false},
}
);
// Test the object.
if (dynamicProxy is IInterface resultPattern)
{
resultPattern.Property1 = "Object With Base Class And Interface And Custom Property";
}
if (dynamicProxy is User login)
{
login.Name = "dynamic proxy user";
}
foreach (var property in dynamicProxy.GetType().GetProperties())
{
Console.WriteLine($"{property.Name}: {property.GetValue(dynamicProxy)}");
}
}
为了使用我们之前创建的 `ObjectWithBaseClassAndInterfaceAndCustomProperty` 和 `ObjectWithInterface`,我们使用了长名称来举例说明。
private static void Main()
{
var dynamicObjectBuilder = new DynamicObjectBuilder("Domain_Assembly");
Console.WriteLine("***** Object With Base Class & Interface *****");
ObjectWithBaseClassAndInterfaceAndCustomProperty(dynamicObjectBuilder);
Console.ReadLine();
ObjectWithInterface(dynamicObjectBuilder);
Console.ReadLine();
}
如何在 **ASP.NET Core 3.1 依赖注入** 中注册对象。
我们创建了新的扩展来注册满足此场景所需的所有对象。
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddResultPattern(this IServiceCollection services)
{
services.AddSingleton<IObjectBuilder, SimpleDynamicObjectBuilder>();
//services.AddSingleton(typeof(IResultPatternAspect<>), typeof(ResultPatternAspect<>));
services.AddSingleton<IResultPatternAspecFactory, ResultPatternAspecFactory>();
return services;
}
}
ASP.NET CORE 中的工厂注入
在我们的主程序和 **Startup** 文件中,我们使用了这个扩展,并使用 **ASP.NET Core 3.1 工厂注入** 注册了 **服务**,以便能够使用 `ResultPatternAspec` 来创建 **服务代理**。
当我们谈论 **工厂** 时,我们指的是程序内部负责创建、实例化类并返回这些实例的机制。
public class Startup
{
public Startup(IConfiguration configuration) => Configuration = configuration;
public IConfiguration Configuration { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper
(
configAction =>
{
configAction
.CreateMap<Login, LoginDto>()
.ReverseMap()
.ForMember((dest) => dest.Id, _ => Guid.NewGuid());
// To Avoid Recursive Mapping
// configAction.ForAllMaps((map, exp) => exp.MaxDepth(2));
},
Assembly.GetExecutingAssembly()
);
services
// Call this to inject all the needs of the
// ServiceResult.AspectOriented Scenario.
.AddResultPattern()
// Inject proxy for service
.AddTransient((serviceProvider) =>
{
// Use Factory Injection In ASP.NET 3.1.
var resultPatternAspecFactory =
serviceProvider.GetService<IResultPatternAspecFactory>();
var mapper = serviceProvider.GetService<IMapper>();
// Create Service Proxy
return resultPatternAspecFactory.Create<ILoginService>
(new LoginService(mapper));
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
就像我们创建 `ServiceResultExtensions` 一样,它包含一个 `GetResult<T>` 函数,其任务是读取属性并使用我们在运行时使用 `SimpleDynamicObjectBuilder` 创建的对象来创建结果实例。
public static class ServiceResultExtensions
{
// Probably one of the exceptions we designed, so we test the exception first.
public static ServiceResult<T> GetResult<T>(this T result) =>
result is IResultPatternProxy proxy
? CreateServiceResult<T>(proxy.Kind, result,
proxy.WarningDescription, proxy.ExceptionDescriptions)
: ServiceResult<T>.Success(result);
private static ServiceResult<T> CreateServiceResult<T>
(ResultKinds kind, object result, string warning,
ExceptionDescriptions exceptionDescriptions) =>
Activator.CreateInstance(typeof(ServiceResult<>).MakeGenericType(typeof(T)),
kind, result, warning, exceptionDescriptions) as ServiceResult<T>;
}
我们会注意到 **控制器** 仍然干净、清晰,其任务仅限于处理请求。
public class LoginController : AppController<ILoginService>
{
public LoginController(ILoginService service, IWebHostEnvironment environment) :
base(service, environment) { }
[AllowAnonymous]
[HttpPost("AuthenticateAsync")]
public async Task<IActionResult> AuthenticateAsync([FromBody] LoginModel model) =>
(
await Service
.AuthenticateAsync(model.UserName, model.Password)
.ConfigureAwait(false)
)
.GetResult()
.ToActionResult(this);
[AllowAnonymous]
[HttpPost("Authenticate")]
public IActionResult Authenticate([FromBody] LoginModel model) =>
Service
.Authenticate(model.UserName, model.Password)
.GetResult()
.ToActionResult(this);
}
结论
在这篇文章中,我展示了多种向调用者返回有意义结果的方法。
这段代码在解释的场景中运行良好。如果您有此代码不起作用的情况的示例,或者有改进此代码的想法,那么我希望您以任何方式进行解释。
希望您喜欢这篇文章。请在下面的评论部分分享您的观点。
您可以在 **GitHub** 上找到源代码。
历史
- 2020年10月12日:初版