在MVC 5中构建动态JSON响应 - 如何处理结果集的属性?

时间:2015-01-15 01:00:02

标签: c# asp.net-mvc json

我将一个对象数组返回到一个基于相册呈现幻灯片的页面。

我从数据库中取出照片。

在我返回这个数组之前,我想要一个" Thumbnail"网址属性。 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字段添加到结果集的最优雅方法是什么?

这个问题非常基本,可能是重复的。但是,我用Google搜索了10分钟,只能找到依赖于第三方库的janky解决方案。我对当前的最佳实践&#34;。

感兴趣

可能重复:How to add dynamically more properties to Json response from the controller。但是,该答案将在生成的JSON中将动态添加的字段(而不是兄弟)替换为我的AlbumPicture属性。

9 个答案:

答案 0 :(得分:8)

目前,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);

答案 1 :(得分:5)

我同意上面的大多数答案,使用动态对象或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);

答案 2 :(得分:2)

我认为答案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();

更新------------------------------

如果您使用的是自动播放器,或者愿意安装它,则可以使用另一种解决方法来获得相同的结果。

首先,您创建了Album的扩展类,并添加了属性

class AlbumExt : Album
    {
        public string Thumbnail { get; set; }
    }

然后在select语句中创建扩展类的新实例,使用automapper(或手动,如果你没有太多)将属性值复制到新类中,并设置缩略图属性。

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基本上只是使用默认约定将属性值从一个类复制到另一个类,如果需要可以覆盖它们。

答案 3 :(得分:2)

使用投影和匿名对象将动态属性与域属性分开。

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();

答案 4 :(得分:2)

您可以使用Json.NET将它们转换为对象,并在创建的JObject上动态添加属性。

从那里你可以将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");

答案 5 :(得分:0)

不直接回答您的问题,但在我看来,您正在使用类似于Entity Framework的东西来使用“数据库优先”方法处理数据库连接。现在您不想在数据库中添加“Thumbnail”属性,但也不想更改动态创建的模型。

现在我认为最好的方法是将属性添加到模型而不仅仅是JSON。在我的选择中它会使它更容易使用,并且如果你需要它还可以直接在视图中使用缩略图。

我会在名为ExtendedModels.cs的数据库项目中添加一个类,并添加如下内容

/// <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。所以在你的情况下,它将类似于以下内容。您可以将获取/设置更改为您想要的任何内容。

public class AlbumPicture
{
    public string ImageName { get; set; } 

    [NotMapped]
    public string Thumbnail{ get; set; } 

}

答案 6 :(得分:0)

您可以使用匿名类型创建具有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"}]

我希望它可以帮到你。

答案 7 :(得分:0)

我建议根据您的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对象添加任意数量的属性,并使用此方法将这些属性返回给客户端。

答案 8 :(得分:0)

并不是说.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;