impedire la serializzazione della proprietà nell'API Web


174

Sto usando un'API Web MVC 4 e moduli Web asp.net 4.0 per creare un'API di riposo. Funziona benissimo:

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

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

    return httpResponseMessage;
}

Ora devo impedire la serializzazione di alcune proprietà. So che posso usare un po 'di LINQ sull'elenco e ottenere solo le proprietà di cui ho bisogno, e generalmente è un buon approccio, ma nel presente scenario l' somethingoggetto è troppo complesso e ho bisogno di un diverso set di proprietà in diversi metodi, quindi è più facile da contrassegnare, in fase di esecuzione, ogni proprietà da ignorare.

C'è un modo per farlo?


È possibile aggiungere ScriptIgnore alla proprietà. visualizzare questa domanda stackoverflow.com/questions/10169648/...
atbebtg

Risposte:


232

L'API Web ASP.NET utilizza Json.Netcome formattatore predefinito, quindi se l'applicazione utilizza solo JSON come formato di dati, è possibile 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<Something> Somethings { get; set; }
}

Ma in questo modo non supporta il formato XML. Quindi, nel caso in cui l'applicazione debba supportare maggiormente il formato XML (o solo supportare XML), invece di utilizzare Json.Net, è necessario utilizzare [DataContract]che supporti 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<Something> Somethings { get; set; }
}

Per una maggiore comprensione, puoi leggere l' articolo ufficiale .


Penso che dovrò trovare un modo per aggiungere e rimuovere gli attributi in fase di esecuzione usando jsonignore.
user1330271

LAVORATO COME UN FASCINO! GRAZIE :)
Paulo Rodrigues,

Perché è triste che l'attributo JsonIgnore non sia supportato con la risposta XML?
Mukus,

Datacontract è un'ottima soluzione. Mi dà un'API REST pulita. Allo stesso tempo, quando salvo i dati in un no-sql, le proprietà ignorate vengono mantenute nonostante gli oggetti vengano archiviati come json.
FrankyHollywood,

1
@FedorSteeman Lo spazio dei nomi di JsonIgnore è Newtonsoft.Json, necessita del pacchetto JSON.Net-nuget. DataContract e DataMember -attributes d'altra parte hanno bisogno di System.Runtime.Serialization-namespace (e riferimento se manca)
Esko

113

In base alla pagina della documentazione dell'API Web Serializzazione JSON e XML nell'API Web ASP.NET per impedire esplicitamente la serializzazione su una proprietà che è possibile utilizzare [JsonIgnore]per il serializzatore Json o [IgnoreDataMember]per il serializzatore XML predefinito.

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


2
Questa è la risposta migliore Copre XML e JSON con un attributo.
Oliver,

17
Purtroppo [IgnoreDataMember]non sembra funzionare con oggetti proxy EF 6 caricati in modo pigro (proprietà virtuali). [DataContract]e [DataMember]comunque.
Nick,

32

Invece di lasciare che tutto venga serializzato per impostazione predefinita, puoi adottare l'approccio "opt-in". In questo scenario, è possibile serializzare solo le proprietà specificate. Puoi farlo con DataContractAttributee DataMemberAttribute, che si trova nello spazio dei nomi System.Runtime.Serialization .

La DataContactAttributesi applica alla classe, e la DataMemberAttributesi applica ad ogni membro che si desidera essere serializzato:

[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 lo farà o meno attraverso la serializzazione. Permette anche alle tue classi di modelli di vivere in un progetto da sole, senza dipendere da JSON.net solo perché da qualche altra parte ti capita di serializzarle con JSON.net.


2
L'unico approccio che ha funzionato fuori dalla scatola con .Net Core per nascondere i membri ereditati. Funziona con serializzazione sia XML che Json. Kudos
Piou,

Ho bisogno della stessa funzionalità ma le proprietà sono incluse o escluse dipende da quale metodo API è invocato, ovvero sono necessari dati diversi per chiamate API diverse. Qualche suggerimento
Nithin Chandran,

Funziona benissimo, ma il mio problema principale è che queste configurazioni scompaiono con ogni impalcatura dbcontext in EF Core, qualcuno ha una soluzione? Questi attributi potrebbero essere in una classe parziale o essere impostati a livello di codice?
Naner,

20

Questo ha funzionato per me: creare un risolutore di contratti personalizzato che abbia una proprietà pubblica chiamata AllowList di tipo array di stringhe. Nella tua azione, modifica quella proprietà in base a ciò che l'azione deve restituire.

1. creare un risolutore di contratti personalizzato:

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. utilizzare il risolutore di contratti personalizzati 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 / non consentire richieste specifiche invece di modificare la definizione della classe. E se non hai bisogno della serializzazione XML, non dimenticare di disattivarlo nella tua App_Start\WebApiConfig.cso la tua API restituirà proprietà bloccate se il client richiede xml anziché json.

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

Qualcosa deve essere cambiato con le versioni più recenti ma non sono riuscito a farlo funzionare. Potrei farlo funzionare facendo 'nuovo' invece di 'come' durante la modifica del resolver. Il tipo JsonContractResolver non è compatibile per qualche motivo. Il problema di fare il nuovo è che lo sovrascrive per tutti invece che solo per quello.
Kalel Wade,

Sono riuscito a farlo funzionare utilizzando il metodo Request.CreateResponse () che riceve un MediaTypeFormatter, in questo modo: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolver = "{ Byte "," MimeType "," Larghezza "," Altezza "}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Paul,

Cosa succede se vogliamo anche che le proprietà bloccate vengano ignorate in una risposta XML?
Carlos P,

A meno che il risolutore del contratto dati non sia assegnato una volta per richiesta, questo non è sicuro per i thread. Penso che questo sia assegnato una volta, nella classe di avvio.
Sprague,

2
Ancora peggio, lo ho provato e la chiamata createproperties è memorizzata nella cache dal risolutore del contratto. Questa risposta è ingenua nella migliore delle ipotesi, pericolosa nella peggiore.
Sprague,

19

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

Primo modo: decorare il 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<Something> Somethings { get; set; }
}

Secondo modo: se si sta negoziando con alcuni scenari complessi, è possibile utilizzare la convenzione Web Api ("ShouldSerialize") per saltare la serializzazione di quel campo a seconda di una logica specifica.

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


Non è fatto da API Web, Web API utilizza Json.NET per impostazione predefinita per la serializzazione. Questo processo viene eseguito da Json.NET non dal web api
Hamid Pourjam il

1
La seconda soluzione è utile perché consente di mantenere agnostica la tecnologia degli oggetti Domain senza la necessità di riscrivere DTO solo per nascondere alcuni campi.
Raffaeu,

17

Sono in ritardo al gioco, ma un oggetto anonimo farebbe il trucco:

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

11

Prova a usare la IgnoreDataMemberproprietà

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

5

Quasi uguale alla risposta di greatbear302, ma creo ContractResolver per richiesta.

1) Crea un ContractResolver personalizzato

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) Utilizzare il risolutore di contratti personalizzati in azione

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

Modificare:

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

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

4

Potresti essere in grado di utilizzare AutoMapper e utilizzare la .Ignore()mappatura e quindi inviare l'oggetto mappato

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

3

Funziona bene semplicemente aggiungendo: [IgnoreDataMember]

Sulla parte superiore della proprietà, come:

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

Funziona con ApiController. Il codice:

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

Inoltre, forse una soluzione migliore è quella di isolare i modelli di visualizzazione dai modelli "back-end", in modo da poter saltare questa dichiarazione. Mi trovo meglio in quella situazione spesso.
Dannejaha,

0

Per qualche motivo [IgnoreDataMember]non sempre funziona per me, e talvolta ottengo StackOverflowException(o simili). Così, invece (o in aggiunta) ho iniziato ad usare un modello alla ricerca qualcosa di simile quando POSTing nella Objectsmia 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);
}

Quindi fondamentalmente passo un JObjecte lo converto dopo che è stato ricevuto in problemi aviod causati dal serializzatore incorporato che a volte causano un ciclo infinito durante l'analisi degli oggetti.

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

Vale la pena notare che è il codice seguente per una proprietà di classe 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<MyObject> MyObjects => MyObject.GetByMyOtherObjectId(Id);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.