我应该如何从服务/用例类返回结果

问题描述 投票:0回答:2

在使用 C# 的 REST API 开发中,我遇到了一个问题:如果控制器使用的服务返回错误,但该错误取决于有关某些内容的业务规则,我应该如何以及在哪里确定在HTTP 响应?

我的意思是,这应该是服务的责任,还是控制器应该根据服务结果确定返回什么状态码?

现在,在我的团队中,我们让服务通过来自名为 Response 的类的通用包装对象返回状态代码和消息以及有效负载。因此,当我们想要返回用户列表时,该服务会执行类似于

的操作
if (users?.Any() ?? true)
    return new Response()
    {
        Message = "There are no users.",
        Status = HttpStatusCodes.Status204NoContent
    }

return new Response()
{
    Message = "Users retrieved successfully!",
    Status = HttpStatusCodes.Status200OK,    
    Data = users
}

但是,尽管这种方法可行,但对我来说看起来不太好,因为从 ServiceA 到 ServiceB 调用一个方法很奇怪,因为有时 HTTP 状态代码不必要地组合在一起。

一种替代方法是将一些结果对象从服务返回到控制器,但在中间件中进行一些数据处理,将结果对象转换为包含状态代码的有效响应对象。

所以可能是这样的:

// Result object returned by services
public class Result<T>
{
    public string Message { get; }
    public ResultReason Reason { get; }
    public T Data { get; set; }
}

public enum ResultReason
{
    BusinessRuleViolation, ItemNotFound, Success, InvalidParameter
}

// Response object returned by a middleware as an HTTP response body
public class Response<T>
{
    public string Message { get; }
    public int StatusCode { get; }
    public T Data { get; set; }
}

public static class ResponseMapper
{
    public static Response<T> ToResponse<T>(this Result<T> result)
    {
        return new Response()
        {
            Data = result.Data,
            Message = result.Message,
            StatusCode = result.Reason == ResultReason.BusinessRuleViolation ? 422
                : result.Reason == ResultReason.ItemNotFound ? 204
                : result.Reason == ResultReason.Success ? 200
                : result.Reason == ResultReason.InvalidParameter ? 400
        }
    }
}

如果你们能够带来来自 DDD、简洁架构、GoF 设计模式、简洁代码或类似内容的概念和想法,我会非常高兴:)

注意:上面的例子不一定有效,我只是为了演示目的而写的

c# .net rest .net-core design-patterns
2个回答
0
投票

您可以使用包Middleware中的问题详细信息中间件。有关问题详情您可以查看微软文档问题详情服务

基本思想是,您可以在域层中拥有全局自定义异常,您可以使用问题详细信息中间件将其映射到业务规则特定异常。

因此,您将将此中间件添加到 asp net core 项目中的管道中。此外,您可以简单地配置您的例外,如下所示(示例用例)

public static IServiceCollection AddCustomProblemDetails(this IServiceCollection services)
{
    services.AddProblemDetails(problemDetailsOptions =>
    {
        problemDetailsOptions.MapToStatusCodeWithErrorMessage<SomeCustomException>(StatusCodes.Status409Conflict);
    });

    return services;
}
private static void MapToStatusCodeWithErrorMessage<TException>(
    this ProblemDetailsOptions options,
    int statusCode,
    LogLevel logLevel = LogLevel.Debug)
    where TException : Exception
{
    ExceptionsLogLevels.Add(typeof(TException).FullName!, logLevel);
    options.Map<TException>(
        exception =>
        {
            var problemDetails = StatusCodeProblemDetails.Create(statusCode);
            problemDetails.Detail = exception.Message;
            return problemDetails;
        });
}

然后,您可以以最小的 API 将其添加到配置服务中的管道(如果您使用旧方式),您必须在程序 cs 文件中执行此操作。

services.AddCustomProblemDetails();

以及配置方法内部

app.UseProblemDetails();

现在,您基本上可以在服务层中抛出业务领域特定的自定义异常,并且您的控制器非常精简。

上面的示例基于您将使用我在此答案中链接的中间件包的假设:)


0
投票

在服务中使用此结果类。

    public class Result<T>
{
    public bool IsSuccess { get; private set; }
    public bool IsFailure { get; private set; }
    public T? Value { get; private set; }
    public string[]? Errors { get; private set; }

    private Result() { }

    public static Result<T> Success(T value) => new Result<T>() { IsSuccess = true, IsFailure = false, Value = value };
    public static Result<T> Failure(params string[] errors) => new Result<T>() { IsSuccess = false, IsFailure = true, Errors = errors };
}

服务层可以从不支持 http 的其他功能中使用。然后在外层你可以决定返回什么http代码。

© www.soinside.com 2019 - 2024. All rights reserved.