impedire che la proprietà venga serializzata nell’API Web

Sto usando un’API web MVC 4 e moduli Web asp.net 4.0 per creare un’API di rest. Funziona alla grande:

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

Ora ho bisogno di evitare che alcune proprietà vengano serializzate. So che posso usare alcuni LINQ sulla lista e ottenere solo le proprietà di cui ho bisogno, e generalmente è un buon approccio, ma nello scenario attuale l’object something è troppo complesso, e ho bisogno di un diverso insieme di proprietà in diversi metodi, quindi è più facile contrassegnare, in fase di esecuzione, ogni proprietà da ignorare.

C’è un modo per farlo?

L’API Web ASP.NET utilizza Json.Net come formattatore predefinito, quindi se l’applicazione utilizza solo JSON come formato dati, è ansible utilizzare [JsonIgnore] per ignorare la proprietà per la serializzazione:

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

Ma in questo modo non supporta il formato XML. Quindi, nel caso in cui la tua applicazione debba supportare il formato XML in più (o solo supportare XML), invece di usare Json.Net , dovresti usare [DataContract] che supporta sia JSON che 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; } } 

Per maggiore comprensione, puoi leggere l’ articolo ufficiale .

In base alla pagina di documentazione API Web JSON e alla serializzazione XML nell’API Web ASP.NET per impedire esplicitamente la serializzazione su una proprietà, è ansible utilizzare [JsonIgnore] per il serializzatore Json o [IgnoreDataMember] per il serializzatore XML predefinito.

Tuttavia, durante i test ho notato che [IgnoreDataMember] impedisce la serializzazione per le richieste XML e Json, quindi consiglierei di utilizzarlo piuttosto che decorare una proprietà con più attributi.

Invece di lasciare che tutto venga serializzato per impostazione predefinita, puoi adottare l’approccio “opt-in”. In questo scenario, solo le proprietà specificate possono essere serializzate. Lo fai con DataContractAttribute e DataMemberAttribute , trovati nello spazio dei nomi System.Runtime.Serialization .

Il DataContactAttribute viene applicato alla class e DataMemberAttribute viene applicato a ciascun membro che si desidera serializzare:

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

Oserei dire che questo è un approccio migliore perché ti costringe a prendere decisioni esplicite su ciò che sarà o non lo farà attraverso la serializzazione. Permette inoltre alle classi del tuo modello di vivere in un progetto da solo, senza dipendere da JSON.net solo perché da qualche altra parte ti capita di serializzarle con JSON.net.

Questo ha funzionato per me: creare un resolver di contratto personalizzato che abbia una proprietà pubblica denominata AllowList del tipo di array di stringhe. Nella tua azione, modifica quella proprietà in base a ciò che l’azione deve restituire.

1. creare un risolutore di contratto personalizzato:

 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. utilizzare il resolver del contratto personalizzato in azione

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

Questo approccio mi ha permesso di consentire / disabilitare una richiesta specifica invece di modificare la definizione della class. E se non hai bisogno di serializzazione XML, non dimenticare di distriggersrlo in App_Start\WebApiConfig.cs o la tua API restituirà le proprietà bloccate se il client richiede xml invece di json.

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

Sono in ritardo per il gioco, ma un object anonimo farebbe il trucco:

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

Ti mostrerò 2 modi per realizzare ciò che desideri:

Primo modo: decora il tuo campo con l’attributo JsonProperty per saltare la serializzazione di quel campo se è nullo.

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

Secondo modo: se si negozia con alcuni scenari complessi, è ansible utilizzare la convenzione Web Api (“ShouldSerialize”) per ignorare la serializzazione di quel campo in base a una determinata logica.

 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 utilizza JSON.Net e utilizza la riflessione sulla serializzazione, quindi quando ha rilevato (ad esempio) il metodo ShouldSerializeFieldX (), il campo con nome FieldX non verrà serializzato.

Prova a usare la proprietà IgnoreDataMember

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

Quasi come la risposta di greatbear302, ma creo ContractResolver per richiesta.

1) Creare un ContrattoResolver personalizzato

 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) Utilizzare il resolver del contratto personalizzato in azione

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

Modificare:

Non ha funzionato come previsto (isolatore resolver per richiesta). Userò oggetti anonimi.

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

Potresti essere in grado di utilizzare AutoMapper e utilizzare il .Ignore() e quindi inviare l’object mappato

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

Per qualche motivo [IgnoreDataMember] non sempre funziona per me e talvolta ottengo StackOverflowException (o simile). Così invece (o in aggiunta) ho iniziato ad usare un pattern che assomigliava a qualcosa di simile a POST ing in Objects alla mia API:

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

Quindi fondamentalmente passo in un JObject e lo converto dopo che è stato ricevuto per aviod problemi causati dal serializzatore integrato che a volte causa un loop infinito mentre analizza gli oggetti.

Se qualcuno conosce una ragione per cui questa è in alcun modo una ctriggers idea, per favore fatemelo sapere.

Può valere la pena notare che è il seguente codice per una proprietà di class EntityFramework che causa il problema (se due classi si riferiscono l’una all’altra):

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