ป้องกันคุณสมบัติไม่ให้ต่อเนื่องใน web API


174

ฉันใช้ MVC 4 web API และ 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

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

เพื่อความเข้าใจที่มากขึ้นคุณสามารถอ่านบทความอย่างเป็นทางการ


ฉันคิดว่าฉันจะต้องหาวิธีในการเพิ่มและลบคุณลักษณะในเวลาทำงานโดยใช้ jsonignore
user1330271

ทำงานเหมือนมีเสน่ห์! ขอบคุณ :)
Paulo Rodrigues

เหตุใดจึงเป็นเรื่องน่าเศร้าที่แอตทริบิวต์ JsonIgnore ไม่รองรับการตอบสนอง XML
Mukus

Datacontract เป็นทางออกที่ดี มันทำให้ฉันสะอาด REST API ในขณะเดียวกันเมื่อฉันบันทึกข้อมูลในรูปแบบ no-sql คุณสมบัติที่ถูกเพิกเฉยจะยังคงอยู่แม้ว่าวัตถุจะถูกจัดเก็บเป็น json
FrankyHollywood

1
@FedorSteeman เนมสเปซของ JsonIgnore คือ Newtonsoft.Json ต้องการแพ็คเกจ JSON.Net-nuget DataContract และ DataMember - คุณลักษณะจำเป็นต้องใช้ System.Runtime.Serialization-namespace (และการอ้างอิงหากไม่มี)
Esko

113

ตามหน้าเอกสารของ Web API JSON และ XML Serialization ใน ASP.NET Web APIเพื่อป้องกันการทำให้เป็นอนุกรมในคุณสมบัติคุณสามารถใช้[JsonIgnore]Json serializer หรือ[IgnoreDataMember]สำหรับ XML serializer เริ่มต้นได้

อย่างไรก็ตามในการทดสอบฉันสังเกตเห็นว่า[IgnoreDataMember]ป้องกันการทำให้เป็นอนุกรมสำหรับทั้งคำขอ XML และ Json ดังนั้นฉันขอแนะนำให้ใช้สิ่งนั้นแทนที่จะตกแต่งคุณสมบัติที่มีคุณสมบัติหลายอย่าง


2
นี่คือคำตอบที่ดีกว่า ครอบคลุม XML และ JSON ด้วยคุณลักษณะเดียว
โอลิเวอร์

17
น่าเศร้าที่[IgnoreDataMember]ไม่ได้ทำงานกับวัตถุพร็อกซีของ EF 6 ที่ขี้เกียจ (คุณสมบัติเสมือน) [DataContract]และ[DataMember]ทำอย่างไร
Nick

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 Kudos
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);

มีบางอย่างที่ต้องเปลี่ยนด้วยเวอร์ชันที่ใหม่กว่า แต่ฉันไม่สามารถใช้งานได้ ฉันสามารถทำให้มันทำงานได้โดยการทำ 'ใหม่' แทน 'เป็น' เมื่อทำการแก้ไขตัวแก้ไข ประเภท JsonContractResolver เข้ากันไม่ได้ด้วยเหตุผลบางประการ ปัญหาของการทำสิ่งใหม่คือมันเขียนทับมันทั้งหมดแทนที่จะเป็นแค่สิ่งเดียว
Kalel Wade

ฉันจัดการเพื่อให้การทำงานนี้โดยใช้ Request.CreateResponse () วิธีการที่ได้รับ MediaTypeFormatter เช่นนี้: var jsonMediaTypeFormatter = ใหม่ [อนุญาต] [ใหม่] ไบต์ "," MimeType "," ความกว้าง "," ความสูง "}}}}}; Return Request.CreateResponse (HttpStatusCode.OK, รูปภาพ, jsonMediaTypeFormatter);
พอล

ถ้าเราต้องการให้คุณสมบัติที่ถูกบล็อกถูกละเว้นในการตอบกลับ XML
Carlos P

ยกเว้นว่าตัวแก้ไขสัญญาข้อมูลได้รับมอบหมายหนึ่งครั้งต่อการร้องขอนี่จะไม่ปลอดภัยสำหรับเธรด ฉันคิดว่านี่ถูกกำหนดหนึ่งครั้งในคลาสเริ่มต้น
Sprague

2
ยิ่งแย่กว่านั้น ive ทดสอบสิ่งนี้และการเรียก 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 จะไม่ถูกทำให้เป็นอนุกรม


มันไม่ได้ทำโดยเว็บ API เว็บ API ใช้ Json.NET โดยค่าเริ่มต้นสำหรับการเป็นอันดับ กระบวนการนี้ทำโดย Json.NET ไม่ใช่ web api
Hamid Pourjam

1
วิธีที่สองนั้นดีเพราะช่วยให้เทคโนโลยี Domain Object ไม่เชื่อเรื่องพระเจ้าโดยไม่จำเป็นต้องเขียน DTO ใหม่เพื่อซ่อนบางฟิลด์
Raffaeu

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]

ด้านบนของคุณสมบัติเช่น:

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-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 ที่เกิดจาก serializer ในตัวที่บางครั้งทำให้เกิดวนรอบไม่สิ้นสุดในขณะที่แยกวัตถุ

หากมีคนรู้เหตุผลว่าสิ่งนี้เป็นความคิดที่ไม่ดีโปรดแจ้งให้เราทราบ

มันอาจจะคุ้มค่าที่จะสังเกตว่ามันเป็นรหัสต่อไปนี้สำหรับ EntityFramework Class-property ที่ทำให้เกิดปัญหา (ถ้าสองคลาสอ้างอิงถึงซึ่งกันและกัน):

[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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.