Net Core:以动态变量方式将DTO包装在响应模式中

Net Core: Wrap DTO in Response Pattern in Dynamic Variable Way

软件要求要求所有 DTO 包含它们自己的响应 Class。所以开发人员基本上将 Product DTO 包装在 Base Response class 中。我们有完整的 class 区域列表,需要数百个 class 产品、销售、客户等都做同样的事情,如下所示。客户不想换行为 BaseResponse<Product> or BaseResponse<IEnumerable<ProductDto> 因为它是 nested/unreadable.

是否有wrap/create变量classes且可读的方法,无需手动编写100s的class(可能是扩展方法,动态class,变量,不确定, 对任何方法开放)?

注意:响应 classes 可以不同,所以想给程序员选择创建自动化标准 class,或者创建自己的自定义手册 class,所以可以存在两个选项.

当前代码:

产品 DTO Class:

public class ProductDto
{
    public int ProductId { get; set;},
    public string ProductName { get; set;},
    public string ProductDescription { get; set;},
    public float SalesAmount { get; set;}
}

基础响应:

public class BaseResponse<T>
{
    [Required, ValidateObject]
    public T Body { get; set; }
    public bool HasError { get; set; }
    public string Error { get; set; }
}

个人回复:

public class GetAllProductResponse : BaseResponse<IEnumerable<ProductDto>>
{
}

public class GetProductResponse : BaseResponse<ProductDto>
{
}

public class UpdateProductResponse : BaseResponse<ProductDto>
{
}

建议代码:

public static class ResponseExtensions
{
    public static BaseRequestResponse<T> GetAllResponse<T> (this T obj) where T:class
    {
        return BaseRequestResponse<IEnumerable<T>>;
    }

    public static BaseRequestResponse<T> GetResponse<T>(this T obj) where T : class
    {
        return BaseRequestResponse<T>;
    }

    public static BaseRequestResponse<T> UpdateResponse<T>(this T obj) where T : class
    {
        return BaseRequestResponse<T>;
    }
}

所以代码现在看起来像这样,

ProductDTO.GetAllResponse
ProductDTO.GetResponse
ProductDTO.UpdateResponse

这是一个好的方法,架构合理,还是应该应用其他方法? 这可能行不通,因为任何中间层 sending/receiving 响应都需要引用为 BaseResponse< IEnumerable< ProductDto > 等

顺便说一下,如果走这条路,这里会收到编译错误

'BaseRequestResponse<T>' is a type, which is not valid in the given context 

更新: 这就是我们使用 DTO 和 Response

的方式
public async Task<ActionResult<GetProductResponse>> GetByProduct(int id)
{
    try
    {
        var productdto = await productAppService.GetProductById(id);
        var response = new GetProductResponse { Body = productdto };
        return Ok(response);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, ex.Message);
        var response = new GetDocumentResponse { HasError = true, Error = ex.Message };
        return StatusCode(StatusCodes.Status500InternalServerError, response);
    }
}

您应该Return BaseRequestResponse 类型的对象。

public static class ResponseExtensions
{
    public static BaseRequestResponse<T> GetAllResponse<T> (this T obj) where T:class
    {
        return //should return a object instade of type 
    }

    public static BaseRequestResponse<T> GetResponse<T>(this T obj) where T : class
    {
        return //should return a object instade of type 
    }

    public static BaseRequestResponse<T> UpdateResponse<T>(this T obj) where T : class
    {
        return //should return a object instade of type 
    }
}

除非您确实需要您的控制器提及这些特定类型,否则更灵活的解决方案是使用 result filters。结果过滤器 运行 在控制器操作之后,允许您转换和替换生成的结果。

我已经在 related answer 中介绍了这个想法的实现。

然后您可以应用该过滤器,例如使用 [TypeFilter] 属性:

[TypeFilter(typeof(ApiResultFilter))]
public ActionResult<ProductDto> GetProduct(int id)
{
    //
    return product;
}

这也可以作为控制器的一个属性,将其应用于装饰控制器的所有操作。

或者通过在您的 Startup:

中配置它,将其全局应用于所有操作结果
services.AddMvc(options =>
{
    options.Filters.Add(new ApiResultFilter());
});

关于您的错误,您编写的扩展只是 return SomeType。但是您实际上必须创建 BaseResponse<T> 类型并将结果对象放入其中,例如:

public static BaseRequestResponse<T> GetResponse<T>(this T obj) where T : class
{
    return new BaseRequestResponse<T>()
    {
        Body = obj,
    };
}

关于您发布的“DTO 和响应”示例,这正是您使用结果过滤器的目的。这个想法是使控制器动作尽可能具体。任何样板文件都应该提取到可以重复使用的过滤器中。所以最后的动作可能是这样的:

public async Task<ActionResult<Product>> GetByProduct(int id)
{
    return await productAppService.GetProductById(id);
}

然后您将使用 result filter to wrap that product with your wrapper type, and use exception filters 来处理 return 自定义错误响应对象的异常。