Impeça que a propriedade seja serializada na API da web

Estou usando uma API da web do MVC 4 e formulários da web do asp.net 4.0 para criar uma API de descanso. Está funcionando muito bem:

[HttpGet] public HttpResponseMessage Me(string hash) { HttpResponseMessage httpResponseMessage; List somethings = ... httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, new { result = true, somethings = somethings }); return httpResponseMessage; } 

Agora preciso impedir que algumas propriedades sejam serializadas. Eu sei que posso usar algum LINQ sobre a lista e obter apenas as propriedades que eu preciso, e geralmente é uma boa abordagem, mas no cenário atual o object something é muito complexo, e eu preciso de um conjunto diferente de propriedades em diferentes methods, é mais fácil marcar, em tempo de execução, cada propriedade a ser ignorada.

Existe uma maneira de fazer isso?

A API da Web do ASP.NET usa Json.Net como formatador padrão, portanto, se seu aplicativo usar apenas JSON como formato de dados, você poderá usar [JsonIgnore] para ignorar a propriedade para serialização:

 public class Foo { public int Id { get; set; } public string Name { get; set; } [JsonIgnore] public List Somethings { get; set; } } 

Mas, desta forma, não suporta o formato XML. Portanto, se seu aplicativo tiver que suportar mais o formato XML (ou apenas suportar XML), em vez de usar o Json.Net , você deve usar o [DataContract] que suporta JSON e XML:

 [DataContract] public class Foo { [DataMember] public int Id { get; set; } [DataMember] public string Name { get; set; } //Ignore by default public List Somethings { get; set; } } 

Para mais compreensão, você pode ler o artigo oficial .

De acordo com a página de documentação da API da Web JSON e XML Serialization na API da Web do ASP.NET para impedir explicitamente a serialização em uma propriedade, você pode usar [JsonIgnore] para o serializador Json ou [IgnoreDataMember] para o serializador XML padrão.

No entanto, nos testes, observei que [IgnoreDataMember] impede a serialização para solicitações XML e Json, portanto, eu recomendaria usar isso em vez de decorar uma propriedade com vários atributos.

Em vez de permitir que tudo seja serializado por padrão, você pode adotar a abordagem “opt-in”. Nesse cenário, somente as propriedades especificadas podem ser serializadas. Você faz isso com o DataContractAttribute e DataMemberAttribute , encontrado no namespace System.Runtime.Serialization .

O DataContactAttribute é aplicado à class e o DataMemberAttribute é aplicado a cada membro que você deseja ser serializado:

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

Ouso dizer que essa é uma abordagem melhor porque força você a tomar decisões explícitas sobre o que irá ou não fazer com a serialização. Ele também permite que suas classs de modelo vivam em um projeto por si mesmas, sem depender do JSON.net apenas porque em outro lugar você está serializando-as com o JSON.net.

Isso funcionou para mim: Crie um resolvedor de contrato personalizado que tenha uma propriedade pública chamada AllowList do tipo de matriz de string. Em sua ação, modifique essa propriedade dependendo do que a ação precisa retornar.

1. crie um resolvedor de contrato customizado:

 public class PublicDomainJsonContractResolverOptIn : DefaultContractResolver { public string[] AllowList { get; set; } protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { IList properties = base.CreateProperties(type, memberSerialization); properties = properties.Where(p => AllowList.Contains(p.PropertyName)).ToList(); return properties; } } 

2. use o resolvedor de contrato personalizado em ação

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

Essa abordagem permitiu que eu permitisse / desaprovasse solicitações específicas em vez de modificar a definição de class. E se você não precisar de serialização XML, não se esqueça de desativá-la em seu App_Start\WebApiConfig.cs ou sua API retornará propriedades bloqueadas se o cliente solicitar xml em vez de json.

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

Eu vou te mostrar 2 maneiras de conseguir o que você quer:

Primeira maneira: Decore seu campo com o atributo JsonProperty para pular a serialização desse campo se ele for nulo.

 public class Foo { public int Id { get; set; } public string Name { get; set; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public List Somethings { get; set; } } 

Segunda maneira: Se você está negociando com alguns cenários complexos, então você pode usar a convenção de Web Api (“ShouldSerialize”) para pular a serialização daquele campo dependendo de alguma lógica específica.

 public class Foo { public int Id { get; set; } public string Name { get; set; } public List Somethings { get; set; } public bool ShouldSerializeSomethings() { var resultOfSomeLogic = false; return resultOfSomeLogic; } } 

O WebApi usa JSON.Net e usa reflection para serialização, de modo que, quando detectar (por exemplo) o método ShouldSerializeFieldX (), o campo com o nome FieldX não será serializado.

Estou atrasado para o jogo, mas um object anônimo faria o truque:

 [HttpGet] public HttpResponseMessage Me(string hash) { HttpResponseMessage httpResponseMessage; List 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; } 

Tente usar a propriedade IgnoreDataMember

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

Quase o mesmo que a resposta do greatbear302, mas eu crio o ContractResolver por solicitação.

1) Crie um ContractResolver personalizado

 public class MyJsonContractResolver : DefaultContractResolver { public List> 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) Use o resolvedor de contrato personalizado em ação

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

Editar:

Não funcionou como esperado (resolvedor isolado por solicitação). Eu vou usar objects anônimos.

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

Você pode usar o AutoMapper e usar o mapeamento .Ignore() e enviar o object mapeado

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

Por algum motivo, [IgnoreDataMember] nem sempre funciona para mim, e às vezes recebo StackOverflowException (ou similar). Então, em vez disso (ou além disso), comecei a usar um padrão com algo parecido com isso quando POST em Objects para minha API:

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

Então basicamente eu passo em um JObject e o converto depois que ele foi recebido para aviod problemas causados ​​pelo serializador embutido que algumas vezes causam um loop infinito ao analisar os objects.

Se alguém souber um motivo para que isso seja de alguma forma uma má ideia, por favor me avise.

Pode valer a pena notar que é o seguinte código para uma propriedade de class EntityFramework que causa o problema (se duas classs referem-se uns aos outros):

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