ngăn tài sản không được tuần tự hóa trong API web


174

Tôi đang sử dụng API web MVC 4 và các mẫu web asp.net 4.0 để xây dựng API còn lại. Nó hoạt động rất tốt:

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

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

    return httpResponseMessage;
}

Bây giờ tôi cần phải ngăn chặn một số thuộc tính được nối tiếp. Tôi biết tôi có thể sử dụng một số LINQ trong danh sách và chỉ nhận các thuộc tính tôi cần, và nói chung đó là một cách tiếp cận tốt, nhưng trong kịch bản hiện tại, somethingđối tượng quá phức tạp và tôi cần một tập các thuộc tính khác nhau trong các phương thức khác nhau, vì vậy nó dễ dàng hơn để đánh dấu, tại thời gian chạy, mỗi thuộc tính được bỏ qua.

Có cách nào làm được việc này không?


Bạn có thể thêm ScriptIgnore vào tài sản. xem câu hỏi này stackoverflow.com/questions/10169648/
Khắc

Câu trả lời:


231

ASP.NET Web API sử dụng Json.Netlàm định dạng mặc định, vì vậy nếu ứng dụng của bạn chỉ sử dụng JSON làm định dạng dữ liệu, bạn có thể sử dụng [JsonIgnore]để bỏ qua thuộc tính để tuần tự hóa:

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

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

Nhưng, cách này không hỗ trợ định dạng XML. Vì vậy, trong trường hợp ứng dụng của bạn phải hỗ trợ định dạng XML nhiều hơn (hoặc chỉ hỗ trợ XML), thay vì sử dụng Json.Net, bạn nên sử dụng [DataContract]hỗ trợ cả JSON và 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; }
}

Để hiểu rõ hơn, bạn có thể đọc bài viết chính thức .


Tôi nghĩ rằng tôi sẽ phải tìm cách thêm và xóa các thuộc tính trong thời gian chạy bằng jsonignore.
dùng1330271

LÀM VIỆC NHƯ NGƯỜI Ở ! CẢM ƠN :)
Paulo Coleues

Tại sao thật buồn khi thuộc tính JsonIgnore không được hỗ trợ với phản hồi XML?
Mukus

Datacontract là một giải pháp tuyệt vời. Nó cung cấp cho tôi một API REST sạch. Đồng thời khi tôi lưu dữ liệu ở chế độ không vuông, các thuộc tính bị bỏ qua vẫn tồn tại mặc dù các đối tượng được lưu trữ dưới dạng json.
FrankyHollywood 16/03/18

1
@FedorSteeman Không gian tên của JsonIgnore là Newtonsoft.Json, cần gói JSON.Net-nuget. Mặt khác, DataContract và DataMember - phân phối cần có System.R.78.Serialization - không gian tên (và tham chiếu nếu thiếu)
Esko

113

Theo trang tài liệu API Web JSON và XML serialization trong ASP.NET Web API để ngăn chặn rõ ràng việc tuần tự hóa trên một thuộc tính mà bạn có thể sử dụng [JsonIgnore]cho trình tuần tự Json hoặc [IgnoreDataMember]cho trình tuần tự XML mặc định.

Tuy nhiên, trong thử nghiệm tôi đã nhận thấy rằng [IgnoreDataMember]ngăn chặn việc tuần tự hóa cho cả các yêu cầu XML và Json, vì vậy tôi khuyên bạn nên sử dụng điều đó thay vì trang trí một thuộc tính có nhiều thuộc tính.


2
Đây là câu trả lời tốt hơn. Nó bao gồm XML và JSON với một thuộc tính.
Oliver

17
Đáng buồn [IgnoreDataMember]là dường như không hoạt động với các đối tượng proxy EF 6 được tải lười biếng (thuộc tính ảo). [DataContract][DataMember]làm, tuy nhiên.
Nick

32

Thay vì để mọi thứ được tuần tự hóa theo mặc định, bạn có thể thực hiện phương pháp "chọn tham gia". Trong trường hợp này, chỉ các thuộc tính bạn chỉ định mới được phép nối tiếp. Bạn làm điều này với DataContractAttributeDataMemberAttribute, được tìm thấy trong không gian tên System.R.78.Serialization .

Cái DataContactAttributenày được áp dụng cho lớp và cái DataMemberAttributeđược áp dụng cho mỗi thành viên bạn muốn được tuần tự hóa:

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

Dare tôi nói rằng đây là một cách tiếp cận tốt hơn bởi vì nó buộc bạn phải đưa ra quyết định rõ ràng về những gì sẽ hoặc sẽ không làm cho nó thông qua tuần tự hóa. Nó cũng cho phép các lớp mô hình của bạn tự sống trong một dự án mà không cần phụ thuộc vào JSON.net chỉ vì một nơi nào khác bạn tình cờ sắp xếp chúng với JSON.net.


2
Cách tiếp cận duy nhất có hiệu quả với .Net Core để ẩn các thành viên được kế thừa. Hoạt động cho cả tuần tự hóa XML và Json. Kudos
Piou

Tôi cần cùng chức năng nhưng các thuộc tính được bao gồm hoặc loại trừ tùy thuộc vào phương thức api nào được gọi, tức là cần có dữ liệu khác nhau cho các cuộc gọi api khác nhau. Mọi góp ý
Nithin Chandran

Điều này hoạt động rất tốt, nhưng vấn đề chính của tôi là các cấu hình này biến mất với mọi giàn giáo dbcontext trong EF Core, có ai có cách giải quyết cho điều đó không? Những thuộc tính này có thể nằm trong một lớp một phần, hoặc được đặt theo chương trình không?
Naner

20

Điều này làm việc cho tôi: Tạo một trình giải quyết hợp đồng tùy chỉnh có thuộc tính công khai gọi là AllowList của kiểu mảng chuỗi. Trong hành động của bạn, sửa đổi thuộc tính đó tùy thuộc vào những gì hành động cần trả lại.

1. tạo một trình giải quyết hợp đồng tùy chỉnh:

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. sử dụng trình giải quyết hợp đồng tùy chỉnh trong hành động

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

Cách tiếp cận này cho phép tôi cho phép / không cho phép yêu cầu cụ thể thay vì sửa đổi định nghĩa lớp. Và nếu bạn không cần tuần tự hóa XML, đừng quên tắt nó trong App_Start\WebApiConfig.csAPI của bạn hoặc API của bạn sẽ trả về các thuộc tính bị chặn nếu máy khách yêu cầu xml thay vì json.

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

Một cái gì đó đã thay đổi với các phiên bản mới hơn nhưng tôi không thể làm cho nó hoạt động. Tôi có thể làm cho nó hoạt động bằng cách thực hiện 'mới' thay vì 'như' khi sửa đổi trình phân giải. Loại JsonContractResolver không thể tương thích vì một số lý do. Vấn đề với việc làm mới là nó ghi đè lên tất cả thay vì chỉ một.
Kalel Wade

Tôi đã quản lý để làm việc này bằng cách sử dụng phương thức Request.CreateResponse () để nhận MediaTypeFormatter, như thế này: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter {serializerSinstall = new JsonMediaTypeFormatter {serializerSinstall = new JsonSerializerSinstall {ContractResolver = new Byte "," MimeType "," Width "," height "}}}}; trả về Request.CreateResponse (HttpStatusCode.OK, hình ảnh, jsonMediaTypeFormatter);
Paul

Điều gì xảy ra nếu chúng ta cũng muốn các thuộc tính bị chặn bị bỏ qua trong phản hồi XML?
Carlos P

Trừ khi trình giải quyết hợp đồng dữ liệu được chỉ định một lần cho mỗi yêu cầu, đây không phải là luồng an toàn. Tôi nghĩ rằng điều này được chỉ định một lần, trong lớp khởi động.
Sprague

2
Thậm chí tệ hơn, tôi đã thử nghiệm điều này và cuộc gọi sáng tạo được lưu trữ bởi bộ giải quyết hợp đồng. Câu trả lời này là ngây thơ ở mức tốt nhất, nguy hiểm ở mức tồi tệ nhất.
Sprague

19

Tôi sẽ chỉ cho bạn 2 cách để thực hiện những gì bạn muốn:

Cách thứ nhất: Trang trí trường của bạn với thuộc tính JsonProperty để bỏ qua việc tuần tự hóa trường đó nếu nó là null.

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

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

Cách thứ hai: Nếu bạn đang đàm phán với một số tình huống phức tạp thì bạn có thể sử dụng quy ước Api Web ("ShouldSerialize") để bỏ qua việc tuần tự hóa trường đó tùy thuộc vào một số logic cụ thể.

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 sử dụng JSON.Net và nó sử dụng sự phản chiếu để tuần tự hóa nên khi nó đã phát hiện (ví dụ), phương thức ShouldSerializeFieldX (), trường có tên FieldX sẽ không được tuần tự hóa.


Nó không được thực hiện bởi web api, api web sử dụng Json.NET theo mặc định để tuần tự hóa. Quá trình này được thực hiện bởi Json.NET chứ không phải web api
Hamid Pourjam

1
Giải pháp thứ hai là tốt vì nó cho phép giữ cho công nghệ đối tượng Miền không bị nghi ngờ mà không cần phải viết lại các DTO chỉ để ẩn một số trường.
Raffaeu

17

Tôi đến trễ trò chơi, nhưng một đối tượng ẩn danh sẽ thực hiện mánh khóe:

[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

Thử sử dụng IgnoreDataMembertài sản

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

5

Gần giống như câu trả lời của greatbear302, nhưng tôi tạo ContractResolver theo yêu cầu.

1) Tạo ContractResolver tùy chỉnh

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) Sử dụng trình giải quyết hợp đồng tùy chỉnh trong hành động

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

Biên tập:

Nó không hoạt động như mong đợi (cô lập trình phân giải theo yêu cầu). Tôi sẽ sử dụng các đối tượng ẩn danh.

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

Bạn có thể sử dụng AutoMapper và sử dụng .Ignore()ánh xạ và sau đó gửi đối tượng được ánh xạ

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

3

Hoạt động tốt bằng cách chỉ cần thêm: [IgnoreDataMember]

Trên đầu trang, như:

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

Điều này hoạt động với ApiControll. Mật mã:

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

Ngoài ra, có thể một giải pháp tốt hơn là tách biệt các Mô hình xem khỏi các mô hình "Mặt sau", do đó bạn có thể bỏ qua tuyên bố này. Tôi thấy mình tốt hơn trong tình huống đó thường xuyên.
Dannejaha

0

Vì một số lý do [IgnoreDataMember]không phải lúc nào tôi cũng làm việc và đôi khi tôi nhận được StackOverflowException(hoặc tương tự). Vì vậy, thay vào đó (hoặc ngoài ra) tôi đã bắt đầu sử dụng một mẫu trông giống như thế này khi POSTnhập Objectsvào API của mình:

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

    //Do some stuff with the object

    return Ok(myObjectConverted);
}

Vì vậy, về cơ bản tôi vượt qua trong một JObject và chuyển đổi nó sau khi nó đã nhận được các vấn đề aviod gây ra bởi serializer tích hợp đôi khi gây ra một vòng lặp vô hạn trong khi phân tích cú pháp các đối tượng.

Nếu ai đó biết một lý do mà đây là một ý tưởng tồi, xin vui lòng cho tôi biết.

Có thể đáng lưu ý rằng đó là mã sau đây cho thuộc tính Lớp EntityFramework gây ra sự cố (Nếu hai lớp tham chiếu lẫn nhau):

[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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.