Vin*_*ent 0 c# asp.net asp.net-mvc asp.net-core asp.net-core-mvc-2.0
我一直在尝试为 ASP.NET Core 2 中的抽象类实现模型绑定程序,但没有成功。
我特别研究了两篇看起来非常好的文章:
http://www.dotnetcurry.com/aspnet-mvc/1368/aspnet-core-mvc-custom-model-binding
我有两个目标正在努力实现
这是我基于上述文章的代码。
public class Trigger
{
    public ActionBase Action { get; set; }
}
[ModelBinder(BinderType = typeof(ActionModelBinder))]
public abstract class ActionBase
{
    public string Type => GetType().FullName;
    public ActionBase Action { get; set; }
}
public class ActionA : ActionBase
{
    public int IntProperty { get; set; }
}
public class ActionB : ActionBase
{
    public string StringProperty { get; set; }
}
public class ActionModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (context.Metadata.ModelType != typeof(ActionBase))
            return null;
        var binders = new Dictionary<string, IModelBinder>();
        foreach (var type in typeof(ActionModelBinderProvider).GetTypeInfo().Assembly.GetTypes())
        {
            var typeInfo = type.GetTypeInfo();
            if (typeInfo.IsAbstract || typeInfo.IsNested)
                continue;
            if (!(typeInfo.IsClass && typeInfo.IsPublic))
                continue;
            if (!typeof(ActionBase).IsAssignableFrom(type))
                continue;
            var metadata = context.MetadataProvider.GetMetadataForType(type);
            var binder = context.CreateBinder(metadata); // This is a BinderTypeModelBinder
            binders.Add(type.FullName, binder);
        }
        return new ActionModelBinder(context.MetadataProvider, binders);
    }
}
public class ActionModelBinder : IModelBinder
{
    private readonly IModelMetadataProvider _metadataProvider;
    private readonly Dictionary<string, IModelBinder> _binders;
    public ActionModelBinder(IModelMetadataProvider metadataProvider, Dictionary<string, IModelBinder> binders)
    {
        _metadataProvider = metadataProvider;
        _binders = binders;
    }
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var messageTypeModelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "Type");
        var messageTypeResult = bindingContext.ValueProvider.GetValue(messageTypeModelName);
        if (messageTypeResult == ValueProviderResult.None)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }
        IModelBinder binder;
        if (!_binders.TryGetValue(messageTypeResult.FirstValue, out binder))
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }
        // Now know the type exists in the assembly.
        var type = Type.GetType(messageTypeResult.FirstValue);
        var metadata = _metadataProvider.GetMetadataForType(type);
        ModelBindingResult result;
        using (bindingContext.EnterNestedScope(metadata, bindingContext.FieldName, bindingContext.ModelName, model: null))
        {
            await binder.BindModelAsync(bindingContext);
            result = bindingContext.Result;
        }
        bindingContext.Result = result;
    }
}
编辑器模板放置在正确的位置:
ActionA.cshtml
@model WebApplication1.Models.ActionA
<div class="row">
    <h4>Action A</h4>
    <div class="col-md-4">
        <div class="form-group">
            <label asp-for="IntProperty" class="control-label"></label>
            <input asp-for="IntProperty" class="form-control" />
            <span asp-validation-for="IntProperty" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Type" class="control-label"></label>
        </div>
        @Html.EditorFor(x => x.Action)
    </div>
</div>
ActionB.cshtml
@model WebApplication1.Models.ActionB
<div class="row">
    <h4>Action B</h4>
    <div class="col-md-4">
        <div class="form-group">
            <label asp-for="StringProperty" class="control-label"></label>
            <input asp-for="StringProperty" class="form-control" />
            <span asp-validation-for="StringProperty" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Type" class="control-label"></label>
        </div>
        @Html.EditorFor(x => x.Action)
    </div>
</div>
索引.cshtml
@model WebApplication1.Models.Trigger
<h2>Edit</h2>
<h4>Trigger</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Index">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            @Html.EditorFor(x=>x.Action)
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>
HomeController.cshtml
public class HomeController : Controller
{
    public IActionResult Index()
    {
        var trigger = new Trigger()
        {
            Action = new ActionA()
            {
                IntProperty = 1,
                Action = new ActionB()
                {
                    StringProperty = "foo"
                }
            }
        };
        return View(trigger);
    }
    [HttpPost]
    public IActionResult Index(Trigger model)
    {
        return View(model);
    }
}
关于目标没有。1 仅渲染第一个动作,即使它有子动作。
当尝试回发(目标 2)时,我遇到了一个异常:
InvalidOperationException:尝试激活“WebApplication1.ActionModelBinder”时无法解析类型“System.Collections.Generic.Dictionary`2[System.String,Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder]”的服务。
非常感谢对此的任何帮助!
我错误地将 ModelBinder 属性添加到我想要执行自定义绑定的类中。
[ModelBinder(BinderType = typeof(ActionModelBinder))]
public abstract class ActionBase
{
    public string Type => GetType().FullName;
    public ActionBase Action { get; set; }
}
这导致提供程序代码被绕过 - 删除此属性解决了几个问题。
我将提供程序和绑定程序重构为通用的,因此无需重复代码。
public class AbstractModelBinderProvider<T> : IModelBinderProvider where T : class
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (context.Metadata.ModelType != typeof(T))
            return null;
        var binders = new Dictionary<string, IModelBinder>();
        foreach (var type in typeof(AbstractModelBinderProvider<>).GetTypeInfo().Assembly.GetTypes())
        {
            var typeInfo = type.GetTypeInfo();
            if (typeInfo.IsAbstract || typeInfo.IsNested)
                continue;
            if (!(typeInfo.IsClass && typeInfo.IsPublic))
                continue;
            if (!typeof(T).IsAssignableFrom(type))
                continue;
            var metadata = context.MetadataProvider.GetMetadataForType(type);
            var binder = context.CreateBinder(metadata);
            binders.Add(type.FullName, binder);
        }
        return new AbstractModelBinder(context.MetadataProvider, binders);
    }
}
public class AbstractModelBinder : IModelBinder
{
    private readonly IModelMetadataProvider _metadataProvider;
    private readonly Dictionary<string, IModelBinder> _binders;
    public AbstractModelBinder(IModelMetadataProvider metadataProvider, Dictionary<string, IModelBinder> binders)
    {
        _metadataProvider = metadataProvider;
        _binders = binders;
    }
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var messageTypeModelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "Type");
        var typeResult = bindingContext.ValueProvider.GetValue(messageTypeModelName);
        if (typeResult == ValueProviderResult.None)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }
        IModelBinder binder;
        if (!_binders.TryGetValue(typeResult.FirstValue, out binder))
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }
        var type = Type.GetType(typeResult.FirstValue);
        var metadata = _metadataProvider.GetMetadataForType(type);
        ModelBindingResult result;
        using (bindingContext.EnterNestedScope(metadata, bindingContext.FieldName, bindingContext.ModelName, model: null))
        {
            await binder.BindModelAsync(bindingContext);
            result = bindingContext.Result;
        }
        bindingContext.Result = result;
        return;
    }
}
并在配置中注册提供者:
services.AddMvc(opts =>
{
    opts.ModelBinderProviders.Insert(0, new AbstractModelBinderProvider<ActionViewModel>());
    opts.ModelBinderProviders.Insert(0, new AbstractModelBinderProvider<TriggerViewModel>());
});
如果有许多抽象类需要处理,还可以更改 AbstractModelBinderProvider 以接受要处理的参数化类型集合而不是通用类型,以减少提供程序的数量。
关于能够为孩子筑巢,必须注意一些限制。
简短的答案是使用部分代替,如下所示:
@model ActionViewModel
@if (Model == null)
{
    return;
}
<div class="actionRow">
    @using (Html.BeginCollectionItem("Actions"))
    {
        <input type="hidden" asp-for="Type" />
        <input type="hidden" asp-for="Id" />
        if (Model is CustomActionViewModel)
        {
            @Html.Partial("EditorTemplates/CustomAction", Model);
        }
    }
</div>
这BeginCollectionItem是一个 html 帮助器。
请参阅: https: //github.com/danludwig/BeginCollectionItem
并且: https: //github.com/saad749/BeginCollectionItemCore
| 归档时间: | 
 | 
| 查看次数: | 2964 次 | 
| 最近记录: |