[c#] empêcher la propriété d'être sérialisé dans l'API web


Answers

Selon la page de documentation Web API JSON et la sérialisation XML dans l'API Web ASP.NET pour empêcher explicitement la sérialisation sur une propriété, vous pouvez utiliser [JsonIgnore] pour le sérialiseur Json ou [IgnoreDataMember] pour le sérialiseur XML par défaut.

Cependant, lors des tests, j'ai remarqué que [IgnoreDataMember] empêche la sérialisation des requêtes XML et Json, donc je recommanderais d'utiliser cela plutôt que de décorer une propriété avec plusieurs attributs.

Question

J'utilise une API web mvc 4 et asp.net webforms 4.0 pour construire une api de repos. Cela fonctionne bien:

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

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

    return httpResponseMessage;
}

Maintenant, j'ai besoin d'empêcher certaines propriétés d'être sérialisées. Je sais que je peux utiliser un peu de linq sur la liste et obtenir seulement les propriétés dont j'ai besoin, et généralement c'est une bonne approche, mais dans le scénario actuel l'objet something est trop complexe et j'ai besoin d'un ensemble différent de propriétés il est plus facile de marquer, au moment de l'exécution, chaque propriété à ignorer.

Y-a-t-il un moyen de faire ça?




Cela a fonctionné pour moi: Créer un résolveur de contrat personnalisé qui a une propriété publique appelée AllowList de type tableau de chaînes. Dans votre action, modifiez cette propriété en fonction de ce que l'action doit renvoyer.

1. créer un résolveur de contrat personnalisé:

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. utiliser le résolveur de contrat personnalisé en action

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

Cette approche m'a permis d'autoriser / de refuser une requête spécifique au lieu de modifier la définition de classe. Et si vous n'avez pas besoin de sérialisation XML, n'oubliez pas de le désactiver dans votre App_Start\WebApiConfig.cs ou votre API retournera des propriétés bloquées si le client demande xml au lieu de json.

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



Je vais vous montrer 2 façons d'accomplir ce que vous voulez:

Première façon: Décorez votre champ avec l'attribut JsonProperty afin d'ignorer la sérialisation de ce champ s'il est nul.

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

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<Something> Somethings { get; set; }
}

Deuxième manière: Si vous négociez avec des scénarios complexes, vous pouvez utiliser la convention Web Api ("ShouldSerialize") afin d'ignorer la sérialisation de ce champ en fonction d'une logique spécifique.

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 utilise JSON.Net et utilise la réflexion pour la sérialisation. Ainsi, lorsqu'il a détecté (par exemple) la méthode ShouldSerializeFieldX (), le champ portant le nom FieldX ne sera pas sérialisé.




Pour une raison quelconque, [IgnoreDataMember] ne fonctionne pas toujours pour moi, et j'ai parfois une Exception (ou similaire). Donc à la place (ou en plus) j'ai commencé à utiliser un pattern ressemblant à ceci quand POST ing dans Objects to my 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);
}

Donc, fondamentalement, je passe dans un JObject et JObject convertis après qu'il a été reçu pour éviter les problèmes causés par le sérialiseur intégré qui provoque parfois une boucle infinie lors de l'analyse des objets.

Si quelqu'un sait que c'est une mauvaise idée, n'hésitez pas à me le faire savoir.

Il peut être utile de noter que c'est le code suivant pour une propriété EntityFramework Class qui provoque le problème (si deux classes se réfèrent l'une à l'autre):

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



Presque la même chose que la réponse de greatbear302, mais je crée ContractResolver par demande.

1) Créez un ContractResolver personnalisé

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) Utiliser le résolveur de contrat personnalisé en action

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

Modifier:

Cela n'a pas fonctionné comme prévu (isoler le résolveur par demande). Je vais utiliser des objets anonymes.

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



Links