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));
}
结果
[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
}