具有通用输入和输出对象转换的扩展方法
Extension method with generic input and output object casting
这个问题主要不是求助问题,而是邀请开发人员比较现代、更先进、更干净的开发方法。
在这里,我将解释我是如何解决这个问题的,并向您展示一个有效的代码示例。无论如何,我对我的解决方案有些怀疑。我真的很好奇大自然是否存在一种更优雅的方法来实现相同的代码优化。
我从这个问题开始,当时我有两个不同的控制器,除了项目 属性 类型外,它们的响应模型基本相同。
Precision: We need to have dedicated and not shared response models for each controller. In my opinion it helps in the future, when the only one controller's response have to be changed without creating side effects for the others.
我从问题开始,当时我有两个不同的控制器,除了项目 属性 类型外,响应模型基本相同。
他们是:
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));
...
一些代码示例将不胜感激。谢谢!
由于 Response
类 仅在 Item
类型上不同,因此可以使用单个通用类型而不是两个 non-generic 类型。
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));
通过在 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<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));
这个问题主要不是求助问题,而是邀请开发人员比较现代、更先进、更干净的开发方法。
在这里,我将解释我是如何解决这个问题的,并向您展示一个有效的代码示例。无论如何,我对我的解决方案有些怀疑。我真的很好奇大自然是否存在一种更优雅的方法来实现相同的代码优化。
我从这个问题开始,当时我有两个不同的控制器,除了项目 属性 类型外,它们的响应模型基本相同。
Precision: We need to have dedicated and not shared response models for each controller. In my opinion it helps in the future, when the only one controller's response have to be changed without creating side effects for the others.
我从问题开始,当时我有两个不同的控制器,除了项目 属性 类型外,响应模型基本相同。
他们是:
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
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));
...
一些代码示例将不胜感激。谢谢!
由于 Response
类 仅在 Item
类型上不同,因此可以使用单个通用类型而不是两个 non-generic 类型。
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));
通过在 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<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));