запобігти серіалізації власності у веб-API


174

Я використовую веб-API MVC 4 та веб-форми asp.net 4.0 для створення API відпочинку. Це чудово працює:

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

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

    return httpResponseMessage;
}

Тепер мені потрібно запобігти серіалізації деяких властивостей. Я знаю, що я можу використовувати деякий LINQ над списком і отримувати лише потрібні мені властивості, і, як правило, це хороший підхід, але в цьому сценарії somethingоб'єкт занадто складний, і мені потрібен різний набір властивостей у різних методах, тому це простіше позначити під час виконання кожну властивість, яку слід ігнорувати.

Чи є спосіб це зробити?


Ви можете додати ScriptIgnore до ресурсу. подивитися на це питання stackoverflow.com/questions/10169648 / ...
atbebtg

Відповіді:


232

Веб-API ASP.NET використовує Json.Netяк формат за замовчуванням, тому якщо ваша програма використовує лише JSON як формат даних, ви можете [JsonIgnore]ігнорувати властивість для серіалізації:

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

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

Але цей спосіб не підтримує формат XML. Отже, якщо ваша програма має більше підтримувати формат XML (або підтримувати лише XML), замість того Json.Net, ви повинні використовувати, [DataContract]який підтримує і JSON, і 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; }
}

Для більшого розуміння ви можете прочитати офіційну статтю .


Я думаю, що мені доведеться знайти спосіб додавання та видалення атрибутів під час виконання за допомогою jsonignore.
користувач1330271

РОБОТАЄТЬСЯ ЛИШЕ ЧАРМУ! ДЯКУЙТЕ :)
Пауло Родрігес

Чому сумно, що атрибут JsonIgnore не підтримується у відповідь XML?
Мукус

Контракт даних - чудове рішення. Це дає мені чистий API REST. У той же час, коли я зберігаю дані в no-sql, ігноровані властивості зберігаються, незважаючи на об'єкти, що зберігаються як json.
FrankyHollywood

1
@FedorSteeman Простір імен JsonIgnore - Newtonsoft.Json, потрібен JSON.Net-nuget пакет. DataContract і DataMember - розподіли, з іншого боку, потребують System.Runtime.Serialization-space names (і посилання, якщо його немає)
Esko,

113

Відповідно до сторінки документації Web API JSON та XML Serialization у ASP.NET Web API для явного запобігання серіалізації у властивості, яку можна використовувати [JsonIgnore]для серіалізатора Json або [IgnoreDataMember]для серіалізатора XML за замовчуванням.

Однак при тестуванні я помітив, що [IgnoreDataMember]перешкоджає серіалізації як для запитів XML, так і для Json, тому я рекомендував би використовувати це, а не прикрашати властивість з декількома атрибутами.


2
Це краща відповідь. Він охоплює XML та JSON з одним атрибутом.
Олівер

17
На жаль [IgnoreDataMember], не здається, що він працює з ліниво завантаженими проксі-об'єктами EF 6 (віртуальні властивості). [DataContract]і [DataMember]робити, однак.
Нік

32

Замість того, щоб давати можливість серіалізувати все за замовчуванням, ви можете скористатися підходом "відмови". У цьому сценарії дозволяється серіалізувати лише вказані вами властивості. Ви робите це за допомогою DataContractAttributeі DataMemberAttribute, знайденого в System.Runtime.Serialization просторі імен .

DataContactAttributeЗастосовуються до класу, і DataMemberAttributeзастосовуються до кожного члена ви хочете серіалізовать:

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

Смію сказати, що це кращий підхід, оскільки він змушує приймати чіткі рішення щодо того, що буде чи не буде, через серіалізацію. Це також дозволяє вашим модельним класам жити в проекті самостійно, не приймаючи залежність від JSON.net лише тому, що десь ви трапляєте їх серіалізувати з JSON.net.


2
Єдиний підхід, який розробив коробку з .Net Core, щоб приховати успадковані члени. Працює як для XML, так і для Json серіалізації. Кудос
Піу

Мені потрібна однакова функціональність, але властивості включені або виключені залежать від того, який метод api викликається, тобто різні дані потрібні для різних дзвінків api. Будь-які пропозиції
Нітін Чандран

Це чудово працює, але головне моє питання полягає в тому, що ці конфігурації відходять із кожним скелетом dbcontext в EF Core, чи є у когось вирішення цього питання? Чи можуть ці атрибути бути в частковому класі або бути задані програмно?
Нанер

20

Це працювало для мене: Створіть власну розв'язувальну систему контракту, яка має загальнодоступну властивість під назвою AllowList типу рядкового масиву. У своїй дії змініть цю властивість залежно від того, яку дію потрібно повернути.

1. створити користувальницьку рішення для контракту:

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. використовувати користувальницьку рішення для контракту в дії

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

Такий підхід дозволив мені дозволити / заборонити для конкретного запиту замість зміни визначення класу. І якщо вам не потрібна серіалізація XML, не забудьте вимкнути її у своєму App_Start\WebApiConfig.csабо ваш API поверне заблоковані властивості, якщо клієнт вимагає xml замість json.

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

Щось, мабуть, змінилося з новішими версіями, але я не зміг змусити це працювати. Я міг би змусити його працювати, роблячи "нове" замість "як" під час зміни резолюції. Тип JsonContractResolver чомусь не можна порівняти. Проблема із створенням нового полягає в тому, що він замінює його для всіх, а не лише одного.
Калель Вейд

Мені вдалося налагодити цю роботу за допомогою методу Request.CreateResponse (), який отримує MediaTypeFormatter, як це: var jsonMediaTypeFormatter = новий JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonContraptResolver "новий" Байти "," MimeType "," Ширина "," Висота "}}}}; return Request.CreateResponse (HttpStatusCode.OK, image, jsonMediaTypeFormatter);
Павло

Що робити, якщо ми також хочемо, щоб заблоковані властивості ігнорувались у відповіді XML?
Карлос П

Якщо вирішувач договору даних не буде призначений один раз на запит, це не є безпечним для потоків. Я думаю, що це призначено один раз, у класі запуску.
Sprague

2
Ще гірше, що Ive перевірив це, і виклик createproperties кешується вирішувачем контракту. Ця відповідь в кращому випадку наївна, в гіршому - небезпечна.
Sprague

19

Я покажу вам два способи досягти того, що ви хочете:

Перший спосіб: прикрасьте своє поле атрибутом JsonProperty, щоб пропустити серіалізацію цього поля, якщо воно є недійсним.

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

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

Другий спосіб: Якщо ви ведете переговори з деякими складними сценаріями, тоді ви можете використовувати конвенцію Web Api ("ShouldSerialize"), щоб пропустити серіалізацію цього поля залежно від певної логіки.

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 використовує JSON.Net, і він використовує відображення для серіалізації, тому коли він виявив (наприклад) метод ShouldSerializeFieldX (), поле з іменем FieldX не буде серіалізуватися.


Це не робиться веб-api, веб-api використовує Json.NET за замовчуванням для серіалізації. Цей процес виконується Json.NET not web api
Хамід Пурджам

1
Друге рішення є приємним, оскільки дозволяє тримати об'єктну технологію об'єкта Domain без необхідності переписування DTO, щоб приховати деякі поля.
Раффау

17

Я запізнююсь на гру, але анонімні об’єкти зробили б справу:

[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

Спробуйте використовувати IgnoreDataMemberвластивість

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

5

Майже те саме, що відповідь greatbear302, але я створюю ContractResolver за запит.

1) Створіть спеціальний 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) Використовуйте користувальницьку рішення для контракту в дії

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

Редагувати:

Він не працював так, як очікувалося (ізолювати роздільну здатність на запит). Я буду використовувати анонімні об’єкти.

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

Можливо, ви зможете використовувати AutoMapper і використовувати .Ignore()відображення, а потім надіслати відображений об’єкт

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

3

Чудово працює, просто додавши: [IgnoreDataMember]

Зверху на Propertyp, наприклад:

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

Це працює з ApiController. Код:

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

Також, можливо, кращим рішенням є ізоляція View-моделей від моделей «Заднього кінця», так що ви можете пропустити цю декларацію. У цій ситуації мені часто буває краще.
Dannejaha

0

Чомусь [IgnoreDataMember]не завжди працює для мене, і я іноді отримую StackOverflowException(або подібне). Тож замість цього (або на додачу) я почав використовувати шаблон, який виглядає приблизно так, коли я працював POSTу Objectsсвоєму 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);
}

Так я в основному проходжу в JObject і перетворюю його після того, як він отримав проблеми з aviod, викликані вбудованим серіалізатором, який іноді викликає нескінченний цикл під час розбору об'єктів.

Якщо хтось знає причину, що це якимось чином погана ідея, будь ласка, дайте мені знати.

Можливо, варто відзначити, що саме такий код для властивості EntityFramework Class викликає проблему (Якщо два класи посилаються один на одного):

[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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.