继承类型的 WebApi 模型绑定

Posted

技术标签:

【中文标题】继承类型的 WebApi 模型绑定【英文标题】:WebApi Model Binding For Inherited Types 【发布时间】:2013-03-09 01:37:45 【问题描述】:

我希望在 WebApi 中处理继承类型的模型绑定,而我真正想做的是使用默认模型绑定来处理绑定(除了选择无法这样做的类型) ,但我缺少一些基本的东西。

所以说我有类型:

public abstract class ModuleVM

    public abstract ModuleType ModuleType  get; 


public class ConcreteVM : ModuleVM



使用 MVC 控制器,我会做这样的事情:

public class ModuleMvcBinder : DefaultModelBinder

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    
        if (modelType == typeof(ModuleVM))
        
            // Just hardcoding the type for simplicity
            Type instantiationType = typeof(ConcreteVM);
            var obj = Activator.CreateInstance(instantiationType);
            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
            bindingContext.ModelMetadata.Model = obj;
            return obj;
        
        return base.CreateModel(controllerContext, bindingContext, modelType);
    



[AttributeUsage( AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class ModuleMvcBinderAttribute : CustomModelBinderAttribute

    public override IModelBinder GetBinder()
    
        return new ModuleMvcBinder();
    

然后使用控制器上的属性,一切都很好,我正在利用 DefaultModelBinder 进行实际工作,我基本上只是提供正确的对象实例化。

那么我该如何为 WebApi 版本做同样的事情呢?

如果我使用自定义模型绑定器(例如Error implementing a Custom Model Binder in Asp.Net Web API),我的问题是(我相信)在 BindModel 方法中,一旦实例化对象,我还没有找到使用“标准”http 绑定的好方法.正如其他帖子中所建议的那样,我可以专门针对 JSON (Deserialising Json to derived types in Asp.Net Web API) 或 XML (Getting my Custom Model bound to my POST controller) 执行此操作,但在我看来,这违背了这一点,因为 web api 应该将其分开,而且是 - 它只是没有知道如何确定类型。 (所有具体类型自然都处理得很好。)

我是否忽略了一些明显的事情,我应该在实例化对象后将 BindModel 调用定向到?

【问题讨论】:

你找到解决办法了吗? 【参考方案1】:

以下是一个示例,其中我的类型具有继承性,并且在进行了一些设置(例如使用 Xml 格式化程序的 datacontractserializer 所需的 KnownType 属性进行装饰)和 Json 格式化程序上的 TypeNameHandling 设置之后,我们可以预期两个 xml/json 请求的行为一致。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;

namespace Service

    class Service
    
        private static HttpSelfHostServer server = null;
        private static string baseAddress = string.Format("http://0:9095/", Environment.MachineName);

        static void Main(string[] args)
        
            HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
            config.Routes.MapHttpRoute("Default", "api/controller/id", new  id = RouteParameter.Optional );
            config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
            config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;

            try
            
                server = new HttpSelfHostServer(config);
                server.OpenAsync().Wait();

                Console.WriteLine("Service listenting at: 0 ...", baseAddress);

                TestWithHttpClient("application/xml");

                TestWithHttpClient("application/json");

                Console.ReadLine();
            
            catch (Exception ex)
            
                Console.WriteLine("Exception Details:\n0", ex.ToString());
            
            finally
            
                if (server != null)
                
                    server.CloseAsync().Wait();
                
            
        

        private static void TestWithHttpClient(string mediaType)
        
            HttpClient client = new HttpClient();

            MediaTypeFormatter formatter = null;

            // NOTE: following any settings on the following formatters should match
            // to the settings that the service's formatters have.
            if (mediaType == "application/xml")
            
                formatter = new XmlMediaTypeFormatter();
            
            else if (mediaType == "application/json")
            
                JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
                jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;

                formatter = jsonFormatter;
            

            HttpRequestMessage request = new HttpRequestMessage();
            request.RequestUri = new Uri(baseAddress + "api/students");
            request.Method = HttpMethod.Get;
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
            HttpResponseMessage response = client.SendAsync(request).Result;
            Student std = response.Content.ReadAsAsync<Student>().Result;

            Console.WriteLine("GET data in '0' format", mediaType);
            if (StudentsController.CONSTANT_STUDENT.Equals(std))
            
                Console.WriteLine("both are equal");
            

            client = new HttpClient();
            request = new HttpRequestMessage();
            request.RequestUri = new Uri(baseAddress + "api/students");
            request.Method = HttpMethod.Post;
            request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
            Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;

            Console.WriteLine("POST and receive data in '0' format", mediaType);
            if (StudentsController.CONSTANT_STUDENT.Equals(std1))
            
                Console.WriteLine("both are equal");
            
        
    

    public class StudentsController : ApiController
    
        public static readonly Student CONSTANT_STUDENT = new Student()  Id = 1, Name = "John", EnrolledCourses = new List<string>()  "maths", "physics"  ;

        public Person Get()
        
            return CONSTANT_STUDENT;
        

        // NOTE: specifying FromBody here is not required. By default complextypes are bound
        // by formatters which read the body
        public Person Post([FromBody] Person person)
        
            if (!ModelState.IsValid)
            
                throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
            

            return person;
        
    

    [DataContract]
    [KnownType(typeof(Student))]
    public abstract class Person : IEquatable<Person>
    
        [DataMember]
        public int Id  get; set; 

        [DataMember]
        public string Name  get; set; 

        public bool Equals(Person other)
        
            if (other == null)
                return false;

            if (ReferenceEquals(this, other))
                return true;

            if (this.Id != other.Id)
                return false;

            if (this.Name != other.Name)
                return false;

            return true;
        
    

    [DataContract]
    public class Student : Person, IEquatable<Student>
    
        [DataMember]
        public List<string> EnrolledCourses  get; set; 

        public bool Equals(Student other)
        
            if (!base.Equals(other))
            
                return false;
            

            if (this.EnrolledCourses == null && other.EnrolledCourses == null)
            
                return true;
            

            if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
                (this.EnrolledCourses != null && other.EnrolledCourses == null))
                return false;

            if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
                return false;

            for (int i = 0; i < this.EnrolledCourses.Count; i++)
            
                if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
                    return false;
            

            return true;
        
    

【讨论】:

欣赏它 Kiran,这绝对是有用的代码。最终,虽然您不是使用 xml 和 json 序列化而不是模型绑定器吗? (Json 通过类型信息,很遗憾我无法添加,xml 通过 DataContract 属性。)我真的很想使用模型绑定器,因为我最终将无法装饰所有类型并且不能需要 JSON 上的类型信息。

以上是关于继承类型的 WebApi 模型绑定的主要内容,如果未能解决你的问题,请参考以下文章

WebAPI:HttpClient 响应字符串模型绑定

Web Api 模型绑定和多态继承

来自 JSON 的 WebAPI 模型绑定

模型绑定不适用于 ASP.NET Core 2 WebAPI 中的 POST 请求

MVC4 webapi中的反序列化/模型绑定不适用于数组

如何传递 WebAPI 控制器数据,以便发布模型在绑定/反序列化或验证时可以访问