具有泛型输入和输出对象强制转换的扩展方法

问题描述

这个问题通常不是问题求助电话,而是邀请开发人员就现代,更高级和简洁的开发方法进行比较。

在这里,我解释了如何解决此问题,并向您展示了一个有效的代码示例。无论如何,我对我的解决方案有一些疑问。我真的很想知道是否存在进入大自然的更优雅的方式来实现相同的代码优化。

从这个问题开始,我有两个不同的控制器,除了Items属性类型外,它们的响应模型几乎相同。

精度:我们需要为每个控制器提供专用且不共享的响应模型。我认为,这在将来很有用,当必须更改一个控制器的响应而不会给其他控制器带来副作用时。

我从这个问题开始,当时我有两个不同的控制器,除了Items属性类型外,它们的响应模型几乎相同。

这里是:

namespace Webapi.Models.File {
    public class Response {
        public FileItem [] Items { get; set; }
        public int Page { get; set; }
        public int TotalPages { get; set; }
    }

    public class FileItem {
        ...
    }
}

namespace Webapi.Models.User {
    public class Response {
        public UserItem [] Items { get; set; }
        public int Page { get; set; }
        public int TotalPages { get; set; }
    }

    public class UserItem {
        ...
    }
}

以这种方式填充第一个模型:

using FileModel = Webapi.Models.File;

private FileModel.Response CreateItemsPage(List<FileModel.FileItem> items,int page) {
   int maxItemsPerPage = 50;
   var chunks = items.Select((v,i) => new { Value = v,Index = i })
      .GroupBy(x => x.Index / maxItemsPerPage).Select(grp => grp.Select(x => x.Value));
   
   int totalChunks = chunks.Count();

   if(totalChunks == 0) {
       return null;
   }

   page = page > 1 ? page : 1;
   page = totalChunks < page ? 1 : page;

   return new FileModel.Response() {
       Items = (chunks.ToArray())[page-1].ToArray(),Page = page,TotalPages = totalChunks
   };
}

第二种方法是完全相同的,除了输入(List )和输出usermodel.Response)类型:

using usermodel = Webapi.Models.User;

private usermodel.Response CreateItemsPage(List<usermodel.UserItem> items,Index = i })
      .GroupBy(x => x.Index / maxItemsPerPage).Select(grp => grp.Select(x => x.Value));
   
   int totalChunks = chunks.Count();

   if(totalChunks == 0) {
       return null;
   }

   page = page > 1 ? page : 1;
   page = totalChunks < page ? 1 : page;

   return new usermodel.Response() {
       Items = (chunks.ToArray())[page-1].ToArray(),TotalPages = totalChunks
   };
}

在我的webapi控制器中拥有两个甚至更多克隆的方法并不是一个很好的观点,我已经通过创建两个ObjectExtensions方法解决了这个问题。

一个只是将属性从源对象重新分配给目标对象。两者在逻辑上必须具有相同的属性名称和类型):

public static TTarget AssignProperties<TTarget,TSource>(this TTarget target,TSource source) {
    foreach (var targetProp in target.GetType().GetProperties()) {
        foreach (var sourceProp in source.GetType().GetProperties()) {
            if (targetProp.Name == sourceProp.Name && targetProp.PropertyType == sourceProp.PropertyType) {
                targetProp.SetValue(target,sourceProp.GetValue(source));
                    break;
                }
            }
        }

     return target;
 }

第二个对象接收目标对象和源对象,在内部创建一个匿名对象,然后使用先前的扩展方法AssignProperties将其属性重新分配给目标对象(之所以需要这样做,是因为无法直接访问通用对象属性):

public static TTarget CreateItemsPage<TTarget,List<TSource> items,int page = 1) {
    int maxItemsPerPage = 50;

    var chunks = items.Select((v,Index = i })
        .GroupBy(x => x.Index / maxItemsPerPage).Select(grp => grp.Select(x => x.Value));

    int totalChunks = chunks.Count();

    if(totalChunks == 0) {
        return target;
    }

    page = page > 1 ? page : 1;
    page = totalChunks < page ? 1 : page;

    var source =  new {
        Items = (chunks.ToArray())[page-1].ToArray(),TotalPages = totalChunks
    };

    target = target.AssignProperties(source);

    return target;
}

这是用法

...
var items = _filesService.ListAllUserFiles(userId,requestData.SearchText);

if(pages.Count() == 0)
   return BadRequest();

return Ok(new FileModel.Response().CreateItemsPage(items,requestData.Page));
...

一些代码示例将不胜感激。谢谢!

解决方法

通过在Reddit上进行讨论,我得出了以下解决方案,该解决方案使我能够保持模型分离并删除效率低下的AssignProperties方法。

接口:

public interface IPaginationResponse<TItem> {
    TItem[] Items { get; set; }
    int Page { get; set; }
    int TotalPages { get; set; }
}

模型示例:

public class Response: IPaginationResponse<Info> {
   public Info [] Items { get; set; }
   public int Page { get; set; }
   public int TotalPages { get; set; }
   ...
}

public class Response: IPaginationResponse<UserFile> {
   public UserFile [] Items { get; set; }
   public int Page { get; set; }
   public int TotalPages { get; set; }
   ...
}

public class Response: IPaginationResponse<UserItem> {
   public UserItem [] Items { get; set; }
   public int Page { get; set; }
   public int TotalPages { get; set; }
   ...
}

现在,我终于从AssignProperties扩展方法中删除了CreateItemsPage。感谢where TTarget : IPaginationResponse<TSource>,我可以直接将值分配给TTarget target

public static TTarget CreateItemsPage<TTarget,TSource>(this TTarget target,IEnumerable<TSource> items,int page = 1) where TTarget : IPaginationResponse<TSource> {
   ...

   target.Items = (chunks.ToArray())[page-1].ToArray();
   target.Page = page;
   target.TotalPages = totalChunks;

   return target;
}

内部控制器我以相同的方式调用它

return Ok(new FileModel.Response().CreateItemsPage(pages,requestData.Page));
,

由于两个Response类仅在Item类型上不同,因此可以使用单个通用类型代替两个非通用类型。

public class ResponsePageTemplate<TItem>
{
    public TItem[] Items { get; set; }
    public int Page { get; set; }
    public int TotalPages { get; set; }
}

然后扩展方法如下:

public static ResponsePageTemplate<TSource> CreateItemsPage<TSource>(this IEnumerable<TSource> items,int page = 1)
{
    int maxItemsPerPage = 50;

    var chunks = items.Select((v,i) => new {Value = v,Index = i})
        .GroupBy(x => x.Index / maxItemsPerPage).Select(grp => grp.Select(x => x.Value));

    int totalChunks = chunks.Count();

    if (totalChunks == 0)
    {
        return new ResponsePageTemplate<TSource>();
    }

    page = page > 1 ? page : 1;
    page = totalChunks < page ? 1 : page;

    var result = new ResponsePageTemplate<TSource>
    {
        Items = (chunks.ToArray())[page - 1].ToArray(),Page = page,TotalPages = totalChunks
    };

    return result;
}

用法如下:

return Ok(items.CreateItemsPage(requestData.Page));