Only return selected fields in Web API results

时间:2015-08-14 22:48:47

标签: asp.net asp.net-mvc asp.net-web-api

First of all, this is not exactly a duplication of the dozens of other posts and I have tried all of them and none of them work.

I have a model that contains many more values than my web api consumers need.

public class Publication
{
    [Key]
    public int PublicationID { get; set; }
    public string PublicationTitle { get; set; }
    public string Frequency { get; set; }
    public DateTime NextIssueDate { get; set; }
    public DateTime SpaceDeadline { get; set; }
    public DateTime MaterialsDeadline { get; set; }
    public DateTime CreatedDt { get; set; }
    public string CreatedBy { get; set; }
    public DateTime UpdatedDt { get; set; }
    public string UpdatedBy { get; set; }
}

I only want say a few of the fields to be passed in the API. I've tried this code but instead of leaving out say UpdateBy in the Json result, it returns it with a null value. How do I get rid of that? I've tried several dozen variations but they either fail to compile or fail to return results.

    public IQueryable<Publication> GetPublications()
    {
        return db.Publications
            .ToList()
            .Select(p => new Publication {
                PublicationID = p.PublicationID,
                PublicationTitle = p.PublicationTitle,
                Frequency = p.Frequency,
                NextIssueDate = p.NextIssueDate
            })
            .AsQueryable();
    }

6 个答案:

答案 0 :(得分:10)

不要序列化您的DAO。创建一个完整的合同,然后有选择地序列化它。要为不同的案例创建不同的合同,您可以使用Json.Net简化它;你可以创建一个自定义合约解析器,并将其用作SerializeObject()的参数,如此

static void Main(string[] args)
{
    var person = new TestContract {FirstName = "John", LastName = "Doe", Age = 36};

    var firstNameContract = new SelectiveSerializer("firstname");
    var allPropertiesContract = new SelectiveSerializer("firstname, lastname, age");

    var allJson = JsonConvert.SerializeObject(
        person, 
        Formatting.Indented,
        new JsonSerializerSettings {ContractResolver = allPropertiesContract});

    var firstNameJson = JsonConvert.SerializeObject(
        person, 
        Formatting.Indented,
        new JsonSerializerSettings {ContractResolver = firstNameContract});

    Console.WriteLine(allJson);
    //  {
    //    "FirstName": "John",
    //    "LastName": "Doe",
    //    "Age": 36
    //  }


    Console.WriteLine(firstNameJson);
    //  {
    //    "FirstName": "John",    
    //  }        
}

public class SelectiveSerializer : DefaultContractResolver
{
    private readonly string[] _fields;

    public SelectiveSerializer(string fields)
    {
        var fieldColl = fields.Split(',');
        _fields = fieldColl
            .Select(f => f.ToLower().Trim())
            .ToArray();
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = o => _fields.Contains(member.Name.ToLower());

        return property;
    }
}

public class TestContract
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }
}

如果没有太多努力,您可以将其用于默认的mediatype格式化程序(在管道中)以在请求中查找名为“fields”或其他的参数,然后使用自定义合约解析程序(如果存在),然后它将是无缝默认行为,如果指定则限制字段,如果未指定则序列化整个对象。

在学术方面,这是正当理由: 对数据的任何修改都被视为“视图关注点”,这意味着在API中,它应该由查询参数控制并接受标题。在这种情况下,数据的“表示”是application / json,您选择“过滤”返回的字段。所有这些都可以(并且应该是,imo)在序列化期间处理。因此,在这种情况下,您的“模型”将始终是完整模型与模型的某个子集。此示例中的完整模型包含名字,姓氏和年龄。实际上,这可能是数百个属性。如果您希望允许客户端选择完整模型的子集,那么您可以通过选择性序列化来实现这一目标。

您可以在图表api中使用类似的行为。在那里,大型模型的默认设置是,如果您没有指定字段,则会获得一个空对象,迫使客户端对其要求的内容非常具体,这在有效负载大小很重要时很有用(例如移动应用程序)。并且,没有什么能阻止创建像'name'这样的字段预设,这可能意味着'firstname,lastname'或'all'包含所有属性。

我从来不喜欢拥有数百个数据对象,这些数据对象都是为在20个不同的上下文中使用的数据集提供一些临时要求,其中某些情况需要更多数据而其他情况需要更少数据。 IMO如果您必须通过相同的过程来获取数据,无论是否完成,您都不应该浪费时间创建额外的对象来为客户端构建数据框架,这应该可以帮助您实现这一目标。

答案 1 :(得分:7)

这是因为您正在返回Publication个对象的集合,因此您将获得该类中包含的每个属性,无论您是否填充它。如果要返回属性的子集,则创建一个只包含要返回的属性的类,并在查询中创建该类的实例。

public IQueryable<WhatIReallyWantToReturn> GetPublications()
{
    return db.Publications
        .ToList()
        .Select(p => new WhatIReallyWantToReturn {
            PublicationID = p.PublicationID,
            PublicationTitle = p.PublicationTitle,
            Frequency = p.Frequency,
            NextIssueDate = p.NextIssueDate
        })
        .AsQueryable();
}

private class WhatIReallyWantToReturn
{
    public int PublicationID { get; set; }
    public string PublicationTitle { get; set; }
    public string Frequency { get; set; }
    public DateTime NextIssueDate { get; set; }
}

答案 2 :(得分:1)

正如Craig W.所说你可以使用viewmodel,你也可以使用匿名类型 (注意viewmodel是更好的方法,因为你可以使用像automapper这样的实用程序来自动映射你的属性)

答案 3 :(得分:1)

JsonIgnore注释对我有用

[JsonIgnore]  
public int Ranking { get; set; }

答案 4 :(得分:0)

import tkinter as tk
from PIL import ImageTk, Image


global counter
counter = 0


def load_next_img(counter):
    #global counter
    # rest of your code
    counter += 1
    global tk_image
    image_data_base64_encoded_string = df_time_text_frames["Frame_(base_64)"][counter]
    image = tk.PhotoImage(image_data_base64_encoded_string)
    tk.Label(root, image=im).pack()
    counter += 1

root =  tk.Tk() 

nextbutton = tk.Button(text="next", 
                        command=lambda counter: load_next_image(counter))
nextbutton.pack()

root.mainloop()

答案 5 :(得分:0)

Here是一篇很棒的文章(2019年12月)。它通过使用ExpandoObjectType Reflection提供了一种数据整形解决方案。然后,客户端所需的属性可以作为查询参数(即用逗号分隔)传递给请求。本文还提供了JSON序列化问题的解决方案。

Startup.cs文件:

services.AddControllers(config =>
{
    config.RespectBrowserAcceptHeader = true;
    config.ReturnHttpNotAcceptable = true;
})
.AddXmlDataContractSerializerFormatters()
.AddNewtonsoftJson();