我正在寻找在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调用?
解决方法
以下是我在我的类型中继承的一个示例,并且在某些设置(如使用KnownType属性装饰,Xml格式化程序的datacontractserializer需要)和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:\n{0}",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; } } }