Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
180 views
in Technique[技术] by (71.8m points)

c# - Net Core: Wrap DTO in Response Pattern in Dynamic Variable Way

Software requirements is asking all DTOs contain their own Response Class. So developers basically wrap Product DTO in Base Response class. We have whole list of class areas, requiring hundreds of classes for Product, Sales, Customer, etc all doing same thing, per below. Client does not want to wrap as BaseResponse<Product> or BaseResponse<IEnumerable<ProductDto> since it is nested/unreadable.

Is there method to wrap/create variable classes and readable, without manually writing 100s of class (maybe extension method, dynamic class, variable, not sure, open to any method)?

Note: Response classes can be different, so want to give programmer option to create automated standard class, or create own customized manual class, so two options can exist.

Current Code:

Product 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;}
}

BaseResponse:

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

Individual Response:

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

public class GetProductResponse : BaseResponse<ProductDto>
{
}

public class UpdateProductResponse : BaseResponse<ProductDto>
{
}

Proposed Code:

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>;
    }
}

So the code will now look as this,

ProductDTO.GetAllResponse
ProductDTO.GetResponse
ProductDTO.UpdateResponse

Is this a good method, architecturally sound, or should something else be applied? This probably will not work, since any middle tier layer sending/receiving response will need refer to as BaseResponse< IEnumerable< ProductDto >, etc.

By the way,if going this route, receiving compilation error here

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

Update: This is how we use DTO and 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);
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Unless you actually need your controllers to mention those specific types, a more flexible solution would be to use result filters. Result filters run after the controller action and allow you to transform and replace the result that waas produced.

I’ve covered the implementation of this idea in this related answer.

You can then apply that filter, e.g. using a [TypeFilter] attribute:

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

This also works as an attribute on the controller, to apply it to all actions of the decorated controller.

Or apply it globally for all action results by configuring it in your Startup:

services.AddMvc(options =>
{
    options.Filters.Add(new ApiResultFilter());
});

Regarding your error, the extensions you wrote just do return SomeType. But you will actually have to create your BaseResponse<T> type and place the result object into it, e.g.:

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

In regards to your “DTO and response” example you posted, that’s exactly what you would use result filters for. The idea is to make controller actions as concrete as possible. Any boilerplate stuff should be extracted into filters that can be reused. So the final action may look like this:

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

You would then use a result filter to wrap that product with your wrapper type, and use exception filters to handle exceptions to return a custom error response object instead.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...