Verhindern, dass Eigenschaften in der Web-API serialisiert werden


174

Ich verwende eine MVC 4-Web-API und asp.net-Webformulare 4.0, um eine Rest-API zu erstellen. Es funktioniert großartig:

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

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

    return httpResponseMessage;
}

Jetzt muss ich verhindern, dass einige Eigenschaften serialisiert werden. Ich weiß, dass ich etwas LINQ über die Liste verwenden und nur die Eigenschaften abrufen kann, die ich benötige. Im Allgemeinen ist dies ein guter Ansatz, aber im vorliegenden Szenario ist das somethingObjekt zu komplex und ich benötige unterschiedliche Eigenschaften in unterschiedlichen Methoden Zur Laufzeit ist es einfacher, jede zu ignorierende Eigenschaft zu markieren.

Gibt es eine Möglichkeit, das zu tun?


Sie können der Eigenschaft ScriptIgnore hinzufügen. Diese Frage anzeigen stackoverflow.com/questions/10169648/…
atbebtg

Antworten:


231

Die ASP.NET-Web-API wird Json.Netals Standardformatierer verwendet. Wenn Ihre Anwendung also nur JSON als Datenformat verwendet, können Sie [JsonIgnore]die Eigenschaft für die Serialisierung ignorieren:

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

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

Auf diese Weise wird das XML-Format jedoch nicht unterstützt. Wenn Ihre Anwendung das XML-Format mehr (oder nur XML) unterstützen muss, anstatt es zu verwenden Json.Net, sollten Sie das verwenden, [DataContract]das sowohl JSON als auch XML unterstützt:

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

Zum besseren Verständnis können Sie den offiziellen Artikel lesen .


Ich denke, ich muss einen Weg finden, um Attribute zur Laufzeit mit jsonignore hinzuzufügen und zu entfernen.
user1330271

LIEF WIE AM SCHNÜRCHEN ! DANKE :)
Paulo Rodrigues

Warum ist es traurig, dass das JsonIgnore-Attribut bei XML-Antworten nicht unterstützt wird?
Mukus

Datacontract ist eine großartige Lösung. Es gibt mir eine saubere REST-API. Zur gleichen Zeit, wenn ich die Daten in einem No-SQL speichere, bleiben die ignorierten Eigenschaften erhalten, obwohl die Objekte als JSON gespeichert sind.
FrankyHollywood

1
@FedorSteeman Der Namespace von JsonIgnore ist Newtonsoft.Json und benötigt das Paket JSON.Net-nuget. DataContract- und DataMember-Attribute benötigen andererseits den System.Runtime.Serialization-Namespace (und die Referenz, falls sie fehlt)
Esko

113

Gemäß der Dokumentationsseite der Web-API JSON- und XML-Serialisierung in der ASP.NET-Web-API , um die Serialisierung für eine Eigenschaft, die Sie entweder verwenden können, explizit zu verhindern[JsonIgnore] für den Json-Serializer oder [IgnoreDataMember]für den Standard-XML-Serializer verwenden können.

Beim Testen ist mir jedoch aufgefallen, dass [IgnoreDataMember]die Serialisierung sowohl für XML- als auch für Json-Anforderungen verhindert wird. Daher würde ich empfehlen, diese zu verwenden, anstatt eine Eigenschaft mit mehreren Attributen zu dekorieren.


2
Dies ist die bessere Antwort. Es behandelt XML und JSON mit einem Attribut.
Oliver

17
Leider [IgnoreDataMember]scheint es nicht mit verzögert geladenen EF 6-Proxy-Objekten (virtuellen Eigenschaften) zu funktionieren. [DataContract]und [DataMember]tun es jedoch.
Nick

32

Anstatt standardmäßig alles serialisieren zu lassen, können Sie den "Opt-In" -Ansatz verwenden. In diesem Szenario dürfen nur die von Ihnen angegebenen Eigenschaften serialisiert werden. Sie tun dies mit dem DataContractAttributeund DataMemberAttribute, das im System.Runtime.Serialization- Namespace zu finden ist.

Das DataContactAttributewird auf die Klasse angewendet, und das DataMemberAttributewird auf jedes Mitglied angewendet, das serialisiert werden soll:

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

Ich wage zu sagen, dass dies ein besserer Ansatz ist, weil er Sie dazu zwingt, explizite Entscheidungen darüber zu treffen, was durch Serialisierung erreicht werden soll oder nicht. Außerdem können Ihre Modellklassen selbstständig in einem Projekt leben, ohne von JSON.net abhängig zu sein, nur weil Sie sie an einem anderen Ort zufällig mit JSON.net serialisieren.


2
Der einzige Ansatz, der mit .Net Core sofort funktioniert hat, um geerbte Mitglieder auszublenden. Funktioniert sowohl für die XML- als auch für die Json-Serialisierung. Kudos
Piou

Ich benötige die gleiche Funktionalität, aber Eigenschaften, die eingeschlossen oder ausgeschlossen werden, hängen davon ab, welche API-Methode aufgerufen wird, dh für verschiedene API-Aufrufe werden unterschiedliche Daten benötigt. Irgendwelche Vorschläge
Nithin Chandran

Das funktioniert großartig, aber mein Hauptproblem ist, dass diese Konfigurationen mit jedem dbcontext-Gerüst in EF Core verschwinden. Hat jemand eine Problemumgehung dafür? Könnten diese Attribute in einer Teilklasse sein oder programmgesteuert festgelegt werden?
Naner

20

Dies hat bei mir funktioniert: Erstellen Sie einen benutzerdefinierten Vertragsauflöser mit einer öffentlichen Eigenschaft namens AllowList vom Typ string array. Ändern Sie in Ihrer Aktion diese Eigenschaft abhängig davon, was die Aktion zurückgeben muss.

1. Erstellen Sie einen benutzerdefinierten Vertragsauflöser:

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. Verwenden Sie den benutzerdefinierten Vertragsauflöser in Aktion

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

Dieser Ansatz ermöglichte es mir, bestimmte Anforderungen zuzulassen / nicht zuzulassen, anstatt die Klassendefinition zu ändern. Wenn Sie keine XML-Serialisierung benötigen, vergessen Sie nicht, diese in Ihrer zu App_Start\WebApiConfig.csdeaktivieren. Andernfalls gibt Ihre API blockierte Eigenschaften zurück, wenn der Client XML anstelle von JSON anfordert.

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

Mit neueren Versionen muss sich etwas geändert haben, aber ich konnte dies nicht zum Laufen bringen. Ich könnte es zum Laufen bringen, indem ich beim Modifizieren des Resolvers 'new' anstelle von 'as' mache. Der Typ JsonContractResolver ist aus irgendeinem Grund nicht kompatibel. Das Problem beim Neumachen ist, dass es für alle überschrieben wird und nicht nur für den einen.
Kalel Wade

Ich habe es geschafft, dies mithilfe der Request.CreateResponse () -Methode zum Laufen zu bringen, die einen MediaTypeFormatter wie folgt empfängt: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolverOptIn {" Bytes "," MimeType "," Width "," Height "}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Paul

Was ist, wenn die blockierten Eigenschaften in einer XML-Antwort ebenfalls ignoriert werden sollen?
Carlos P

Dies ist nicht threadsicher, es sei denn, der Datenvertragsauflöser wird einmal pro Anforderung zugewiesen. Ich denke, dies wird einmal in der Startklasse zugewiesen.
Sprague

2
Schlimmer noch, ich habe dies getestet und der Aufruf von createproperties wird vom Vertragsauflöser zwischengespeichert. Diese Antwort ist bestenfalls naiv, im schlimmsten Fall gefährlich.
Sprague

19

Ich werde Ihnen zwei Möglichkeiten zeigen, um das zu erreichen, was Sie wollen:

Erster Weg: Dekorieren Sie Ihr Feld mit dem JsonProperty-Attribut, um die Serialisierung dieses Felds zu überspringen, wenn es null ist.

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

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

Zweiter Weg: Wenn Sie mit einigen komplexen Szenarien verhandeln, können Sie die Web-API-Konvention ("ShouldSerialize") verwenden, um die Serialisierung dieses Felds abhängig von einer bestimmten Logik zu überspringen.

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 verwendet JSON.Net und verwendet Reflection für die Serialisierung. Wenn also beispielsweise die ShouldSerializeFieldX () -Methode erkannt wurde, wird das Feld mit dem Namen FieldX nicht serialisiert.


Es wird nicht von der Web-API ausgeführt, die Web-API verwendet standardmäßig Json.NET für die Serialisierung. Dieser Prozess wird von Json.NET nicht Web-API durchgeführt
Hamid Pourjam

1
Die zweite Lösung ist nützlich, da sie es ermöglicht, die Domain-Objekttechnologie unabhängig zu halten, ohne dass DTOs neu geschrieben werden müssen, um nur einige Felder auszublenden.
Raffaeu

17

Ich bin zu spät zum Spiel, aber anonyme Objekte würden den Trick machen:

[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

Versuchen Sie es mit der IgnoreDataMemberEigenschaft

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

5

Fast wie die Antwort von greatbear302, aber ich erstelle ContractResolver pro Anfrage.

1) Erstellen Sie einen benutzerdefinierten ContractResolver

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) Verwenden Sie einen benutzerdefinierten Vertragsauflöser in Aktion

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

Bearbeiten:

Es hat nicht wie erwartet funktioniert (Resolver pro Anfrage isolieren). Ich werde anonyme Objekte verwenden.

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

Möglicherweise können Sie AutoMapper verwenden, die .Ignore()Zuordnung verwenden und dann das zugeordnete Objekt senden

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

3

Funktioniert gut, indem Sie einfach Folgendes hinzufügen: [IgnoreDataMember]

Über dem richtigen Typ, wie:

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

Dies funktioniert mit ApiController. Der Code:

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

Vielleicht ist es auch eine bessere Lösung, die Ansichtsmodelle von den "Back-End" -Modellen zu isolieren, damit Sie diese Deklaration überspringen können. Ich finde mich in dieser Situation oft besser.
Dannejaha

0

Aus irgendeinem Grund [IgnoreDataMember]funktioniert das nicht immer bei mir und ich bekomme manchmal StackOverflowException(oder ähnliches). Also habe ich stattdessen (oder zusätzlich) angefangen, ein Muster zu verwenden, das ungefähr so ​​aussieht, wenn ich POSTin Objectsmeine API gehe:

[Route("api/myroute")]
[AcceptVerbs("POST")]
public IHttpActionResult PostMyObject(JObject myObject)
{
    MyObject myObjectConverted = myObject.ToObject<MyObject>();

    //Do some stuff with the object

    return Ok(myObjectConverted);
}

Im Grunde genommen übergebe ich ein JObjectund konvertiere es, nachdem es empfangen wurde, in Aviod-Probleme, die durch den eingebauten Serializer verursacht werden und manchmal eine Endlosschleife verursachen, während die Objekte analysiert werden.

Wenn jemand einen Grund kennt, warum dies in irgendeiner Weise eine schlechte Idee ist, lassen Sie es mich bitte wissen.

Es kann erwähnenswert sein, dass es der folgende Code für eine EntityFramework-Klasseneigenschaft ist, der das Problem verursacht (wenn zwei Klassen aufeinander verweisen):

[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);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.