【问题标题】:prevent property from being serialized in web API防止属性在 Web API 中被序列化
【发布时间】:2012-08-04 18:39:24
【问题描述】:

我正在使用 MVC 4 web API 和 asp.net web forms 4.0 来构建一个 rest API。效果很好:

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = somethings });

    return httpResponseMessage;
}

现在我需要防止某些属性被序列化。我知道我可以在列表上使用一些 LINQ 并只获取我需要的属性,通常这是一个好方法,但在目前的情况下,something 对象太复杂了,我需要不同方法中的一组不同属性,因此更容易在运行时标记要忽略的每个属性。

有没有办法做到这一点?

【问题讨论】:

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


【解决方案1】:

ASP.NET Web API 使用Json.Net 作为默认格式化程序,因此如果您的应用程序仅使用 JSON 作为数据格式,您可以使用[JsonIgnore] 忽略属性进行序列化:

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonIgnore]
    public List<Something> Somethings { get; set; }
}

但是,这种方式不支持 XML 格式。因此,如果您的应用程序必须更多地支持 XML 格式(或仅支持 XML),而不是使用 Json.Net,您应该使用同时支持 JSON 和 XML 的 [DataContract]

[DataContract]
public class Foo
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Name { get; set; }

    //Ignore by default
    public List<Something> Somethings { get; set; }
}

更多了解,可以阅读official article

【讨论】:

  • 我想我必须找到一种在运行时使用 jsonignore 添加和删除属性的方法。
  • 工作就像一个魅力!谢谢:)
  • 为什么 XML 响应不支持 JsonIgnore 属性令人难过?
  • Datacontract 是一个很好的解决方案。它给了我一个干净的 REST API。同时,当我将数据保存在 no-sql 中时,尽管对象被存储为 json,但被忽略的属性仍然存在。
  • @FedorSteeman JsonIgnore的命名空间是Newtonsoft.Json,需要JSON.Net-nuget包。另一方面,DataContract 和 DataMember -attributes 需要 System.Runtime.Serialization-namespace (如果缺少则引用)
【解决方案2】:

根据 Web API 文档页面 JSON and XML Serialization in ASP.NET Web API,要明确防止对属性进行序列化,您可以将 [JsonIgnore] 用于 Json 序列化程序或使用 [IgnoreDataMember] 作为默认 XML 序列化程序。

但是在测试中我注意到[IgnoreDataMember] 会阻止 XML 和 Json 请求的序列化,所以我建议使用它而不是用多个属性装饰属性。

【讨论】:

  • 这是更好的答案。它涵盖了具有一个属性的 XML 和 JSON。
  • 可悲的是 [IgnoreDataMember] 似乎不适用于延迟加载的 EF 6 代理对象(虚拟属性)。但是,[DataContract][DataMember] 可以。
【解决方案3】:

您可以采用“选择加入”的方法,而不是让一切默认序列化。在这种情况下,只允许序列化您指定的属性。您可以使用位于 System.Runtime.Serialization 命名空间中的 DataContractAttributeDataMemberAttribute 来执行此操作。

DataContactAttribute 应用于类,DataMemberAttribute 应用于您要序列化的每个成员:

[DataContract]
public class MyClass {

  [DataMember]
  public int Id { get; set;} // Serialized

  [DataMember]
  public string Name { get; set; } // Serialized

  public string DontExposeMe { get; set; } // Will not be serialized
}

我敢说这是一种更好的方法,因为它迫使您明确决定什么会或不会通过序列化实现。它还允许您的模型类自己存在于项目中,而无需依赖 JSON.net,因为您碰巧在其他地方使用 JSON.net 对它们进行序列化。

【讨论】:

  • 唯一一种开箱即用的 .Net Core 隐藏继承成员的方法。适用于 XML 和 Json 序列化。荣誉
  • 我需要相同的功能,但是否包含属性取决于调用的 api 方法,即不同的 api 调用需要不同的数据。任何建议
  • 这很好用,但我的主要问题是这些配置随着 EF Core 中的每个 dbcontext 脚手架而消失,有人有解决方法吗?这些属性可以在部分类中,还是以编程方式设置?
【解决方案4】:

这对我有用:创建一个自定义合同解析器,它有一个名为 AllowList 的字符串数组类型的公共属性。在您的操作中,根据操作需要返回的内容修改该属性。

1.创建自定义合同解析器:

public class PublicDomainJsonContractResolverOptIn : DefaultContractResolver
{
    public string[] AllowList { get; set; }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);

        properties = properties.Where(p => AllowList.Contains(p.PropertyName)).ToList();
        return properties;
    }
}

2.实际使用自定义合约解析器

[HttpGet]
public BinaryImage Single(int key)
{
    //limit properties that are sent on wire for this request specifically
    var contractResolver = Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver as PublicDomainJsonContractResolverOptIn;
    if (contractResolver != null)
        contractResolver.AllowList = new string[] { "Id", "Bytes", "MimeType", "Width", "Height" };

    BinaryImage image = new BinaryImage { Id = 1 };
    //etc. etc.
    return image;
}

这种方法允许我允许/禁止特定请求,而不是修改类定义。如果您不需要 XML 序列化,请不要忘记在您的 App_Start\WebApiConfig.cs 中将其关闭,否则如果客户端请求 xml 而不是 json,您的 API 将返回被阻止的属性。

//remove xml serialization
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);

【讨论】:

  • 新版本一定发生了一些变化,但我无法让它工作。在修改解析器时,我可以通过执行“new”而不是“as”来使其工作。 JsonContractResolver 类型由于某种原因不兼容。做 new 的问题是它会覆盖所有而不是只覆盖一个。
  • 我设法通过使用接收 MediaTypeFormatter 的 Request.CreateResponse() 方法来完成这项工作,如下所示: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new PublicDomainJsonContractResolverOptIn { AllowList = new string [] { "Id", "Bytes", "MimeType", "Width", "Height" } } } }; return Request.CreateResponse(HttpStatusCode.OK, image, jsonMediaTypeFormatter);
  • 如果我们还希望在 XML 响应中忽略被阻止的属性怎么办?
  • 除非为每个请求分配一次数据合约解析器,否则这不是线程安全的。我认为这是在启动类中分配一次。
  • 更糟糕的是,我对此进行了测试,并且合约解析器缓存了 createproperties 调用。这个答案充其量是幼稚的,最坏的情况是危险的。
【解决方案5】:

我将向你展示 2 种方法来完成你想要的:

第一种方式:使用 JsonProperty 属性装饰您的字段,以便在该字段为空时跳过该字段的序列化。

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<Something> Somethings { get; set; }
}

第二种方式:如果您正在与一些复杂的场景进行协商,那么您可以使用 Web Api 约定(“ShouldSerialize”),以便根据某些特定逻辑跳过该字段的序列化。

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Something> Somethings { get; set; }

    public bool ShouldSerializeSomethings() {
         var resultOfSomeLogic = false;
         return resultOfSomeLogic; 
    }
}

WebApi 使用 JSON.Net 并且它使用反射来序列化,因此当它检测到(例如)ShouldSerializeFieldX() 方法时,名称为 FieldX 的字段将不会被序列化。

【讨论】:

  • 不是web api做的,web api默认使用Json.NET进行序列化。这个过程是由 Json.NET 而不是 web api 完成的
  • 第二种解决方案很好,因为它允许保持域对象技术不可知,而无需重写 DTO 来隐藏某些字段。
【解决方案6】:

我迟到了,但匿名对象可以解决问题:

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    var returnObjects = somethings.Select(x => new {
        Id = x.Id,
        OtherField = x.OtherField
    });

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = returnObjects });

    return httpResponseMessage;
}

【讨论】:

    【解决方案7】:

    尝试使用IgnoreDataMember 属性

    public class Foo
        {
            [IgnoreDataMember]
            public int Id { get; set; }
            public string Name { get; set; }
        }
    

    【讨论】:

      【解决方案8】:

      只需添加以下内容即可正常工作: [IgnoreDataMember]

      在属性p之上,比如:

      public class UserSettingsModel
      {
          public string UserName { get; set; }
          [IgnoreDataMember]
          public DateTime Created { get; set; }
      }
      

      这适用于 ApiController。代码:

      [Route("api/Context/UserSettings")]
          [HttpGet, HttpPost]
          public UserSettingsModel UserSettings()
          {
              return _contextService.GetUserSettings();
          }
      

      【讨论】:

      • 也许更好的解决方案是将视图模型与“后端”模型隔离,因此您可以跳过此声明。我发现自己经常在这种情况下变得更好。
      【解决方案9】:

      与 greatbear302 的回答几乎相同,但我根据请求创建 ContractResolver。

      1) 创建自定义 ContractResolver

      public class MyJsonContractResolver : DefaultContractResolver
      {
          public List<Tuple<string, string>> ExcludeProperties { get; set; }
      
          protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
          {
              JsonProperty property = base.CreateProperty(member, memberSerialization);
      
              if (ExcludeProperties?.FirstOrDefault(
                  s => s.Item2 == member.Name && s.Item1 == member.DeclaringType.Name) != null)
              {
                  property.ShouldSerialize = instance => { return false; };
              }
      
              return property;
          }
      }
      

      2) 使用自定义合约解析器

      public async Task<IActionResult> Sites()
      {
          var items = await db.Sites.GetManyAsync();
      
          return Json(items.ToList(), new JsonSerializerSettings
          {
              ContractResolver = new MyJsonContractResolver()
              {
                  ExcludeProperties = new List<Tuple<string, string>>
                  {
                      Tuple.Create("Site", "Name"),
                      Tuple.Create("<TypeName>", "<MemberName>"),
                  }
              }
          });
      }
      

      编辑:

      它没有按预期工作(根据请求隔离解析器)。我将使用匿名对象。

      public async Task<IActionResult> Sites()
      {
          var items = await db.Sites.GetManyAsync();
      
          return Json(items.Select(s => new
          {
              s.ID,
              s.DisplayName,
              s.Url,
              UrlAlias = s.Url,
              NestedItems = s.NestedItems.Select(ni => new
              {
                  ni.Name,
                  ni.OrdeIndex,
                  ni.Enabled,
              }),
          }));
      }
      

      【讨论】:

        【解决方案10】:

        您也许可以使用 AutoMapper 并使用 .Ignore() 映射,然后发送映射对象

        CreateMap<Foo, Foo>().ForMember(x => x.Bar, opt => opt.Ignore());
        

        【讨论】:

          【解决方案11】:

          对于 .NET Core 3.0 及更高版本:

          ASP.NET Core 的默认 JSON 序列化程序现在是 System.Text.Json,这是 .NET Core 3.0 中的新功能。尽可能考虑使用 System.Text.Json。它是高性能的,不需要额外的库依赖。

          https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#newtonsoftjson-jsonnet-support

          示例(感谢cuongle)

          using System.Text.Json.Serialization;
          
          public class Foo
          {
              public int Id { get; set; }
              public string Name { get; set; }
          
              [JsonIgnore]
              public List<Something> Somethings { get; set; }
          }
          

          如果您已经安装了 Newtonsoft.Json 并选择使用它,默认情况下,[JsonIgnore] 将无法按预期工作。

          【讨论】:

          • 我想听听关于这个答案的任何反馈。
          • 两个 json 序列化器都有一个 JsonIgnore 属性;只是确保你的匹配
          【解决方案12】:

          由于某种原因,[IgnoreDataMember] 并不总是对我有用,我有时会得到StackOverflowException(或类似的)。因此,当POSTing in Objects 到我的 API 时,我开始使用类似这样的模式(或另外):

          [Route("api/myroute")]
          [AcceptVerbs("POST")]
          public IHttpActionResult PostMyObject(JObject myObject)
          {
              MyObject myObjectConverted = myObject.ToObject<MyObject>();
          
              //Do some stuff with the object
          
              return Ok(myObjectConverted);
          }
          

          所以基本上我传入一个JObject 并在收到它后将其转换为避免由内置序列化程序引起的问题,这些问题有时会在解析对象时导致无限循环。

          如果有人知道这是一个坏主意的原因,请告诉我。

          可能值得注意的是,导致问题的 EntityFramework 类属性的以下代码(如果两个类相互引用):

          [Serializable]
          public partial class MyObject
          {
             [IgnoreDataMember]
             public MyOtherObject MyOtherObject => MyOtherObject.GetById(MyOtherObjectId);
          }
          
          [Serializable]
          public partial class MyOtherObject
          {
             [IgnoreDataMember]
             public List<MyObject> MyObjects => MyObject.GetByMyOtherObjectId(Id);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-03-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多