【问题标题】:How do I use a interface in a partial view如何在局部视图中使用界面
【发布时间】:2021-03-12 12:14:03
【问题描述】:

你好 StackOverflow 的人们,
我正在尝试以下方法:

我有一个选择国家的部分。但我想将此部分用于 2 个不同的模型:
_CountrySelectorPartial.cshtml

@model CountrySelectorViewModel

<div class="mb-3">
    <label asp-for="Model.Country" class="control-label"></label>
    <select asp-for="Model.CountryId" asp-items="@(new SelectList(Model.Countries, "Id", "Name", Model.Model?.CountryId))"></select>
    <span asp-validation-for="Model.CountryId" class="text-danger"></span>
</div>

CountrySelectorPartial.cs

public class CountrySelectorViewModel
    {
        public ICountry Model { get; set; }
        
        [Required]
        public ICollection<Country> Countries { get; set; } = new List<Country>();
    }

这些是实现ICountry的类:
Branch.cs

public class Branch : IEntity, ICountry
    {
        public int Id { get; set; }
        
        [Required]
        public int CountryId { get; set; }
        public Country Country { get; set; }
        // More properties
    }

Employee.cs

public class Employee : IEntity, ICountry
    {
        public int Id { get; set; }

        [Required]
        public int CountryId { get; set; }
        public Country Country { get; set; }
        // more properties
    }

我想为这些类使用部分。
我像这样使用部分:

<partial name="Components/_CountrySelector" model="@(new CountrySelectorViewModel { Model = Model.Employee, Countries = Model.Countries })"/>

或者对于这样的分支:

<partial name="Components/_CountrySelector" model="@(new CountrySelectorViewModel { Model = Model.Branch, Countries = Model.Countries })"/>

但是当我提交表单时,传入的类是null 但是当我在CountrySelectorPartial 中使用Employee 而不是ICountry 时,它的数据是正确的。

是否可以使用该界面将此部分用于 2 个模型?

编辑:
为清楚起见,它确实在页面上正确显示,但在提交完整表单时它返回 null

这是发送数据的路径:

【问题讨论】:

标签: c# .net asp.net-core asp.net-core-mvc partial-views


【解决方案1】:

据我所知,如果 CountrySelectorViewModel 的 Model 属性是 ICountry,您仍然需要在局部视图中检查模型的属性才能将 icountry 转换为 Employee 或 Branch。

此外,由于渲染局部视图的编辑视图使用的是 CreateEmployeeViewModelData 而不是 CountrySelectorViewModel,因此您应该使用名称设置输入,而不是直接使用 asp.net core taghelper。生成的标签名称不对。您应该将其设置为 {Employee.Propertyname}。

最后,由于你设置了 ICountry 作为属性,如果你想绑定到子类,你应该创建一个自定义的模型绑定器来检查类型,你应该根据它的属性分配正确的绑定器。

通常情况下,我们会在 ICountry 中添加名为 kind 的新属性,并在您要调用局部视图时进行设置。

关于如何实现您的要求的更多细节,您可以参考以下代码:

由于我不知道您的所有模型属性,因此我自己创建了它们。你应该根据你的实际模型修改它。

型号:

国家:

public interface ICountry
{
    public string Kind { get; set; }
}

分支: 公共类分支:ICountry { 公共 int BranchId { 获取;放; }

    //[Required]
    //public int CountryId { get; set; }
    //public Country Country { get; set; }
    private string _kind;
    public string Kind { get => _kind; set => _kind = "Branch"; }

    // More properties
}

员工:

public class Employee : ICountry
{
    public int EmployeeId { get; set; }

    private string _kind;
    public string Kind { get => _kind;  set => _kind = "Employee"; }
}

CountrySelectorViewModel:

public class CountrySelectorViewModel
{
    public int Id { get; set; }
    public ICountry Model { get; set; }



    //[Required]
    //public ICollection<Country> Countries { get; set; } = new List<Country>();
}

CreateEmployeeViewModelData:

public class CreateEmployeeViewModelData
{
    public ICountry Employee { get; set; }
}

ICountryModelBinderProvider:

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(ICountry))
        {
            return null;
        }

        var subclasses = new[] { typeof(Employee), typeof(Branch), };

        var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
        foreach (var type in subclasses)
        {
            var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
            binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
        }

        return new ICountryModelBinder(binders);
    }

    public class ICountryModelBinder : IModelBinder
    {
        private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;

        public ICountryModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
        {
            this.binders = binders;
        }

        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(ICountry.Kind));
            var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
            //var modelTypeValue = bindingContext.ValueProvider.GetValue("Kind").FirstValue;
          IModelBinder modelBinder;
            ModelMetadata modelMetadata;
            if (modelTypeValue == "Employee")
            {
                (modelMetadata, modelBinder) = binders[typeof(Employee)];
            }
            else if (modelTypeValue == "Branch")
            {
                (modelMetadata, modelBinder) = binders[typeof(Branch)];
            }
            else
            {
                bindingContext.Result = ModelBindingResult.Failed();
                return;
            }

            var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
                bindingContext.ActionContext,
                bindingContext.ValueProvider,
                modelMetadata,
                bindingInfo: null,
                bindingContext.ModelName);

            await modelBinder.BindModelAsync(newBindingContext);
            bindingContext.Result = newBindingContext.Result;

            if (newBindingContext.Result.IsModelSet)
            {
                // Setting the ValidationState ensures properties on derived types are correctly 
                bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
                {
                    Metadata = modelMetadata,
                };
            }
        }
    }

Startup.cs ConfigureServices 方法:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new ICountryModelBinderProvider()); });
    }

局部视图:

@model CountrySelectorViewModel

<div class="mb-3">
    @*<label asp-for="Model.Country" class="control-label"></label>
        <select asp-for="Model.CountryId" asp-items="@(new SelectList(Model.Countries, "Id", "Name", Model.Model?.CountryId))"></select>
        <span asp-validation-for="Model.CountryId" class="text-danger"></span>*@
    @if (Model.Model.GetType() == typeof(Employee))
    {
        <input name="Employee.EmployeeId" value="@((Model.Model as Employee).EmployeeId)" />
        <input name="Employee.Kind" value="@Model.Model.Kind" />
    }

    @if (Model.Model.GetType() == typeof(Branch))
    {
        <input name="Employee.EmployeeId" value="@((Model.Model as Branch).BranchId)" />
        <input name="Employee.Kind" value="@Model.Model.Kind" />
    }


</div>

编辑视图:

<form asp-action="Edit">
    <partial name="_CountrySelectorPartial" model="@(new CountrySelectorViewModel { Model = new Employee{EmployeeId = 1, Kind="Employee"}, Id = 1 })" />
    <input type="submit" value="Click" />
</form>

<hr />

<form asp-action="Edit">
    <partial name="_CountrySelectorPartial" model="@(new CountrySelectorViewModel { Model = new Branch{BranchId = 2,Kind="Branch"}, Id = 2 })" />

    <input type="submit" value="Click" />
</form>

结果:

【讨论】:

    猜你喜欢
    • 2013-03-29
    • 1970-01-01
    • 2012-01-29
    • 1970-01-01
    • 2020-10-18
    • 1970-01-01
    • 2020-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多