evitar que la propiedad se serialice en la API web

174

Estoy usando una API web MVC 4 y formularios web asp.net 4.0 para construir una API de descanso. Funciona muy bien:

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = somethings });

    return httpResponseMessage;
}

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

¿Hay una manera de hacer eso?

usuario1330271
fuente
Puede agregar ScriptIgnore a la propiedad. ver esta pregunta stackoverflow.com/questions/10169648/…
atbebtg

Respuestas:

232

La API web ASP.NET usa Json.Netcomo 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<Something> Somethings { get; set; }
}

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

Para una mejor comprensión, puede leer el artículo oficial .

cuongle
fuente
Creo que tendré que encontrar una manera de agregar y eliminar atributos en tiempo de ejecución usando jsonignore.
user1330271
TRABAJADO COMO UN ENCANTO ! GRACIAS :)
Paulo Rodrigues
¿Por qué es triste que el atributo JsonIgnore no sea compatible con la respuesta XML?
Mukus
Datacontract es una gran solución. Me da una API REST limpia. Al mismo tiempo, cuando guardo los datos en un no-sql, las propiedades ignoradas persisten a pesar de que los objetos se almacenan como json.
FrankyHollywood
1
@FedorSteeman El espacio de nombres de JsonIgnore es Newtonsoft.Json, necesita el paquete JSON.Net-nuget. DataContract y DataMember -trtributes por otro lado necesitan System.Runtime.Serialization-namespace (y referencia si falta)
Esko
113

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

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

Michael Mason
fuente
2
Esta es la mejor respuesta. Cubre XML y JSON con un atributo.
Oliver
17
Lamentablemente [IgnoreDataMember], no parece funcionar con objetos proxy EF 6 con carga lenta (propiedades virtuales). [DataContract]y [DataMember]hacer, sin embargo.
Nick
32

En lugar de permitir que todo se serialice de forma predeterminada, puede adoptar el enfoque de "suscripción". En este escenario, solo las propiedades que especifique se pueden serializar. Puede hacer esto con DataContractAttributey DataMemberAttribute, que se encuentra en el espacio de nombres System.Runtime.Serialization .

Se DataContactAttributeaplica a la clase y DataMemberAttributese 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 se hará o no a través de la serialización. También permite que sus clases modelo vivan en un proyecto por sí mismas, sin depender de JSON.net solo porque en algún otro lugar las está serializando con JSON.net.

CBono
fuente
2
El único enfoque que funcionó fuera de la caja con .Net Core para ocultar miembros heredados. Funciona para la serialización XML y Json. Felicitaciones
Piou
Necesito la misma funcionalidad, pero las propiedades que se incluyen o excluyen dependen de qué método api se invoque, es decir, se necesitan diferentes datos para diferentes llamadas api. Cualquier sugerencia
Nithin Chandran
Esto funciona muy bien, pero mi problema principal es que estas configuraciones desaparecen con cada andamio de dbcontext en EF Core, ¿alguien tiene una solución para eso? ¿Podrían estos atributos estar en una clase parcial o establecerse mediante programación?
Naner
20

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

1. cree un solucionador de contratos personalizado:

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. use el 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 clase. Y si no necesita la serialización XML, no olvide desactivarlo en App_Start\WebApiConfig.cssu API o su API devolverá 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);
joym8
fuente
Algo debe haber cambiado con las versiones más nuevas, pero no pude hacer que esto funcione. Podría hacer que funcione haciendo 'nuevo' en lugar de 'como' al modificar el resolutor. El tipo JsonContractResolver no es compatible por alguna razón. El problema con hacer nuevo es que lo sobrescribe para todos en lugar de solo uno.
Kalel Wade
Logré que esto funcionara utilizando el método Request.CreateResponse () que recibe un MediaTypeFormatter, como este: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolverOptIn {"Permitir Id. Bytes "," MimeType "," Ancho "," Altura "}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Paul
¿Qué sucede si también queremos que las propiedades bloqueadas se ignoren en una respuesta XML?
Carlos P
A menos que se asigne una resolución de contrato de datos una vez por solicitud, esto no es seguro para subprocesos. Creo que esto se asigna una vez, en la clase de inicio.
Sprague
2
Peor aún, he probado esto y la resolución de contrato almacena en caché la llamada createproperties. Esta respuesta es ingenua en el mejor de los casos, peligrosa en el peor.
Sprague
19

Te mostraré 2 formas de lograr lo que quieres:

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<Something> Somethings { get; set; }
}

Segunda forma: si está negociando con algunos escenarios complejos, entonces podría usar 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<Something> Somethings { get; set; }

    public bool ShouldSerializeSomethings() {
         var resultOfSomeLogic = false;
         return resultOfSomeLogic; 
    }
}

WebApi utiliza JSON.Net y utiliza 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á.

Foxhard
fuente
No se realiza mediante la API web, la API web utiliza Json.NET de forma predeterminada para la serialización. Este proceso lo realiza Json.NET, no la API web
Hamid Pourjam, el
1
La segunda solución es buena porque permite mantener agnóstica la tecnología de objetos de dominio sin la necesidad de reescribir los DTO solo para ocultar algunos campos.
Raffaeu
17

Llego tarde al juego, pero un objeto anónimo sería suficiente:

[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;
}
Tim Hoolihan
fuente
11

Intenta usar la IgnoreDataMemberpropiedad

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

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

1) Crear un ContractResolver personalizado

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) Use el solucionador de contratos personalizado en acción

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

Editar:

No funcionó como se esperaba (aislar resolución por solicitud). Usaré objetos anónimos.

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

Es posible que pueda usar AutoMapper y usar la .Ignore()asignación y luego enviar el objeto asignado

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

Funciona bien simplemente agregando: [IgnoreDataMember]

En la parte superior de la propiedad p, como:

public class UserSettingsModel
{
    public string UserName { get; set; }
    [IgnoreDataMember]
    public DateTime Created { get; set; }
}

Esto funciona con ApiController. El código:

[Route("api/Context/UserSettings")]
    [HttpGet, HttpPost]
    public UserSettingsModel UserSettings()
    {
        return _contextService.GetUserSettings();
    }
Dannejaha
fuente
También, tal vez, una mejor solución es aislar los modelos de vista de los modelos "back end", para que pueda omitir esta declaración. Me encuentro mejor en esa situación a menudo.
Dannejaha
0

Por alguna razón [IgnoreDataMember], no siempre funciona para mí, y a veces me da StackOverflowException(o algo similar). Entonces, en cambio (o además) comencé a usar un patrón que se parecía a esto cuando POSTingresé Objectsa mi 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);
}

Básicamente, paso una JObjecty la convierto después de que ha sido recibida en problemas de aviod 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ágamelo saber.

Vale 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 entre sí):

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