【问题标题】:Only return selected fields in Web API results仅返回 Web API 结果中的选定字段
【发布时间】:2015-08-14 22:48:46
【问题描述】:

首先,这不完全是其他几十个帖子的重复,我已经尝试了所有这些帖子,但没有一个有效。

我的模型包含的值比我的 Web api 消费者需要的多。

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; }
}

我只想说几个要在 API 中传递的字段。我已经尝试过这段代码,但它没有在 Json 结果中省略说 UpdateBy ,而是以空值返回它。我该如何摆脱它?我已经尝试了几十种变体,但它们要么无法编译,要么无法返回结果。

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

【问题讨论】:

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


    【解决方案1】:

    不要序列化你的 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; }
    }
    

    不费吹灰之力,您可能可以将其用于您的默认媒体类型格式化程序(在管道中),以在请求中查找名为“字段”或其他任何参数,然后使用自定义合同解析器(如果存在),然后它会无缝默认行为,如果指定则限制字段,如果未指定则序列化整个对象。

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

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

    我从来都不喜欢拥有数百个数据对象,这些数据对象都满足数据集的一些特殊需求,这些数据集用于 20 种不同的上下文中,有些情况需要更多数据,而另一些情况需要更少的数据。 IMO,如果您必须通过相同的过程来获取数据,无论数据是否完整,您都不应该浪费时间为客户创建额外的对象来构建数据,这应该可以帮助您实现这一目标。

    【讨论】:

    • 我喜欢你的SelectiveSerializer 的想法;你知道是否有一个图书馆可以做到这一点?因为更进一步的想法是想指定子字段,比如:new SelectiveSerializer("firstname, mother.firstname, mother.age");... 听起来像是一个很好的独立项目本身
    • 显然有 FlexJSON,它有一个包含和排除方法:jsons = new JSONSerializer().include("name", "age", ...)
    • Json.Net 开箱即用。您只需要实现他们的自定义序列化程序。这不是很难。我想我上次尝试它时,花了大约 5 分钟,其中大部分只是弄清楚这 3 种方法中的每一种都做了什么。您不必实现所有这些。我给它提供了一个列表属性名称,然后将每个属性名称进行比较并在它们不匹配的地方短路。
    【解决方案2】:

    这是因为您要返回 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; }
    }
    

    【讨论】:

    • 是的,这就是我需要做的,创建一个视图模型。
    • 将可查询对象转换为可枚举对象(通过 ToList())然后返回可查询对象没有任何意义。省略 .ToList() 和 .AsQueryable(),您的结果会直接从 db 映射到新类
    【解决方案3】:
    using Newtonsoft.Json;
    
    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; }
    
        [JsonIgnore]    
        public DateTime CreatedDt { get; set; }
    
        [JsonIgnore]    
        public string CreatedBy { get; set; }
    
        [JsonIgnore]    
        public DateTime UpdatedDt { get; set; }
    
        [JsonIgnore]    
        public string UpdatedBy { get; set; }
    }
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 匿名类型的唯一问题是方法不能返回IQueryable&lt;SomeType&gt;,它必须是dynamic。这会起作用,但它使单元测试成为真正的 PITA。
      【解决方案5】:

      JsonIgnore 注释对我有用

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

      【讨论】:

        【解决方案6】:

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

        Startup.cs 文件:

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

        【讨论】:

          【解决方案7】:

          +1 表示 Sinaesthetic 的答案。 我刚刚读完一篇关于 GraphQL 的文章,它正好解决了这个问题。您可以准确定义同一请求中需要哪些字段。当调用者只需要属性的特定子集时,无需每次都创建新端点。 如果您也可以在 .NET WEB API 中执行此操作而无需创建新模型和端点,只需付出很少的额外努力,您为什么不这样做(而不是将 Web Api 换成 GraphQL)。

          其实他的 SelectiveSerializer 可以通过反射来升级,所以如果你想定义你需要的道具 C#,您可以通过提供属性表达式来做到这一点,因此您不必担心输入错误的属性名称。

          我敢打赌还有其他解决方案,但基本概念是最重要的,我们可以在 json 中定义我们需要哪些字段,而无需创建新模型。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-05-12
            • 1970-01-01
            • 2015-07-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多