предотвратить сериализацию свойства в веб-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объект слишком сложен, и мне нужен другой набор свойств в разных методах, поэтому проще отметить во время выполнения каждое свойство, которое следует игнорировать.

Есть ли способ сделать это?

user1330271
источник
Вы можете добавить ScriptIgnore к свойству. просмотреть этот вопрос stackoverflow.com/questions/10169648/…
atbebtg

Ответы:

232

ASP.NET Web API использует 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; }
}

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

cuongle
источник
Я думаю, мне нужно будет найти способ добавлять и удалять атрибуты во время выполнения, используя jsonignore.
user1330271
РАБОТАЕТ, КАК ОЧАРОВАНИЕ! СПАСИБО :)
Пауло Родригес
Почему печально, что атрибут JsonIgnore не поддерживается с ответом XML?
Мукус
Datacontract - отличное решение. Это дает мне чистый REST API. В то же время, когда я сохраняю данные в no-sql, игнорируемые свойства сохраняются, несмотря на то, что объекты хранятся как json.
FrankyHollywood
1
@FedorSteeman Пространство имен JsonIgnore - Newtonsoft.Json, требуется пакет JSON.Net-nuget. DataContract и DataMember -attributes, с другой стороны, нуждаются в пространстве имен System.Runtime.Serialization-(и ссылаются, если оно отсутствует)
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

Вместо того, чтобы все по умолчанию сериализовать, вы можете воспользоваться подходом «opt-in». В этом случае сериализуются только те свойства, которые вы указали. Это делается с помощью 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.

CBono
источник
2
Единственный подход, который работал из коробки с .Net Core, чтобы скрыть унаследованные члены. Работает как для XML, так и для Json-сериализации. Слава
Piou
Мне нужна та же функциональность, но свойства включаются или исключаются, в зависимости от того, какой метод API вызывается, т.е. для разных вызовов API нужны разные данные. Любые предложения
Nithin Chandran
Это прекрасно работает, но моя главная проблема заключается в том, что эти конфигурации исчезают со всех скаффолдов dbcontext в EF Core. У кого-нибудь есть обходной путь для этого? Могут ли эти атрибуты быть в частичном классе или быть установлены программно?
Naner
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);
joym8
источник
Что-то должно было измениться с более новыми версиями, но я не смог заставить это работать. Я мог заставить его работать, используя «new» вместо «as» при изменении резольвера. Тип JsonContractResolver по какой-то причине несовместим. Проблема с созданием нового заключается в том, что он перезаписывает его для всех, а не только для одного.
Kalel Wade
Мне удалось заставить это работать с помощью метода Request.CreateResponse (), который получает MediaTypeFormatter, например, так: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {SerializerSettings = new JsonSerializerSettings {ContractResolver = new PublicDomainJsonLontOnt {IllOnt} {Allow> «IringOnt» Байты "," MimeType "," Ширина "," Высота "}}}}; return Request.CreateResponse (HttpStatusCode.OK, изображение, jsonMediaTypeFormatter);
Пол
Что если мы также хотим, чтобы заблокированные свойства игнорировались в ответе XML?
Карлос П
Если обработчик контракта данных не назначается один раз для каждого запроса, это не является потокобезопасным. Я думаю, что это назначается один раз, в классе запуска.
Sprague
2
Хуже того, я протестировал это, и вызов createproperties кэшируется решателем контракта. Этот ответ в лучшем случае наивен, в худшем - опасен.
Sprague
19

Я покажу вам 2 способа достичь желаемого:

Первый способ: Украсьте свое поле атрибутом 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 не будет сериализовано.

foxhard
источник
Это не сделано через веб-интерфейс, веб-интерфейс по умолчанию использует Json.NET для сериализации. Этот процесс выполняется Json.NET, а не веб-API
Хамид Pourjam
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,
        }),
    }));
}
tsu1980
источник
4

Возможно, вы сможете использовать AutoMapper и использовать .Ignore()сопоставление, а затем отправить сопоставленный объект

CreateMap<Foo, Foo>().ForMember(x => x.Bar, opt => opt.Ignore());
kenwarner
источник
3

Работает нормально, просто добавив: [IgnoreDataMember]

Поверх свойства p, например:

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();
    }
Dannejaha
источник
Также, возможно, лучшим решением будет изолировать View-Models от моделей «Back end», так что вы можете пропустить это объявление. Я часто чувствую себя лучше в этой ситуации.
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, которое вызывает проблему (если два класса ссылаются друг на друга):

[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
источник