zapobiec serializacji właściwości w internetowym interfejsie API

174

Używam internetowego interfejsu API MVC 4 i formularzy sieci web asp.net 4.0 do tworzenia reszt API. Działa świetnie:

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

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

    return httpResponseMessage;
}

Teraz muszę zapobiec serializacji niektórych właściwości. Wiem, że mogę użyć LINQ na liście i uzyskać tylko te właściwości, których potrzebuję, i ogólnie jest to dobre podejście, ale w obecnym scenariuszu somethingobiekt jest zbyt złożony i potrzebuję innego zestawu właściwości w różnych metodach, więc jest to łatwiej jest oznaczyć w czasie wykonywania każdą właściwość do zignorowania.

Czy jest na to sposób?

user1330271
źródło
Możesz dodać ScriptIgnore do właściwości. wyświetl to pytanie stackoverflow.com/questions/10169648/…
atbebtg

Odpowiedzi:

232

ASP.NET Web API jest używany Json.Netjako domyślny program formatujący, więc jeśli Twoja aplikacja używa tylko formatu JSON jako formatu danych, możesz użyć [JsonIgnore]do zignorowania właściwości dla serializacji:

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

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

Ale w ten sposób nie obsługuje formatu XML. Tak więc, w przypadku, gdy twoja aplikacja musi obsługiwać format XML bardziej (lub tylko obsługiwać XML), zamiast używać Json.Net, powinieneś użyć, [DataContract]który obsługuje zarówno JSON, jak i 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; }
}

Aby uzyskać więcej informacji, możesz przeczytać oficjalny artykuł .

cuongle
źródło
Myślę, że będę musiał znaleźć sposób na dodawanie i usuwanie atrybutów w czasie wykonywania za pomocą jsonignore.
user1330271
PRACOWALI JAK UROK! DZIĘKI :)
Paulo Rodrigues
Dlaczego to smutne, że atrybut JsonIgnore nie jest obsługiwany w odpowiedzi XML?
Mukus
Datacontract to świetne rozwiązanie. Daje mi czysty REST API. W tym samym czasie, gdy zapisuję dane w no-sql, zignorowane właściwości są utrwalane, mimo że obiekty są przechowywane jako json.
FrankyHollywood
1
@FedorSteeman Przestrzeń nazw JsonIgnore to Newtonsoft.Json, wymaga pakietu JSON.Net-nuget. DataContract i DataMember -attributes wymagają natomiast przestrzeni nazw System.Runtime.Serialization (i referencji, jeśli jej brakuje)
Esko
113

Zgodnie ze stroną dokumentacji interfejsu API sieci Web, Serializacja JSON i XML w ASP.NET Web API, aby jawnie zapobiec serializacji właściwości, której można użyć [JsonIgnore]dla serializatora Json lub [IgnoreDataMember]dla domyślnego serializatora XML.

Jednak podczas testów zauważyłem, że [IgnoreDataMember]zapobiega to serializacji zarówno dla żądań XML, jak i Json, więc zalecałbym używanie tego zamiast dekorowania właściwości wieloma atrybutami.

Michael Mason
źródło
2
To jest lepsza odpowiedź. Obejmuje XML i JSON z jednym atrybutem.
Oliver
17
Niestety [IgnoreDataMember]nie wydaje się działać z ładowanymi z opóźnieniem obiektami proxy EF 6 (właściwościami wirtualnymi). [DataContract]i [DataMember]jednak tak.
Nick
32

Zamiast pozwalać, aby wszystko było domyślnie serializowane, możesz zastosować podejście „opt-in”. W tym scenariuszu tylko określone właściwości mogą być serializowane. Robisz to za pomocą DataContractAttributei DataMemberAttribute, znajdującego się w przestrzeni nazw System.Runtime.Serialization .

DataContactAttributeJest stosowana do klasy i DataMemberAttributejest stosowana do każdego członka chcesz być w odcinkach:

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

Ośmielę się powiedzieć, że jest to lepsze podejście, ponieważ zmusza cię do podejmowania wyraźnych decyzji o tym, co zrobi, a czego nie zrobi poprzez serializację. Pozwala również klasom modelu żyć samodzielnie w projekcie, bez uzależnienia od JSON.net tylko dlatego, że gdzie indziej zdarza się, że serializujesz je za pomocą JSON.net.

CBono
źródło
2
Jedyne podejście, które działało po wyjęciu z pudełka z .Net Core, aby ukryć dziedziczonych członków. Działa zarówno w przypadku serializacji XML, jak i Json. Kudos
Piou
Potrzebuję tej samej funkcjonalności, ale właściwości są uwzględniane lub wykluczane, w zależności od tego, która metoda interfejsu API jest wywoływana, tj. Potrzebne są różne dane dla różnych wywołań interfejsu API. Wszelkie sugestie
Nithin Chandran
Działa to świetnie, ale moim głównym problemem jest to, że te konfiguracje znikają z każdym szkieletem dbcontext w EF Core, czy ktoś ma obejście tego problemu? Czy te atrybuty mogą znajdować się w klasie częściowej lub być ustawiane programowo?
Naner
20

To zadziałało dla mnie: Utwórz niestandardowy program do rozpoznawania umów, który ma publiczną właściwość o nazwie AllowList typu tablicowego. W akcji zmodyfikuj tę właściwość w zależności od tego, co akcja musi zwrócić.

1. utwórz niestandardowy program do rozpoznawania umów:

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. używać niestandardowego narzędzia do rozpoznawania umów w akcji

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

Takie podejście pozwoliło mi zezwolić / zabronić na określone żądanie zamiast modyfikować definicję klasy. A jeśli nie potrzebujesz serializacji XML, nie zapomnij go wyłączyć w App_Start\WebApiConfig.csswoim interfejsie. W przeciwnym razie interfejs API zwróci zablokowane właściwości, jeśli klient zażąda kodu XML zamiast json.

//remove xml serialization
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
joym8
źródło
Coś musiało się zmienić w nowszych wersjach, ale nie udało mi się to uruchomić. Mogłem to zmusić do działania, robiąc „nowy” zamiast „as” podczas modyfikowania programu rozpoznawania nazw. Typ JsonContractResolver nie jest kompatybilny z jakiegoś powodu. Problem z robieniem nowego polega na tym, że zastępuje go dla wszystkich zamiast tylko jednego.
Kalel Wade,
Udało mi się to uruchomić za pomocą metody Request.CreateResponse (), która odbiera MediaTypeFormatter, na przykład: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContractResolverOptIn "{AllowverOptIn", "{AllowverOpt]" Bajty ”,„ MimeType ”,„ Szerokość ”,„ Wysokość ”}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Paul
A co, jeśli chcemy, aby zablokowane właściwości były również ignorowane w odpowiedzi XML?
Carlos P,
O ile program rozpoznawania kontraktu danych nie jest przypisany raz na żądanie, nie jest to bezpieczne wątkowo. Myślę, że jest to przypisane raz, w klasie startowej.
Sprague
2
Co gorsza, przetestowałem to i wywołanie createproperties jest buforowane przez resolver kontraktu. Ta odpowiedź jest w najlepszym przypadku naiwna, w najgorszym niebezpieczna.
Sprague
19

Pokażę ci 2 sposoby na osiągnięcie tego, czego chcesz:

Pierwszy sposób: Udekoruj swoje pole atrybutem JsonProperty, aby pominąć serializację tego pola, jeśli jest ono puste.

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

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

Drugi sposób: jeśli prowadzisz negocjacje z niektórymi złożonymi scenariuszami, możesz użyć konwencji Web Api („ShouldSerialize”), aby pominąć serializację tego pola w zależności od określonej logiki.

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 używa JSON.Net i wykorzystuje odbicie do serializacji, więc po wykryciu (na przykład) metody ShouldSerializeFieldX () pole o nazwie FieldX nie zostanie serializowane.

foxhard
źródło
Nie jest to wykonywane przez interfejs API sieci Web, interfejs API sieci Web używa domyślnie Json.NET do serializacji. Ten proces jest wykonywany przez Json.NET, a nie przez interfejs sieciowy
Hamid Pourjam
1
Drugie rozwiązanie jest fajne, ponieważ pozwala na utrzymanie agnostycznej technologii obiektowej domeny bez konieczności przepisywania DTO tylko po to, aby ukryć niektóre pola.
Raffaeu
17

Spóźniłem się na grę, ale anonimowe przedmioty załatwią sprawę:

[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;
}
Tim Hoolihan
źródło
11

Spróbuj użyć IgnoreDataMemberwłaściwości

public class Foo
    {
        [IgnoreDataMember]
        public int Id { get; set; }
        public string Name { get; set; }
    }
Kavi
źródło
5

Prawie to samo, co odpowiedź Greatbear302, ale tworzę ContractResolver na żądanie.

1) Utwórz niestandardowy 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) Użyj niestandardowego narzędzia do rozpoznawania umów w akcji

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

Edytować:

Nie działało zgodnie z oczekiwaniami (izolowanie programu rozpoznawania nazw na żądanie). Użyję anonimowych obiektów.

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,
        }),
    }));
}
tsu1980
źródło
4

Możesz być w stanie użyć funkcji AutoMapper i użyć .Ignore()mapowania, a następnie wysłać zmapowany obiekt

CreateMap<Foo, Foo>().ForMember(x => x.Bar, opt => opt.Ignore());
kenwarner
źródło
3

Działa dobrze, po prostu dodając: [IgnoreDataMember]

Na górze nieruchomościp, na przykład:

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

Działa to z ApiController. Kod:

[Route("api/Context/UserSettings")]
    [HttpGet, HttpPost]
    public UserSettingsModel UserSettings()
    {
        return _contextService.GetUserSettings();
    }
Dannejaha
źródło
Być może lepszym rozwiązaniem jest również odizolowanie modeli widoków od modeli „zaplecza”, aby można było pominąć tę deklarację. Często czuję się lepiej w tej sytuacji.
Dannejaha,
0

Z jakiegoś powodu [IgnoreDataMember]nie zawsze mi się to udaje, a czasem dostaję StackOverflowException(lub podobnie). Zamiast tego (lub dodatkowo) zacząłem używać wzorca wyglądającego mniej więcej tak, kiedy POSTwchodzę Objectsdo mojego 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);
}

Więc w zasadzie przekazuję an JObjecti konwertuję go po otrzymaniu, aby uniknąć problemów spowodowanych przez wbudowany serializator, który czasami powoduje nieskończoną pętlę podczas analizowania obiektów.

Jeśli ktoś zna powód, dla którego jest to w jakikolwiek sposób zły pomysł, daj mi znać.

Warto zauważyć, że jest to następujący kod właściwości klasy EntityFramework, który powoduje problem (jeśli dwie klasy odwołują się do siebie):

[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);
}
Arg0n
źródło