复杂抽象对象

时间:2016-05-17 16:43:26

标签: c# asp.net .net asp.net-web-api model-binding

这是一个艰难的。我有一个从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"
    }
  ]
}

2 个答案:

答案 0 :(得分:13)

经过一番研究后,我发现元数据提供者并不存在于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)
    {
    }

答案 1 :(得分:1)

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)
{ 
}