复杂抽象对象的WebAPI自定义模型绑定

Posted

技术标签:

【中文标题】复杂抽象对象的WebAPI自定义模型绑定【英文标题】:WebAPI Custom Model binding of complex abstract object 【发布时间】:2016-09-13 21:37:59 【问题描述】:

这是一个艰难的过程。我在从 JSON 绑定模型时遇到问题。我正在尝试以多态方式解析提供的记录,并提供它将解析到的记录类型(我希望将来能够添加许多记录类型)。我尝试在调用端点时使用following example 来解析我的模型,但是此示例仅适用于 MVC,不适用于 Web API 应用程序。

我尝试使用 IModelBinder 和 BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) 来编写它。但是我在 System.Web.Http 命名空间中找不到 ModelMetadataProviders 的等价物。

感谢任何人可以提供的任何帮助。

我有一个具有以下对象结构的 Web API 2 应用程序。

public abstract class ResourceRecord

    public abstract string Type  get; 


public class ARecord : ResourceRecord

    public override string Type
    
        get  return "A"; 
    

    public string AVal  get; set; 



public class BRecord : ResourceRecord

    public override string Type
    
        get  return "B"; 
    

    public string BVal  get; set; 


public class RecordCollection

    public string Id  get; set; 

    public string Name  get; set; 

    public List<ResourceRecord> Records  get; 

    public RecordCollection()
    
        Records = new List<ResourceRecord>();
    

JSON 结构


  "Id": "1",
  "Name": "myName",
  "Records": [
    
      "Type": "A",
      "AValue": "AVal"
    ,
    
      "Type": "B",
      "BValue": "BVal"
    
  ]

【问题讨论】:

Web Api Model Binding and Polymorphic Inheritence的可能重复 这是我在问题中使用的示例。为这个问题提供的答案是针对 MVC 模型绑定的,我需要 Web API 模型绑定。 【参考方案1】:

经过一些研究,我发现 WebAPI 中不存在元数据提供程序,为了绑定到复杂的抽象对象,您必须自己编写。

我首先编写了一个新的模型绑定方法,使用了自定义类型名称 JSon 序列化程序,最后我更新了我的端点以使用自定义绑定器。值得注意的是,以下内容仅适用于正文中的请求,您必须在标头中为请求编写其他内容。我建议阅读 Adam Freeman 的 Expert ASP.NET Web API 2 for MVC Developers 和复杂对象绑定的第 16 章。

我能够使用以下代码从请求正文中序列化我的对象。

WebAPI 配置

public static class WebApiConfig

    public static void Register(HttpConfiguration config)
    
        config.Services.Insert(typeof(ModelBinderProvider), 0,
            new SimpleModelBinderProvider(typeof(RecordCollection), new JsonBodyModelBinder<RecordCollection>()));
    

自定义模型绑定器

public class JsonBodyModelBinder<T> : IModelBinder

    public bool BindModel(HttpActionContext actionContext,
        ModelBindingContext bindingContext)
    
        if (bindingContext.ModelType != typeof(T))
        
            return false;
        

        try
        
            var json = ExtractRequestJson(actionContext);

            bindingContext.Model = DeserializeObjectFromJson(json);

            return true;
        
        catch (JsonException exception)
        
            bindingContext.ModelState.AddModelError("JsonDeserializationException", exception);

            return false;
        


        return false;
    

    private static T DeserializeObjectFromJson(string json)
    
        var binder = new TypeNameSerializationBinder("");

        var obj = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings
        
            TypeNameHandling = TypeNameHandling.Auto,
            Binder = binder
        );
        return obj;
    

    private static string ExtractRequestJson(HttpActionContext actionContext)
    
        var content = actionContext.Request.Content;
        string json = content.ReadAsStringAsync().Result;
        return json;
    

自定义序列化绑定

public class TypeNameSerializationBinder : SerializationBinder

    public string TypeFormat  get; private set; 

    public TypeNameSerializationBinder(string typeFormat)
    
        TypeFormat = typeFormat;
    

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    
        assemblyName = null;
        typeName = serializedType.Name;
    

    public override Type BindToType(string assemblyName, string typeName)
    
        string resolvedTypeName = string.Format(TypeFormat, typeName);

        return Type.GetType(resolvedTypeName, true);
    

端点定义

    [HttpPost]
    public void Post([ModelBinder(BinderType = typeof(JsonBodyModelBinder<RecordCollection>))]RecordCollection recordCollection)
    
    

【讨论】:

BindModel 实现代码中的最后一个return false; 语句不是不可访问代码吗?【参考方案2】:

不再需要 TypeNameSerializationBinder 类以及 WebApiConfig 配置。

首先,您需要为记录类型创建枚举:

public enum ResourceRecordTypeEnum

    a,
    b

然后,将 ResourceRecord 中的“类型”字段更改为我们刚刚创建的枚举:

public abstract class ResourceRecord

    public abstract ResourceRecordTypeEnum Type  get; 

现在你应该创建这两个类:

模型绑定器

public class ResourceRecordModelBinder<T> : IModelBinder

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    
        if (bindingContext.ModelType != typeof(T))
            return false;

        try
        
            var json = ExtractRequestJson(actionContext);
            bindingContext.Model = DeserializeObjectFromJson(json);
            return true;
        
        catch (JsonException exception)
        
            bindingContext.ModelState.AddModelError("JsonDeserializationException", exception);
            return false;
        
    

    private static T DeserializeObjectFromJson(string json)
    
        // This is the main part of the conversion
        var obj = JsonConvert.DeserializeObject<T>(json, new ResourceRecordConverter());
        return obj;
    

    private string ExtractRequestJson(HttpActionContext actionContext)
    
        var content = actionContext.Request.Content;
        string json = content.ReadAsStringAsync().Result;
        return json;
    

转换器类

public class ResourceRecordConverter : CustomCreationConverter<ResourceRecord>

    private ResourceRecordTypeEnum _currentObjectType;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        var jobj = JObject.ReadFrom(reader);
        // jobj is the serialized json of the reuquest
        // It pulls from each record the "type" field as it is in requested json,
        // in order to identify which object to create in "Create" method
        _currentObjectType = jobj["type"].ToObject<ResourceRecordTypeEnum>();
        return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
    

    public override ResourceRecord Create(Type objectType)
    
        switch (_currentObjectType)
        
            case ResourceRecordTypeEnum.a:
                return new ARecord();
            case ResourceRecordTypeEnum.b:
                return new BRecord();
            default:
                throw new NotImplementedException();
        
    

控制器

[HttpPost]
public void Post([ModelBinder(BinderType = typeof(ResourceRecordModelBinder<RecordCollection>))] RecordCollection recordCollection)
 

【讨论】:

以上是关于复杂抽象对象的WebAPI自定义模型绑定的主要内容,如果未能解决你的问题,请参考以下文章

将对象绑定到 Web API 端点时指定自定义属性名称

如何自定义 ASP.Net Core 模型绑定错误?

在 spring 中将 Path 变量绑定到自定义模型对象

自定义验证属性未在模型上验证 - WebAPI C# 和 JSON

模型绑定不适用于 asp.net 核心 Web api 控制器操作方法中的 Stream 类型参数。(即使使用自定义流输入格式化程序)

来自json的web api 2绑定模型