在 MVC 5 中构建动态 JSON 响应 - 如何将属性附加到结果集?
Building a dynamic JSON response in MVC 5 - How to tack on properties to a result set?
我正在 return 将一组对象添加到一个页面,该页面根据相册呈现幻灯片。
我从数据库中获取我的照片。
在我return这个数组之前,我想加上一个"Thumbnail"url属性。此 属性 在 AlbumPicture 上不存在,但我希望它出现在响应中。
这说明了这个想法:
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
foreach(AlbumPicture p in pics)
{
p.AddPropertyThatDoesntExist("Thumbnail", ThumbManager.GetThumb(p.ID));
}
return Json(pics, JsonRequestBehavior.AllowGet);
将此 JSON 字段添加到我的结果集中的最优雅方法是什么?
这个问题太基础了,可能是重复的。然而,我用谷歌搜索了 10 分钟,只能找到依赖于 3rd 方库的 janky 解决方案。我对当前的 "best practice".
感兴趣
可能重复:How to add dynamically more properties to Json response from the controller。但是,该答案将使动态添加的字段成为我的 AlbumPicture 属性的叔叔而不是兄弟姐妹 JSON.
目前,C# 4.0 支持动态对象,因此您可以使用动态对象来添加您的属性作为 运行 时间。
首先,将此扩展方法添加到您的代码中:
public static class DynamicExtensions
{
public static dynamic ToDynamic(this object value)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
expando.Add(property.Name, property.GetValue(value));
return expando as ExpandoObject;
}
}
然后,将您的代码更改为如下内容
var pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p=>
{ dynamic copy = p.ToDynamic();
copy.Thumbnail = ThumbManager.GetThumb(p.ID); //You can add more dynamic properties here
return copy;
})
.ToList();
return Json(pics, JsonRequestBehavior.AllowGet);
conanak99 提供的答案在我看来是正确的做法。无论如何,像这样的扩展通常隐藏在您的核心实用程序中,因此他的答案具有可重用性。如果你确定你只会将它用于这个目的,你可以将它重构为
public static dynamic AppendProperty<T>(this object value, string propertyName, T newValue)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
expando.Add(property.Name, property.GetValue(value));
expando.Add(propertyName, newValue);
return expando as ExpandoObject;
}
这将依次产生以下代码来执行您想要的操作:
var pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p =>
{
return p.AppendProperty("Thumbnail", ThumbManager.GetThumb(p.ID));
})
.ToList();
更新----------------------------
如果您正在使用 automapper,或者愿意安装它,这是另一个可以用来达到相同结果的解决方法。
首先,您创建相册的扩展 class,添加 属性
class AlbumExt : Album
{
public string Thumbnail { get; set; }
}
然后在 select 语句中创建扩展 class 的新实例,使用自动映射器复制 属性 值(如果没有太多,则手动复制) 进入新的 class,并设置缩略图 属性.
var pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p =>
{
AlbumExt tmp = new AlbumExt();
AutoMapper.Mapper.DynamicMap(p, tmp);
tmp.Thumbnail = "new prop value";
return tmp;
})
.ToList();
Automapper 基本上只是使用默认约定将 属性 值从一个 class 复制到另一个 属性,如果需要可以覆盖这些默认约定。
我同意上面的大部分答案,使用动态对象或 expando 对象你可以添加额外的 属性 等。然而任务很简单,在我看来你永远不应该将你的实体对象暴露给 UI 或消费者。我会创建一个简单的 ViewModel 并将其传回给调用者,如下所示:
Public Class Album{
string PhotoUrl {get; set;}
string ThumbnailUrl {get; set;}
}
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
List<Album> albums = new List<Album>();
foreach(AlbumPicture p in pics)
{
albums.add(new Album(){
PhotoUrl = p.PhotoUrl,//your photo url
ThumbnailUrl = p.ThumbnailUrl
});
}
return Json(albums, JsonRequestBehavior.AllowGet);
不是您问题的直接答案,但在我看来,您正在使用类似 Entity Framework 的方法通过 "database first" 方法处理数据库连接。现在您不想在数据库中添加 "Thumbnail" 属性,也不想更改动态创建的模型。
现在我认为最好的方法是将 属性 添加到模型中,而不仅仅是添加到 JSON 中。在我看来,这将使它更易于使用,并且还可以在需要时直接在视图中使用缩略图。
我会在名为 ExtendedModels.cs
的数据库项目中添加一个 class 并添加如下内容
/// <summary>
/// This class is used to extend the AlbumPicture object from the database
/// </summary>
/// <remarks>
/// 2015-01-20: Created
/// </remarks>
public partial class AlbumPicture
{
/// <summary>An URL to the thumbnail.</summary>
public string Thumbnail
{
get
{
//ImageName is the actual image from the AlbumPicture object in the database
if (string.IsNullOrEmpty(ImageName))
{
return null;
}
return string.Format("/Thumbnail/{0}", ImageName);
}
}
}
显然它也会被添加到 JSON 输出中。
更新
如果您先使用代码,则可以使用 NotMapped
注释来确保未在数据库中创建 属性。有关不同版本的更多详细信息,请参阅 Ignoring a class property in Entity Framework。因此,在您的情况下,它将类似于以下内容。您可以将 get/set 更改为任何您想要的。
public class AlbumPicture
{
public string ImageName { get; set; }
[NotMapped]
public string Thumbnail{ get; set; }
}
使用投影和匿名对象将动态属性与域属性分开。
var pics = db.AlbumPictures
.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p => new { album = p, Thumbnail = ThumbManager.GetThumb(p.ID))
.ToList();
您可以使用匿名类型创建具有 AlbumPicture 和 Thumbnail 属性的新类型。
这是修改后的代码。
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
var lstPics = new List<object>();
foreach (AlbumPicture p in pics)
{
lstPics.Add(new { AlbumPicture = p, Thumbnail = ThumbManager.GetThumb(p.ID) });
}
return Json(lstPics, JsonRequestBehavior.AllowGet);
你会得到 json 这样的结果
[{"AlbumPicture":{"ID":1,"AlbumID":1,"RankOrder":1,,,,},"Thumbnail":"Image_1"},{"AlbumPicture":{"ID":2,"AlbumID":2,"RankOrder":2,,,,},"Thumbnail":"Image_2"},{"AlbumPicture":{"ID":3,"AlbumID":3,"RankOrder":3,,,,},"Thumbnail":"Image_3"}]
希望对你有所帮助
您可以使用 Json.NET 将它们转换为其对象,并在创建的 JObject 上动态添加属性。
从那里您可以return json 字符串作为操作的内容。
var pics = db.AlbumPictures.Where(x => x.ID == albumID);
//Will be used to hold each picture in the sequence
JArray dynamicPics = new JArray();
foreach (var pic in pics)
{
//Convert your object to JObject
JObject dynamicPic = JObject.FromObject(pic);
//Create your dynamic properties here.
dynamicPic["Thumbnail"] = ThumbManager.GetThumb(pic.ID);
dynamicPics.Add(dynamicPic);
}
//Convert the dynamic pics to the json string.
string json = dynamicPics.ToString();
//Return the json as content, with the type specified.
return this.Content(json, "application/json");
我建议根据您的 AlbumPicture 对象创建一个数据传输对象。
public class AlbumPictureDto
{
public int id { get; set; }
public string url{ get; set; }
public string thumbnailUrl{ get; set; } //create new properties
public new static readonly Func<AlbumPicture, AlbumPictureDto> AsDto = p => new AlbumPictureDto()
{
id = p.id,
url= p.url,
thumbnailUrl= ThumbManager.GetThumb(p.id)
};
}
//控制器代码
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
return Json(pics.Select(AlbumPicturesDto.AsDto),JsonRequestBehavior.AllowGet);
您可以向 Dto 对象添加任意数量的属性,并使用此方法将这些属性 return 提供给客户端。
并不是说 .NET 在 JSON 方面不好,只是它是一种强类型语言,这意味着在处理动态时你不能将它与 javascript 这样的动态语言进行比较类型操作。
在我看来,获得所需结果的最佳方法是为客户端创建新的 类。直接使用域模型与客户端通信通常不是一个好主意。如果您希望尽可能减少代码重复,您充其量可以简单地创建继承域模型的 ViewModel。但正如所说,您可能不想将模型的所有属性公开给您的客户。
如果打算进行完整的模型展示,那么您可以像@conanak99 在他的出色回答中指出的那样做。使用 LINQ 形式的代码可能会更优雅:
var pics = from album in db.AlbumPictures
where album.AlbumID == album.ID
orderby album.RankOrder
let dynamicAlbum = album.ToDynamic()
dynamicAlbum.Thumbnail = ThumbManager.GetThumb(p.ID)
select dynamicAlbum;
我正在 return 将一组对象添加到一个页面,该页面根据相册呈现幻灯片。
我从数据库中获取我的照片。
在我return这个数组之前,我想加上一个"Thumbnail"url属性。此 属性 在 AlbumPicture 上不存在,但我希望它出现在响应中。
这说明了这个想法:
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
foreach(AlbumPicture p in pics)
{
p.AddPropertyThatDoesntExist("Thumbnail", ThumbManager.GetThumb(p.ID));
}
return Json(pics, JsonRequestBehavior.AllowGet);
将此 JSON 字段添加到我的结果集中的最优雅方法是什么?
这个问题太基础了,可能是重复的。然而,我用谷歌搜索了 10 分钟,只能找到依赖于 3rd 方库的 janky 解决方案。我对当前的 "best practice".
感兴趣可能重复:How to add dynamically more properties to Json response from the controller。但是,该答案将使动态添加的字段成为我的 AlbumPicture 属性的叔叔而不是兄弟姐妹 JSON.
目前,C# 4.0 支持动态对象,因此您可以使用动态对象来添加您的属性作为 运行 时间。
首先,将此扩展方法添加到您的代码中:
public static class DynamicExtensions
{
public static dynamic ToDynamic(this object value)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
expando.Add(property.Name, property.GetValue(value));
return expando as ExpandoObject;
}
}
然后,将您的代码更改为如下内容
var pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p=>
{ dynamic copy = p.ToDynamic();
copy.Thumbnail = ThumbManager.GetThumb(p.ID); //You can add more dynamic properties here
return copy;
})
.ToList();
return Json(pics, JsonRequestBehavior.AllowGet);
conanak99 提供的答案在我看来是正确的做法。无论如何,像这样的扩展通常隐藏在您的核心实用程序中,因此他的答案具有可重用性。如果你确定你只会将它用于这个目的,你可以将它重构为
public static dynamic AppendProperty<T>(this object value, string propertyName, T newValue)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
expando.Add(property.Name, property.GetValue(value));
expando.Add(propertyName, newValue);
return expando as ExpandoObject;
}
这将依次产生以下代码来执行您想要的操作:
var pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p =>
{
return p.AppendProperty("Thumbnail", ThumbManager.GetThumb(p.ID));
})
.ToList();
更新----------------------------
如果您正在使用 automapper,或者愿意安装它,这是另一个可以用来达到相同结果的解决方法。
首先,您创建相册的扩展 class,添加 属性
class AlbumExt : Album
{
public string Thumbnail { get; set; }
}
然后在 select 语句中创建扩展 class 的新实例,使用自动映射器复制 属性 值(如果没有太多,则手动复制) 进入新的 class,并设置缩略图 属性.
var pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p =>
{
AlbumExt tmp = new AlbumExt();
AutoMapper.Mapper.DynamicMap(p, tmp);
tmp.Thumbnail = "new prop value";
return tmp;
})
.ToList();
Automapper 基本上只是使用默认约定将 属性 值从一个 class 复制到另一个 属性,如果需要可以覆盖这些默认约定。
我同意上面的大部分答案,使用动态对象或 expando 对象你可以添加额外的 属性 等。然而任务很简单,在我看来你永远不应该将你的实体对象暴露给 UI 或消费者。我会创建一个简单的 ViewModel 并将其传回给调用者,如下所示:
Public Class Album{
string PhotoUrl {get; set;}
string ThumbnailUrl {get; set;}
}
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
List<Album> albums = new List<Album>();
foreach(AlbumPicture p in pics)
{
albums.add(new Album(){
PhotoUrl = p.PhotoUrl,//your photo url
ThumbnailUrl = p.ThumbnailUrl
});
}
return Json(albums, JsonRequestBehavior.AllowGet);
不是您问题的直接答案,但在我看来,您正在使用类似 Entity Framework 的方法通过 "database first" 方法处理数据库连接。现在您不想在数据库中添加 "Thumbnail" 属性,也不想更改动态创建的模型。
现在我认为最好的方法是将 属性 添加到模型中,而不仅仅是添加到 JSON 中。在我看来,这将使它更易于使用,并且还可以在需要时直接在视图中使用缩略图。
我会在名为 ExtendedModels.cs
的数据库项目中添加一个 class 并添加如下内容
/// <summary>
/// This class is used to extend the AlbumPicture object from the database
/// </summary>
/// <remarks>
/// 2015-01-20: Created
/// </remarks>
public partial class AlbumPicture
{
/// <summary>An URL to the thumbnail.</summary>
public string Thumbnail
{
get
{
//ImageName is the actual image from the AlbumPicture object in the database
if (string.IsNullOrEmpty(ImageName))
{
return null;
}
return string.Format("/Thumbnail/{0}", ImageName);
}
}
}
显然它也会被添加到 JSON 输出中。
更新
如果您先使用代码,则可以使用 NotMapped
注释来确保未在数据库中创建 属性。有关不同版本的更多详细信息,请参阅 Ignoring a class property in Entity Framework。因此,在您的情况下,它将类似于以下内容。您可以将 get/set 更改为任何您想要的。
public class AlbumPicture
{
public string ImageName { get; set; }
[NotMapped]
public string Thumbnail{ get; set; }
}
使用投影和匿名对象将动态属性与域属性分开。
var pics = db.AlbumPictures
.Where(p => p.AlbumID == album.ID)
.OrderBy(p => p.RankOrder)
.Select(p => new { album = p, Thumbnail = ThumbManager.GetThumb(p.ID))
.ToList();
您可以使用匿名类型创建具有 AlbumPicture 和 Thumbnail 属性的新类型。
这是修改后的代码。
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
var lstPics = new List<object>();
foreach (AlbumPicture p in pics)
{
lstPics.Add(new { AlbumPicture = p, Thumbnail = ThumbManager.GetThumb(p.ID) });
}
return Json(lstPics, JsonRequestBehavior.AllowGet);
你会得到 json 这样的结果
[{"AlbumPicture":{"ID":1,"AlbumID":1,"RankOrder":1,,,,},"Thumbnail":"Image_1"},{"AlbumPicture":{"ID":2,"AlbumID":2,"RankOrder":2,,,,},"Thumbnail":"Image_2"},{"AlbumPicture":{"ID":3,"AlbumID":3,"RankOrder":3,,,,},"Thumbnail":"Image_3"}]
希望对你有所帮助
您可以使用 Json.NET 将它们转换为其对象,并在创建的 JObject 上动态添加属性。
从那里您可以return json 字符串作为操作的内容。
var pics = db.AlbumPictures.Where(x => x.ID == albumID);
//Will be used to hold each picture in the sequence
JArray dynamicPics = new JArray();
foreach (var pic in pics)
{
//Convert your object to JObject
JObject dynamicPic = JObject.FromObject(pic);
//Create your dynamic properties here.
dynamicPic["Thumbnail"] = ThumbManager.GetThumb(pic.ID);
dynamicPics.Add(dynamicPic);
}
//Convert the dynamic pics to the json string.
string json = dynamicPics.ToString();
//Return the json as content, with the type specified.
return this.Content(json, "application/json");
我建议根据您的 AlbumPicture 对象创建一个数据传输对象。
public class AlbumPictureDto
{
public int id { get; set; }
public string url{ get; set; }
public string thumbnailUrl{ get; set; } //create new properties
public new static readonly Func<AlbumPicture, AlbumPictureDto> AsDto = p => new AlbumPictureDto()
{
id = p.id,
url= p.url,
thumbnailUrl= ThumbManager.GetThumb(p.id)
};
}
//控制器代码
List<AlbumPicture> pics = db.AlbumPictures.Where(p => p.AlbumID == album.ID).OrderBy(p => p.RankOrder).ToList();
return Json(pics.Select(AlbumPicturesDto.AsDto),JsonRequestBehavior.AllowGet);
您可以向 Dto 对象添加任意数量的属性,并使用此方法将这些属性 return 提供给客户端。
并不是说 .NET 在 JSON 方面不好,只是它是一种强类型语言,这意味着在处理动态时你不能将它与 javascript 这样的动态语言进行比较类型操作。
在我看来,获得所需结果的最佳方法是为客户端创建新的 类。直接使用域模型与客户端通信通常不是一个好主意。如果您希望尽可能减少代码重复,您充其量可以简单地创建继承域模型的 ViewModel。但正如所说,您可能不想将模型的所有属性公开给您的客户。
如果打算进行完整的模型展示,那么您可以像@conanak99 在他的出色回答中指出的那样做。使用 LINQ 形式的代码可能会更优雅:
var pics = from album in db.AlbumPictures
where album.AlbumID == album.ID
orderby album.RankOrder
let dynamicAlbum = album.ToDynamic()
dynamicAlbum.Thumbnail = ThumbManager.GetThumb(p.ID)
select dynamicAlbum;