复杂抽象对象的 WebAPI 自定义模型绑定
WebAPI Custom Model binding of complex abstract object
这是一个艰难的过程。我在绑定来自 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"
}
]
}
经过一些研究,我发现 WebAPI 中不存在元数据提供程序,为了绑定到复杂的抽象 object,您必须自己编写。
我首先编写了一个新的模型绑定方法,使用自定义类型名称 JSon 序列化程序,最后我更新了我的端点以使用自定义绑定器。值得注意的是,以下仅适用于 body 中的请求,您必须为 header 中的请求编写其他内容。我建议阅读 Adam Freeman 的 Expert ASP.NET Web API 2 for MVC Developers and complex object 绑定的第 16 章。
我能够使用以下代码从请求的 body 序列化我的 object。
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)
{
}
不再需要 TypeNameSerializationBinder class 以及 WebApiConfig 配置。
首先,您需要为记录类型创建枚举:
public enum ResourceRecordTypeEnum
{
a,
b
}
然后,将 ResourceRecord 中的 "Type" 字段更改为我们刚刚创建的枚举:
public abstract class ResourceRecord
{
public abstract ResourceRecordTypeEnum Type { get; }
}
现在你应该创建这两个 classes:
模型活页夹
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;
}
}
转换器class
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)
{
}
这是一个艰难的过程。我在绑定来自 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"
}
]
}
经过一些研究,我发现 WebAPI 中不存在元数据提供程序,为了绑定到复杂的抽象 object,您必须自己编写。
我首先编写了一个新的模型绑定方法,使用自定义类型名称 JSon 序列化程序,最后我更新了我的端点以使用自定义绑定器。值得注意的是,以下仅适用于 body 中的请求,您必须为 header 中的请求编写其他内容。我建议阅读 Adam Freeman 的 Expert ASP.NET Web API 2 for MVC Developers and complex object 绑定的第 16 章。
我能够使用以下代码从请求的 body 序列化我的 object。
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)
{
}
不再需要 TypeNameSerializationBinder class 以及 WebApiConfig 配置。
首先,您需要为记录类型创建枚举:
public enum ResourceRecordTypeEnum
{
a,
b
}
然后,将 ResourceRecord 中的 "Type" 字段更改为我们刚刚创建的枚举:
public abstract class ResourceRecord
{
public abstract ResourceRecordTypeEnum Type { get; }
}
现在你应该创建这两个 classes:
模型活页夹
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;
}
}
转换器class
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)
{
}