it-roy-ru.com

предотвратить сериализацию свойства в веб-API

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

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

135
user1330271

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

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

184
cuongle

Согласно странице документации Web API Сериализация JSON и XML в ASP.NET Web API для явного предотвращения сериализации для свойства вы можете использовать [JsonIgnore] для сериализатора Json или [IgnoreDataMember] для сериализатора XML по умолчанию.

Однако при тестировании я заметил, что [IgnoreDataMember] предотвращает сериализацию для запросов XML и Json, поэтому я бы рекомендовал использовать это вместо украшения свойства с несколькими атрибутами.

104
Michael Mason

Вместо того, чтобы позволить everything быть сериализованным по умолчанию, вы можете использовать подход «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.

25
CBono

Это сработало для меня: создайте пользовательский преобразователь контракта, который имеет открытое свойство 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);
19
joym8

Я опаздываю к игре, но анонимные объекты справятся с задачей:

[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;
}
15
Tim Hoolihan

Я покажу вам 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 не будет сериализовано.

14
foxhard

Попробуйте использовать свойство IgnoreDataMember

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

Почти так же, как ответ 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,
        }),
    }));
}
5
tsu1980

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

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

По какой-то причине [IgnoreDataMember] не всегда работает для меня, и я иногда получаю StackOverflowException (или подобное). Поэтому вместо этого (или в дополнение) я начал использовать шаблон, похожий на этот, когда POSTing в 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 и преобразую его после того, как он получен, чтобы избежать проблем, вызванных встроенным сериализатором, которые иногда вызывают бесконечный цикл при анализе объектов.

Если кто-то знает причину того, что это в любом случае плохая идея, пожалуйста, дайте мне знать.

Возможно, стоит отметить, что это следующий код для свойства класса 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);
}
0
Arg0n