mencegah properti dari serial di web API


174

Saya menggunakan API web MVC 4 dan formulir web asp.net 4.0 untuk membangun API sisanya. Ini bekerja dengan baik:

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

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

    return httpResponseMessage;
}

Sekarang saya perlu mencegah beberapa properti untuk serial. Saya tahu saya dapat menggunakan beberapa LINQ di daftar dan hanya mendapatkan properti yang saya butuhkan, dan umumnya ini adalah pendekatan yang baik, tetapi dalam skenario ini somethingobjeknya terlalu kompleks, dan saya memerlukan set properti yang berbeda dalam metode yang berbeda, jadi lebih mudah untuk menandai, pada saat runtime, setiap properti harus diabaikan.

Apakah ada cara untuk melakukan itu?


Anda dapat menambahkan ScriptIgnore ke properti. lihat pertanyaan ini stackoverflow.com/questions/10169648/...
atbebtg

Jawaban:


232

ASP.NET Web API menggunakan Json.Netsebagai formatter default, jadi jika aplikasi Anda hanya menggunakan JSON sebagai format data, Anda dapat menggunakan [JsonIgnore]untuk mengabaikan properti untuk serialisasi:

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

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

Tapi, cara ini tidak mendukung format XML. Jadi, jika aplikasi Anda harus mendukung format XML lebih banyak (atau hanya mendukung XML), alih-alih menggunakan Json.Net, Anda harus menggunakan [DataContract]yang mendukung JSON dan 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; }
}

Untuk lebih memahami, Anda bisa membaca artikel resmi .


Saya pikir saya harus menemukan cara untuk menambah dan menghapus atribut pada saat run time menggunakan jsonignore.
user1330271

BEKERJA SEPERTI PESONA! TERIMA KASIH :)
Paulo Rodrigues

Mengapa sedih bahwa atribut JsonIgnore tidak didukung dengan respons XML?
Mukus

Datacontract adalah solusi hebat. Ini memberi saya API REST bersih. Pada saat yang sama ketika saya menyimpan data dalam no-sql, properti yang diabaikan tetap ada meskipun objek disimpan sebagai json.
FrankyHollywood

1
@FedorSteeman Namespace dari JsonIgnore adalah Newtonsoft.Json, membutuhkan paket JSON.Net-nuget. DataContract dan DataMember -memberikan kontribusi di sisi lain membutuhkan System.Runtime.Serialization-namespace (dan referensi jika tidak ada)
Esko

113

Menurut halaman dokumentasi API Web JSON dan XML Serialization di ASP.NET Web API untuk secara eksplisit mencegah serialisasi pada properti yang bisa Anda gunakan [JsonIgnore]untuk serializer Json atau [IgnoreDataMember]untuk serializer XML default.

Namun dalam pengujian saya perhatikan bahwa [IgnoreDataMember]mencegah serialisasi untuk permintaan XML dan Json, jadi saya akan merekomendasikan menggunakan itu daripada mendekorasi properti dengan banyak atribut.


2
Ini jawaban yang lebih baik. Ini mencakup XML dan JSON dengan satu atribut.
Oliver

17
Sedih [IgnoreDataMember]tampaknya tidak berfungsi dengan objek proxy EF 6 yang malas (properti virtual). [DataContract]dan [DataMember]lakukan.
Nick

32

Alih-alih membiarkan semuanya serialisasi secara default, Anda dapat mengambil pendekatan "opt-in". Dalam skenario ini, hanya properti yang Anda tentukan diizinkan untuk diserialisasi. Anda melakukan ini dengan DataContractAttributedan DataMemberAttribute, ditemukan di System.Runtime.Serialization namespace.

Ini DataContactAttributediterapkan ke kelas, dan DataMemberAttributediterapkan untuk setiap anggota yang Anda ingin serialkan:

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

Berani saya katakan ini adalah pendekatan yang lebih baik karena memaksa Anda untuk membuat keputusan eksplisit tentang apa yang akan atau tidak akan membuatnya melalui serialisasi. Itu juga memungkinkan kelas model Anda untuk hidup dalam sebuah proyek sendiri, tanpa mengambil ketergantungan pada JSON.net hanya karena di tempat lain Anda membuat cerita bersambung dengan JSON.net.


2
Satu-satunya pendekatan yang bekerja di luar kotak dengan .Net Core untuk menyembunyikan anggota yang diwarisi. Bekerja untuk serialisasi XML dan Json. Kudos
Piou

Saya memerlukan fungsi yang sama tetapi properti dimasukkan atau dikecualikan tergantung pada metode api yang dipanggil yaitu data yang berbeda diperlukan untuk panggilan api yang berbeda. Ada saran
Nithin Chandran

Ini berfungsi dengan baik, tetapi masalah utama saya adalah bahwa konfigurasi ini hilang dengan setiap perancah dbcontext di EF Core, apakah ada yang punya solusi untuk itu? Bisakah atribut ini berada di kelas parsial, atau diatur secara terprogram?
Naner

20

Ini bekerja untuk saya: Buat penyelesai kontrak khusus yang memiliki properti publik yang disebut AllowList dari tipe array string Dalam tindakan Anda, modifikasi properti itu tergantung pada apa yang diperlukan tindakan untuk mengembalikan.

1. buat penyelesai kontrak khusus:

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. gunakan penyelesaian kontrak kustom dalam tindakan

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

Pendekatan ini memungkinkan saya untuk mengizinkan / melarang permintaan spesifik alih-alih memodifikasi definisi kelas. Dan jika Anda tidak memerlukan serialisasi XML, jangan lupa untuk mematikannya di App_Start\WebApiConfig.csAPI Anda atau Anda akan mengembalikan properti yang diblokir jika klien meminta xml alih-alih json.

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

Sesuatu pasti telah berubah dengan versi yang lebih baru, tetapi saya tidak dapat membuatnya berfungsi. Saya bisa membuatnya bekerja dengan melakukan 'baru' alih-alih 'sebagai' saat memodifikasi resolver. Jenis JsonContractResolver tidak kompatibel untuk beberapa alasan. Masalah dengan melakukan yang baru adalah bahwa itu menimpa untuk semua, bukan hanya satu.
Kalel Wade

Saya berhasil mendapatkan ini berfungsi dengan menggunakan metode Request.CreateResponse () yang menerima MediaTypeFormatter, seperti ini: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonComplat {{"TemplateRout {{" Bytes "," MimeType "," Width "," Height "}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Paul

Bagaimana jika kita juga ingin properti yang diblokir diabaikan dalam respons XML?
Carlos P

Kecuali jika penyelesai kontrak data ditetapkan satu kali per permintaan, ini bukan utas yang aman. Saya pikir ini diberikan satu kali, di kelas startup.
Sprague

2
Lebih buruk lagi, saya telah menguji ini dan panggilan properti diciptakan di-cache oleh penyelesai kontrak. Jawaban ini naif di terbaik, berbahaya paling buruk.
Sprague

19

Saya akan menunjukkan kepada Anda 2 cara untuk mencapai apa yang Anda inginkan:

Cara pertama: Hiasi bidang Anda dengan atribut JsonProperty untuk melewati serialisasi bidang itu jika itu nol.

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

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

Cara kedua: Jika Anda bernegosiasi dengan beberapa skenario kompleks maka Anda dapat menggunakan konvensi Web Api ("ShouldSerialize") untuk melewati serialisasi bidang itu tergantung pada beberapa logika tertentu.

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 menggunakan JSON.Net dan menggunakan refleksi ke serialisasi sehingga ketika telah mendeteksi (misalnya) metode ShouldSerializeFieldX () bidang dengan nama FieldX tidak akan serial.


Ini tidak dilakukan oleh api web, api web menggunakan Json.NET secara default untuk membuat serial. Proses ini dilakukan oleh Json.NET bukan web api
Hamid Pourjam

1
Solusi kedua bagus karena memungkinkan untuk menjaga agnostik teknologi objek Domain tanpa perlu menulis ulang DTO hanya untuk menyembunyikan beberapa bidang.
Raffaeu

17

Saya terlambat ke permainan, tetapi benda anonim akan melakukan trik:

[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

Coba gunakan IgnoreDataMemberproperti

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

5

Hampir sama dengan jawaban greatbear302, tetapi saya membuat ContractResolver per permintaan.

1) Buat ContractResolver kustom

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) Gunakan penyelesai kontrak khusus dalam tindakan

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

Edit:

Itu tidak berfungsi seperti yang diharapkan (isolasikan resolver per permintaan). Saya akan menggunakan benda anonim.

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

Anda mungkin dapat menggunakan AutoMapper dan menggunakan .Ignore()pemetaan lalu mengirim objek yang dipetakan

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

3

Bekerja dengan baik hanya dengan menambahkan: [IgnoreDataMember]

Di atas propertyp, seperti:

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

Ini berfungsi dengan ApiController. Kode:

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

Mungkin juga solusi yang lebih baik adalah mengisolasi View-Models dari model "Back end", sehingga Anda dapat melewati deklarasi ini. Saya sering menemukan diri saya lebih baik dalam situasi itu.
Dannejaha

0

Untuk beberapa alasan [IgnoreDataMember]tidak selalu berhasil untuk saya, dan saya terkadang mendapatkan StackOverflowException(atau serupa). Jadi sebagai gantinya (atau sebagai tambahan) saya sudah mulai menggunakan pola yang terlihat seperti ini ketika POSTmasuk Objectske API saya:

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

    //Do some stuff with the object

    return Ok(myObjectConverted);
}

Jadi pada dasarnya saya mengirimkan JObjectdan mengonversinya setelah menerima masalah aviod yang disebabkan oleh serializer built-in yang terkadang menyebabkan loop tak terbatas saat mengurai objek.

Jika seseorang mengetahui alasan bahwa ini adalah ide yang buruk, tolong beri tahu saya.

Mungkin perlu dicatat bahwa ini adalah kode berikut untuk properti Kelas EntityFramework yang menyebabkan masalah (Jika dua kelas merujuk satu sama lain):

[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);
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.