鼎鼎知识库
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

5、Result模式,一站式解决响应问题.md 7.3KB

引出Result模式

通常的方式

public Customer GetCustomer(int customerId)
{
  // more logic
  return customer;
}

public Customer CreateCustomer(string firstName, string lastName)
{
  // more logic
  return customer;
}

在逻辑中,我们可能会判断customerId不存在,判断lastName没有提供,甚至当前用户是否有权限创建用户。

我们在处理正常和异常这两种情况。

Result模式用来对这两种情况进行封装,并且统一了返回结果的格式。

public async Task<Result<BlogCategory>> UpdateAsync(BlogCategory blogCategory)
{
    if (Guid.Empty == blogCategory.BlogCategoryId) return Result<BlogCategory>.NotFound();//没有找到

    var validator = new BlogCategoryValidator();
    var validation = await validator.ValidateAsync(blogCategory);
    if (!validation.IsValid)
    {
        return Result<BlogCategory>.Invalid(validation.AsErrors());//验证异常
    }

    var itemToUpdate = (await GetByIdAsync(blogCategory.BlogCategoryId)).Value;
    if (itemToUpdate == null)
    {
        return Result<BlogCategory>.NotFound();
    }

    itemToUpdate.Update(blogCategory.Name, blogCategory.ParentId);

    return Result<BlogCategory>.Success(await _blogCategoryRepository.UpdateAsync(itemToUpdate));
}

结果

ddd12

接口

    [ApiController]
    [Route("mediatr/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IMediator _mediator;
        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(
            IMediator mediator,
            ILogger<WeatherForecastController> logger)
        {
            _mediator = mediator;
            _logger = logger;
        }

        /// <summary>
        /// This uses a filter to convert an Ardalis.Result return type to an ActionResult.
        /// This filter could be used per controller or globally!
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [TranslateResultToActionResult]//把Result的结果转换成ActionResult的结果,可以定义我们自己的特性,这点特别重要
        [HttpPost("Create")]
        public Task<Result<IEnumerable<WeatherForecast>>> CreateForecast([FromBody] NewForecastCommand model)
        {
            // One might try to perform translation from Result<T> to an appropriate IActionResult from within a MediatR pipeline
            // Unfortunately without having Result<T> depend on IActionResult there doesn't appear to be a way to do this, so this
            // example is still using the TranslateResultToActionResult filter.
            return _mediator.Send(model);
        }

        public class NewForecastCommand : IRequest<Result<IEnumerable<WeatherForecast>>>
        {
            [Required]
            public string PostalCode { get; set; }
        }

        public class NewForecastHandler : IRequestHandler<NewForecastCommand, Result<IEnumerable<WeatherForecast>>>
        {
            private readonly WeatherService _weatherService;

            public NewForecastHandler(WeatherService weatherService)
            {
                _weatherService = weatherService;
            }
            public Task<Result<IEnumerable<WeatherForecast>>> Handle(NewForecastCommand request, CancellationToken cancellationToken)
            {
                var result = _weatherService.GetForecastAsync(new ForecastRequestDto { PostalCode = request.PostalCode });
                return result;
            }
        }
    }

WeatherService

    public class WeatherService
    {
        public WeatherService(IStringLocalizer<WeatherService> stringLocalizer)
        {
            _stringLocalizer = stringLocalizer;
        }
        private static readonly string[] Summaries = new[]
{
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private IStringLocalizer<WeatherService> _stringLocalizer;

        public Task<Result<IEnumerable<WeatherForecast>>> GetForecastAsync(ForecastRequestDto model)
        {
            return Task.FromResult(GetForecast(model));
        }

        public Result<IEnumerable<WeatherForecast>> GetForecast(ForecastRequestDto model)
        {
            if (model.PostalCode == "NotFound") return Result<IEnumerable<WeatherForecast>>.NotFound();//没有找到

            // validate model
            if (model.PostalCode.Length > 10)
            {
                return Result<IEnumerable<WeatherForecast>>.Invalid(new List<ValidationError> {
                    new ValidationError
                    {
                        Identifier = nameof(model.PostalCode),
                        ErrorMessage = _stringLocalizer["PostalCode cannot exceed 10 characters."].Value }
                });//模型验证失败
            }

            // test value
            if (model.PostalCode == "55555")
            {
                return new Result<IEnumerable<WeatherForecast>>(Enumerable.Range(1, 1)//返回成功的结果
                    .Select(index =>
                    new WeatherForecast
                    {
                        Date = DateTime.Now,
                        TemperatureC = 0,
                        Summary = Summaries[0]
                    }));
            }

            var rng = new Random();
            return new Result<IEnumerable<WeatherForecast>>(Enumerable.Range(1, 5)//返回成功的结果
                .Select(index => new WeatherForecast
                {
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = rng.Next(-20, 55),
                    Summary = Summaries[rng.Next(Summaries.Length)]
                })
            .ToArray());
        }
    }

如何影射Result结果呢?

   public class TranslateResultToActionResultAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            if (!((context.Result as ObjectResult)?.Value is IResult result)) return;

            if (!(context.Controller is ControllerBase controller)) return;

			//判断IResult的ResultStatus
            if (result.Status == ResultStatus.NotFound)
                context.Result = controller.NotFound();

            if (result.Status == ResultStatus.Invalid)
            {
                foreach (var error in result.ValidationErrors)
                {
                    // TODO: Fix after updating to 3.0.0
                    (context.Controller as ControllerBase)?.ModelState.AddModelError(error.Identifier, error.ErrorMessage);
                }

                context.Result = controller.BadRequest(controller.ModelState);
            }

            if (result.Status == ResultStatus.Ok)
            {
                context.Result = new OkObjectResult(result.GetValue());
            }
        }
    }

IResult

    public interface IResult
    {
        ResultStatus Status { get; }
        IEnumerable<string> Errors { get; }
        List<ValidationError> ValidationErrors { get; }
        Type ValueType { get; }
        Object GetValue();
    }

ResultStatus

    public enum ResultStatus
    {
        Ok,
        Error,
        Forbidden,
        Invalid,
        NotFound
    }