WebAPI สร้างอินสแตนซ์ของออบเจ็กต์แม้ว่าจะเป็นโมฆะใน JSON

เมื่อโพสต์โมเดล JSON ไปยังวิธีการควบคุม WebAPI ฉันสังเกตเห็นว่าหากมีออบเจ็กต์ว่างในโมเดล JSON ตัวผูกโมเดลจะสร้างอินสแตนซ์รายการเหล่านี้แทนที่จะทำให้เป็นโมฆะในออบเจ็กต์ฝั่งเซิร์ฟเวอร์

สิ่งนี้ตรงกันข้ามกับวิธีที่ตัวควบคุม MVC ปกติจะผูกข้อมูล...มันจะไม่สร้างอินสแตนซ์ของวัตถุหากเป็นโมฆะใน JSON

ตัวควบคุม MVC

public class HomeController : Controller
{
    [HttpPost]
    public ActionResult Test(Model model)
    {
        return Json(model);
    }
}

ตัวควบคุม WebAPI

public class APIController : ApiController
{
    [HttpPost]
    public Model Test(Model model)
    {
        return model;
    }
}

คลาสโมเดลที่จะได้รับ POSTed

public class Model
{
    public int ID { get; set; }
    public Widget MyWidget { get; set; }
}

คลาสที่ใช้ในคลาส Model

public class Widget
{
    public int ID { get; set; }
    public string Name { get; set; }
}

นี่คือผลลัพธ์ของฉันเมื่อฉันโพสต์โมเดล JSON ไปยังคอนโทรลเลอร์แต่ละตัว:

$.post('/Home/Test', { ID: 29, MyWidget: null })
//Results in: {"ID":29,"MyWidget":null}

$.post('/api/api/Test', { ID: 29, MyWidget: null })
//Results in: {"ID":29,"MyWidget":{"ID":0,"Name":null}}

ดังที่คุณเห็นแล้วว่าเมธอด WebAPI สร้างอินสแตนซ์คุณสมบัติ MyWidget ด้วยอ็อบเจ็กต์ ในขณะที่การกระทำ MVC ปล่อยให้มันเป็นโมฆะ

ดูเหมือนจะไม่ง่ายสำหรับฉันที่ WebAPI จะทำงานในลักษณะนี้ ทำไมมันถึงทำเช่นนี้? ฉันสามารถทำให้มันทำงานเหมือนการกระทำของ MVC ในเรื่องนี้ได้หรือไม่?


person Casey Williams    schedule 05.01.2016    source แหล่งที่มา
comment
ไม่มีโอกาสที่ Model ของคุณจะสร้าง Widget ในตัวสร้างเริ่มต้นและกำหนดให้กับ MyWidget ใช่ไหม   -  person dbc    schedule 06.01.2016
comment
คุณใช้ Web API เวอร์ชันใดอยู่   -  person Nikolai Samteladze    schedule 06.01.2016
comment
@dbc ไม่มีคลาสตรงตามที่คุณเห็น ไม่มีการระบุตัวสร้าง นอกจากนี้ หากเป็นปัญหาเกี่ยวกับคอนสตรัคเตอร์ ก็จะเกิดขึ้นกับคอนโทรลเลอร์ทั้งสองตัว   -  person Casey Williams    schedule 06.01.2016
comment
@Nikolai, WebAPI เวอร์ชัน 2   -  person Casey Williams    schedule 06.01.2016


คำตอบ (2)


ฉันคิดว่ามันคล้ายกับปัญหาที่เคยพบในโครงการของเรา

คุณต้องเปลี่ยนรหัสไปรษณีย์สำหรับ jQuery เป็นรหัสต่อไปนี้:

$.ajax({
            type: 'POST',
            url: '/api/api/Test',
            data: JSON.stringify({ ID: 29, MyWidget: null }),
            contentType: "application/json",
            dataType: 'json',
            timeout: 30000
        })
        .done(function (data) {
        })
        .fail(function() {
        });

โดยค่าเริ่มต้น jQuery 'โพสต์' จะส่งพารามิเตอร์เป็นข้อมูลที่เข้ารหัสรูปแบบ URL

application/x-www-form-urlencoded
ID 29
MyWidget

ID=29&MyWidget=

ดังนั้นมันจึงถูกดีซีเรียลไลซ์อย่างถูกต้องอย่างแน่นอน MyWidget เป็นสตริงว่าง ดังนั้นจะมีค่าว่างของคลาส Widget

นอกจากนี้ ฉันขอแนะนำให้คุณเพิ่มการกำหนดค่า Formatters สำหรับคอนโทรลเลอร์ WebApi:

public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        // Formatters
        JsonMediaTypeFormatter json = config.Formatters.JsonFormatter;

        config.Formatters.Clear();
        config.Formatters.Add(json);
    }

ดังนั้นคุณจะใช้เฉพาะตัวจัดรูปแบบ JSON สำหรับการเรียก API

อัปเดต

ข้อแตกต่างหลักที่ข้อมูลที่เข้ารหัสแบบฟอร์ม URL ที่ส่งผ่านไปยังตัวควบคุม MVC จะถูกประมวลผลโดยรันไทม์และจัดการในที่สุดโดย DefaultModelBinder (หากไม่มีเครื่องผูกแบบกำหนดเอง) ดังนั้นข้อมูลที่เข้ารหัสเป็น form-url จึงเป็นเรื่องปกติสำหรับ MVC เพราะโดยปกติแล้วข้อมูลจะถูกสร้างขึ้นโดยการโพสต์แบบฟอร์ม HTML แต่ Web API ไม่ได้ขึ้นอยู่กับการเข้ารหัสเฉพาะใดๆ จากการออกแบบ ดังนั้นจึงใช้กลไกเฉพาะ (ตัวจัดรูปแบบ) เพื่อแยกวิเคราะห์ข้อมูล... เช่น json เหมือนที่กล่าวข้างต้น ดังนั้น FormUrlEncodedMediaTypeFormatter จาก System.Net.Http.Formatting และ DefaultModelBinder จาก System.Web.Mvc จัดการสตริงว่างแตกต่างกัน

สำหรับ DefaultModelBinder สตริงว่างจะถูกแปลงเป็นโมฆะ กำลังวิเคราะห์โค้ด ฉันสามารถตัดสินใจได้ว่าวิธี BindModel จะสร้างโมเดลว่างก่อน:

 if (model == null)
 {
     model = CreateModel(controllerContext, bindingContext, modelType);
 }

หลังจากนั้นจะเติมคุณสมบัติ:

// call into the property's model binder
        IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
        object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
        ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
        propertyMetadata.Model = originalPropertyValue;
        ModelBindingContext innerBindingContext = new ModelBindingContext()
        {
            ModelMetadata = propertyMetadata,
            ModelName = fullPropertyKey,
            ModelState = bindingContext.ModelState,
            ValueProvider = bindingContext.ValueProvider
        };
        object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);

และในที่สุด GetBinder จะส่งคืน fallbackBinder สำหรับประเภท Widget (ประเภทของคุณสมบัติ) และ fallbackBinder เองจะเรียก ConvertSimpleType โดยที่สตริงได้รับการประมวลผลดังต่อไปนี้:

        string valueAsString = value as string;
        if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString))
        {
            return null;
        }

ฉันเดาว่าไม่มีมาตรฐานใด ๆ ที่อธิบายการแปลงจากสตริงที่เข้ารหัส url เป็นวัตถุ C# เลยไม่รู้ว่าอันไหนถูกต้อง ไม่ว่าในกรณีใด ฉันแน่ใจว่าคุณต้องส่ง json ผ่านการเรียก AJAX ไม่ใช่ข้อมูลที่เข้ารหัสรูปแบบ URL

person Maxim    schedule 06.01.2016
comment
ใช้งานได้ (โดยเฉพาะการใช้ $.ajax) ขอบคุณสำหรับความเข้าใจของคุณ! เป็นเรื่องที่น่าสนใจสำหรับฉันที่ MVC Action และวิธี WebAPI มีพฤติกรรมแตกต่างออกไปในเรื่องนี้ - person Casey Williams; 06.01.2016
comment
ฉันจะเพิ่มคำอธิบายเกี่ยวกับความแตกต่างในคำตอบเดิม - person Maxim; 06.01.2016
comment
คำอธิบายที่ยอดเยี่ยม ขอบคุณ! ฉันหวังว่าฉันจะโหวตคำตอบของคุณอีกครั้ง - person Casey Williams; 06.01.2016

ใช้โปรแกรมซีเรียลไลเซอร์ของ newtonsoft Newtonsoft.Json ทำให้พวกมันเป็นโมฆะ

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;


public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
              var jsonformatter = new JsonMediaTypeFormatter();
              config.Formatters.Clear();
              config.Formatters.Add(jsonformatter);
         }
    }
person halit    schedule 05.01.2016
comment
Web API ไม่ใช้ Newtonsoft.Json serializer เป็นค่าเริ่มต้นหรือไม่ - person Nikolai Samteladze; 06.01.2016
comment
@NikolaiSamteladze คุณพูดถูก ฉันได้ตรวจสอบแล้ว. แต่ Json.Net ไม่ได้เริ่มต้นวัตถุว่าง - person halit; 06.01.2016
comment
วัตถุกำลังได้รับอินสแตนซ์ในช่วงเวลารวม หากฉันเบรกพอยต์ในการทำงานของคอนโทรลเลอร์ ฉันจะเห็นวัตถุใหม่ที่สร้างไว้แล้ว (หรือเป็นโมฆะในกรณีของคอนโทรลเลอร์ MVC) - person Casey Williams; 06.01.2016