evitar que la propiedad se serialice en la API web

Estoy usando una API web MVC 4 y formularios web asp.net 4.0 para construir una API de descanso. Está funcionando bien:

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

Ahora necesito evitar que algunas propiedades sean serializadas. Sé que puedo usar algunos LINQ sobre la lista y obtener solo las propiedades que necesito, y en general es un buen enfoque, pero en el escenario actual el objeto something es demasiado complejo, y necesito un conjunto diferente de propiedades en diferentes métodos, por lo que es más fácil marcar, en tiempo de ejecución, cada propiedad que se ignorará.

¿Hay una manera de hacer eso?

ASP.NET Web API utiliza Json.Net como formateador predeterminado, por lo que si su aplicación solo usa JSON como formato de datos, puede usar [JsonIgnore] para ignorar la propiedad para la serialización:

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

Pero, de esta manera no es compatible con formato XML. Entonces, en caso de que su aplicación tenga que admitir más el formato XML (o solo sea compatible con XML), en lugar de usar Json.Net , debe usar [DataContract] que sea compatible con JSON y 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 obtener más información, puede leer el artículo oficial .

Según la página de documentación de la API web Serialización JSON y XML en ASP.NET Web API para evitar explícitamente la serialización en una propiedad, puede usar [JsonIgnore] para el serializador Json o [IgnoreDataMember] para el serializador XML predeterminado.

Sin embargo, en las pruebas me he dado cuenta de que [IgnoreDataMember] impide la serialización para las solicitudes XML y Json, por lo que recomendaría usar eso en lugar de decorar una propiedad con múltiples atributos.

En lugar de dejar que todo se serialice de manera predeterminada, puede tomar el enfoque de “opt-in”. En este escenario, solo las propiedades que especifique pueden ser serializadas. Haga esto con DataContractAttribute y DataMemberAttribute , que se encuentran en el espacio de nombres System.Runtime.Serialization .

El DataContactAttribute se aplica a la clase, y DataMemberAttribute se aplica a cada miembro que desea serializar:

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

Me atrevo a decir que este es un mejor enfoque porque te obliga a tomar decisiones explícitas sobre lo que pasará o no a través de la serialización. También permite que tus clases modelo vivan en un proyecto por sí mismas, sin tomar una dependencia de JSON.net solo porque en algún otro lugar las estás serializando con JSON.net.

Esto funcionó para mí: crear un sistema de resolución de contratos personalizado que tenga una propiedad pública llamada AllowList del tipo de matriz de cadenas. En su acción, modifique esa propiedad según lo que la acción necesite devolver.

1. crea una resolución de contrato personalizada:

 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. usar un solucionador de contratos personalizado en acción

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

Este enfoque me permitió permitir / rechazar solicitudes específicas en lugar de modificar la definición de la clase. Y si no necesita serialización de XML, no olvide apagarlo en su App_Start\WebApiConfig.cs o su API devolverá las propiedades bloqueadas si el cliente solicita xml en lugar de json.

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

Llego tarde al juego, pero un objeto anónimo haría el truco:

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

Le mostraré 2 formas de lograr lo que quiere:

Primera forma: decore su campo con el atributo JsonProperty para omitir la serialización de ese campo si es nulo.

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

Segunda forma: si está negociando con algunos escenarios complejos, puede utilizar la convención Web Api (“ShouldSerialize”) para omitir la serialización de ese campo dependiendo de alguna 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; } } 

WebApi usa JSON.Net y usa la reflexión para la serialización, por lo que cuando haya detectado (por ejemplo) el método ShouldSerializeFieldX (), el campo con el nombre FieldX no se serializará.

Intenta usar la propiedad IgnoreDataMember

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

Casi lo mismo que la respuesta de greatbear302, pero creo ContractResolver por solicitud.

1) Crea un 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 un solucionador de contratos personalizado en acción

 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:

No funcionó como se esperaba (resolver aislado por solicitud). Usaré objetos 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, }), })); } 

Es posible que pueda usar AutoMapper y usar el mapeo .Ignore() y luego enviar el objeto mapeado

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

Por algún motivo, [IgnoreDataMember] no siempre funciona para mí, y a veces obtengo StackOverflowException (o similar). Por lo tanto, en su lugar (o además), comencé a usar un patrón con un aspecto similar al que se POST al POST OBJETOS a mi API:

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

Así que, básicamente, paso un JObject y lo convierto después de que ha sido recibido a causa de los problemas causados ​​por el serializador incorporado que a veces causa un bucle infinito al analizar los objetos.

Si alguien sabe una razón por la cual esto es de alguna manera una mala idea, házmelo saber.

Puede valer la pena señalar que es el siguiente código para una propiedad de clase EntityFramework que causa el problema (si dos clases se refieren a la otra):

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