防止在Web API中序列化属性


173

我正在使用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对象太复杂了,并且我需要使用不同方法的一组不同属性,因此易于在运行时标记每个要忽略的属性。

有没有办法做到这一点?


您可以将ScriptIgnore添加到属性。针对这一问题stackoverflow.com/questions/10169648/...
atbebtg

Answers:


230

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,则应使用[DataContract]同时支持JSON和XML的格式:

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

要了解更多信息,可以阅读官方文章


我想我必须找到一种使用jsonignore在运行时添加和删除属性的方法。
user1330271

像个迷宫般工作!谢谢:)
保罗·罗德里格斯

为什么令人遗憾的是XML响应不支持JsonIgnore属性?
Mukus,2016年

数据合同是一个很好的解决方案。它给了我一个干净的REST API。同时,当我将数据保存在no-sql中时,尽管将对象存储为json,但被忽略的属性仍会保留。
FrankyHollywood

1
@FedorSteeman JsonIgnore的命名空间是Newtonsoft.Json,需要JSON.Net-nuget包。另一方面,DataContract和DataMember属性需要System.Runtime.Serialization-命名空间(如果缺少,请进行引用)
Esko

113

根据Web API文档页面的ASP.NET Web API中的JSON和XML序列化以显式阻止对属性的序列化,您可以将其[JsonIgnore]用于Json序列化器或[IgnoreDataMember]默认XML序列化器。

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


2
这是更好的答案。它包含具有一个属性的XML和JSON。
奥利弗

17
可悲的是,[IgnoreDataMember]它似乎不适用于延迟加载的EF 6代理对象(虚拟属性)。 [DataContract]并且[DataMember]做。
尼克

32

您可以采用“选择加入”方法,而不是让所有默认情况下序列化。在这种情况下,仅允许您指定的属性进行序列化。你这样做与DataContractAttributeDataMemberAttribute,在发现System.Runtime.Serialization命名空间。

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对其进行了序列化。


2
使用.Net Core开箱即用的唯一方法是隐藏继承的成员。适用于XML和Json序列化。荣誉
Piou

我需要相同的功能,但包含或排除的属性取决于调用的api方法,即,不同的api调用需要不同的数据。任何建议
Nithin Chandran

这很好用,但是我的主要问题是,EF Core中的每个dbcontext支架都无法使用这些配置,有人对此有解决方法吗?这些属性可以在局部类中,还是可以通过编程设置?
Naner

20

这对我有用:创建一个自定义合同解析器,该解析器具有称为字符串数组类型的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序列化,请不要忘记在您的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);

较新的版本一定有所更改,但是我无法使它正常工作。修改解析器时,我可以通过执行“新”而不是“ as”来使其工作。由于某种原因,JsonContractResolver类型不兼容。进行新操作的问题在于它会覆盖所有对象,而不仅仅是覆盖所有对象。
Kalel Wade 2014年

我设法使用接收到MediaTypeFormatter的Request.CreateResponse()方法来使其工作,如下所示:var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolverOptIn {AllowList = new string [] {“ Id”,“字节”,“ MimeType”,“宽度”,“高度”}}}}; 返回Request.CreateResponse(HttpStatusCode.OK,image,jsonMediaTypeFormatter);
保罗

如果我们还希望在XML响应中忽略被阻止的属性怎么办?
卡洛斯·P

除非每个请求为数据合同解析器分配一次,否则这不是线程安全的。我认为这是在启动类中分配一次的。
斯普拉(Sprague)

2
更糟糕的是,我测试了此问题,并且由合同解析器缓存了createproperties调用。这个答案充其量是天真的,最坏的是危险的。
斯普拉,

19

我将向您展示两种实现所需目标的方法:

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

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完成
Hamid Pourjam '16

1
第二种解决方案很好,因为它可以使域对象技术保持不可知状态,而无需重写DTO来隐藏某些字段。
拉斐尔

17

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

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

11

尝试使用IgnoreDataMember属性

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

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

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

4

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

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

3

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

在propertyp之上,例如:

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

另外,也许更好的解决方案是将视图模型与“后端”模型隔离开来,因此您可以跳过此声明。在那种情况下,我常常发现自己更好。
Dannejaha

0

由于某种原因,[IgnoreDataMember]它并不总是对我有用,并且有时我会得到StackOverflowException(或类似)。因此,取而代之(或另外),我POSTObjects进入我的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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.