防止在 Web API 中序列化属性

我使用 MVC 4 web API 和 asp.net web form 4.0来构建一个静止的 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对象太复杂了,我需要在不同的方法中使用一组不同的属性,所以在运行时更容易标记每个被忽略的属性。

有办法吗?

183218 次浏览

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

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

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 和 XML 的 [DataContract],而不是使用 Json.Net:

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

为了更好的理解,您可以阅读 官方文件

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

然而,在测试中,我注意到 [IgnoreDataMember]阻止对 XML 和 Json 请求进行序列化,因此我建议使用序列化,而不是用多个属性装饰属性。

您可以采用“ opt-in”方法,而不是默认情况下让 一切序列化。在此方案中,只允许序列化您指定的属性。您可以使用 系统。运行时。序列化名称空间中的 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 序列化它们。

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

创建一个自定义合同解析器:

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

我玩游戏迟到了,但是一个匿名的物体可以解决这个问题:

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

尝试使用 IgnoreDataMember属性

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

我将向你展示两种实现目标的方法:

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

由于某些原因,[IgnoreDataMember]并不总是为我工作,我有时得到 StackOverflowException(或类似)。因此,当 Objects中的 POSTing 到我的 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);
}

几乎和 great bear 302的答案一样,但是我根据请求创建了 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,
}),
}));
}

只需添加以下内容就可以很好地工作: [ IgnoreDataMember ]

在属性之上,比如:

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

对于.NET Core 3.0及以上版本:

NET Core 的默认 JSON 序列化程序现在是 System.Text.Json,这在。NET Core 3.0.考虑使用 System。短信。尽可能叫我杰森。它是高性能的,并且不需要额外的库依赖。

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

样本 (多谢聪聪)

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]将无法按预期工作。