【问题标题】:Extension method with generic input and output object casting具有通用输入和输出对象转换的扩展方法
【发布时间】:2020-09-05 20:31:12
【问题描述】:

这个问题主要不是问题帮助电话,而是邀请开发人员就当代、更先进和更清洁的开发方法进行比较。

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

当我有两个不同的控制器时,我从这个问题开始,除了 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, 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 UserModel.Response() {
       Items = (chunks.ToArray())[page-1].ToArray(),
       Page = page,
       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, TSource>(this TTarget target, List<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 target;
    }

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

    var source =  new {
        Items = (chunks.ToArray())[page-1].ToArray(),
        Page = page,
        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));
...

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

【问题讨论】:

    标签: c# .net generics extension-methods


    【解决方案1】:

    通过在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; }
       ...
    }
    

    现在我终于从CreateItemsPage 扩展方法中删除了AssignProperties。感谢where TTarget : IPaginationResponse&lt;TSource&gt;,我可以直接将值赋值给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));
    

    【讨论】:

      【解决方案2】:

      由于两个 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));
      

      【讨论】:

      • 非常感谢您花时间写下这个答案!在多个控制器之间共享相同的模型可以简化当前的设置。抱歉,这是我的错误,我应该在问题中指定(现在我将对其进行编辑),还需要为每个控制器提供专用的响应模型。在我看来,保留专用的响应模型对将来很有帮助,因为必须更改唯一一个控制器的响应,而不会对其他控制器产生副作用。
      • 一直在研究你的提议,我喜欢你如何改变(这个 List 到(这个 IEnumerable
      猜你喜欢
      • 2010-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-11
      • 2020-02-07
      • 2017-12-26
      • 2022-01-20
      相关资源
      最近更新 更多