web-dev-qa-db-fra.com

empêcher la sérialisation de la propriété dans l'API Web

J'utilise une API Web MVC 4 et asp.net Web Form 4.0 pour créer une API de repos. Cela fonctionne très 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, je dois empêcher la sérialisation de certaines propriétés. Je sais que je peux utiliser certains LINQ au-dessus de la liste et obtenir uniquement les propriétés dont j'ai besoin. En général, c'est une bonne approche, mais dans le scénario actuel, l'objet something est trop complexe et j'ai besoin d'un ensemble de propriétés différent dans différentes méthodes. 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?

135
user1330271

L'API Web ASP.NET utilise Json.Net comme formateur par défaut. Par conséquent, si votre application utilise uniquement le format JSON comme format de données, vous pouvez utiliser [JsonIgnore] pour ignorer la propriété pour la sérialisation:

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

    [JsonIgnore]
    public List<Something> Somethings { get; set; }
}

Mais, cette façon ne supporte pas le format XML. Ainsi, dans le cas où votre application doit prend en charge le format XML davantage (ou uniquement le support XML), au lieu d'utiliser Json.Net, vous devez utiliser [DataContract], qui prend en charge les formats JSON et 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; }
}

Pour plus de compréhension, vous pouvez lire le article officiel

184
cuongle

Selon la page de documentation de l'API Web Sérialisation JSON et 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. Je vous recommande donc de l'utiliser plutôt que de décorer une propriété avec plusieurs attributs.

104
Michael Mason

Au lieu de laisser tout être sérialisé par défaut, vous pouvez adopter l'approche «opt-in». Dans ce scénario, seules les propriétés que vous spécifiez sont autorisées à être sérialisées. Vous faites cela avec le DataContractAttribute et DataMemberAttribute , situé dans le System.Runtime.Serialization namespace.

La DataContactAttribute est appliquée à la classe et la DataMemberAttribute est appliquée à chaque membre que vous souhaitez sérialiser:

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

Oserai-je dire que c'est une meilleure approche, car elle vous oblige à prendre des décisions explicites sur ce qui sera ou non réalisé grâce à la sérialisation. Cela permet également à vos classes de modèle de vivre dans un projet par elles-mêmes, sans aucune dépendance vis-à-vis de JSON.net, simplement parce que vous les sérialisez ailleurs avec JSON.net.

25
CBono

Cela a fonctionné pour moi: Créez un résolveur de contrat personnalisé qui possède 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. utilise 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/refuser une demande spécifique au lieu de modifier la définition de la classe. Et si vous n'avez pas besoin de la sérialisation XML, n'oubliez pas de la désactiver dans votre App_Start\WebApiConfig.cs sinon votre API retournera les 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);
19
joym8

Je suis en retard au jeu, mais un objet anonyme ferait l'affaire:

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

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 null.

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

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

Seconde façon: si vous négociez avec des scénarios complexes, vous pouvez utiliser la convention Web Api ("ShouldSerialize") afin d’éviter la sérialisation de ce champ en fonction d’une logique particulière.

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 nommé FieldX ne sera pas sérialisé.

14
foxhard

Essayez d'utiliser la propriété IgnoreDataMember

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

Presque identique à la réponse de greatbear302, mais je crée ContractResolver par requête.

1) Créer 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 (isolateur de résolveur par requête). 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,
        }),
    }));
}
5
tsu1980

Vous pourrez peut-être utiliser AutoMapper et utiliser le mappage .Ignore(), puis envoyer l'objet mappé.

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

Fonctionne bien en ajoutant simplement le: [IgnoreDataMember]

En plus du type de propriété, comme:

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

Cela fonctionne avec ApiController. Le code:

[Route("api/Context/UserSettings")]
    [HttpGet, HttpPost]
    public UserSettingsModel UserSettings()
    {
        return _contextService.GetUserSettings();
    }
2
Dannejaha

Pour une raison quelconque, [IgnoreDataMember] ne fonctionne pas toujours pour moi, et je reçois parfois StackOverflowException (ou similaire). Donc, au lieu de cela (ou en plus), j'ai commencé à utiliser un motif ressemblant à ceci lorsque POSTing dans Objects de mon 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, en gros, je passe une JObject et la convertis après l'avoir reçue en résolvant les problèmes causés par le sérialiseur intégré qui parfois provoque une boucle infinie lors de l'analyse des objets.

Si quelqu'un connaît une raison pour laquelle il s'agit d'une mauvaise idée, veuillez me le faire savoir.

Il peut être intéressant de noter que c'est le code suivant d'une propriété de classe EntityFramework qui est à l'origine du 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);
}
0
Arg0n