首页 文章

防止在 Web API 中序列化属性

提问于
浏览
157

我正在使用 MVC 4 Web API 和 asp.net web 表单 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对象太复杂了,我需要在不同的方法中使用不同的属性集,所以在运行时,更容易标记要忽略的每个属性。

有没有办法做到这一点?

10 回答

  • 15

    我迟到了,但是一个匿名的对象可以解决这个问题:

    [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;
    }
    
  • 10

    尝试使用IgnoreDataMember属性

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

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

    为了更好地理解,您可以阅读官方文章

  • 109

    根据 Web API 文档页面ASP.NET Web API 中的 JSON 和 XML 序列化明确阻止对属性进行序列化,您可以使用[JsonIgnore]作为 Json 序列化程序,或[IgnoreDataMember]作为默认 XML 序列化程序。

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

  • 28

    您可以采用“opt-in”方法,而不是让所有在默认情况下被序列化。在此方案中,只允许序列化您指定的属性。您可以使用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 序列化它们。

  • 19

    这对我有用:创建一个自定义合约解析器,它有一个名为 AllowList of string array type 的公共属性。在您的操作中,根据操作需要返回的内容修改该属性。

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

    这种方法允许我为特定请求 allow/disallow 而不是修改类定义。如果您不需要 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);
    
  • 17

    我将告诉你两种方法来实现你想要的:

    第一种方法:使用 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 的字段将不会被序列化。

  • 5

    几乎与 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;
        }
    }
    
    1. 使用自定义合同解析程序
    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>"),
                }
            }
        });
    }
    

    编辑:

    它不能作为每个请求的 expected(isolate 解析器工作)。我将使用匿名对象。

    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,
            }),
        }));
    }
    
  • 3

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

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

    出于某种原因[IgnoreDataMember]并不总是对我有用,我有时会得到StackOverflowException(或类似的)。所以相反(或者另外)我开始在Objects中使用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并在收到它之后将其转换为由 built-in 序列化器引起的 aviod 问题,这有时在解析对象时会导致无限循环。

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

    值得注意的是,EntityFramework Class-property 的以下代码会导致问题(如果两个类引用 each-other):

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

相关问题