Недавно я играл с пользовательским полиморфом c modelbinder , немного большим, чем то, что есть в документах, и я столкнулся со сценарием, в котором я не уверен, как справиться.
Я реализовал связыватель модели так же, как в документации, для иерархии, подобной этой:
public interface Step {}
public class StepWithSimpleProperty : Step
{
public string Bar { get; set; }
}
public class StepWithComplexProperty : Step
{
public List<Complex> MyList { get; set; }
}
Проблема, с которой я сталкиваюсь, заключается в том, что при связывании класса с Complex Это свойство, похоже, не связывает также список Complex
экземпляров.
Теперь, пытаясь go немного подробнее рассказать о моем конкретном случае c, моя страница Razor имеет Таблица примерно такая:
@model Project.Pages.StepWithComplexProperty
<table>
<tr>
<th>Prop1</th>
<th>Prop2</th>
</tr>
@for (var i = 0; i < Model.MyList.Count; i++)
{
<tr>
<input type="hidden" asp-for="MyList[@i].ID"/>
<td>
<input type="text" asp-for="MyList[@i].Prop1"/>
</td>
<td>
<input type="text" asp-for="MyList[@i].Prop2"/>
</td>
</tr>
}
</table>
Это фактическая страница бритвы:
@page
@model Project.Pages.WizardViewModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
var currentStep = Model.Steps[Model.CurrentStepIndex];
var viewName = currentStep.ToString().Substring(currentStep.ToString().LastIndexOf('.') + 1);
}
<h3>Step @(Model.CurrentStepIndex + 1) out of @Model.Steps.Count</h3>
@using (Html.BeginForm())
{
@Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
@Html.HiddenFor(m => m.CurrentStepIndex, Model.CurrentStepIndex)
@Html.Partial("" + viewName + "", currentStep);
if (Model.CurrentStepIndex > 0)
{
<input type="submit" value="Previous" name="prev" asp-page-handler="Previous" class="btn btn-secondary" />
}
if (Model.CurrentStepIndex < (Model.Steps.Count - 1))
{
<input type="submit" value="Next" name="next" asp-page-handler="Next" class="btn btn-primary" />
}
else
{
<input type="submit" value="Finish" name="finish" asp-page-handler="Finish" class="btn btn-primary" />
}
}
И модель представления страницы бритвы:
public class WizardViewModel : PageModel
{
[BindRequired]
[BindProperty(SupportsGet = true)]
public int CurrentStepIndex { get; set; }
public IList<Step> Steps { get; set; }
// Constructor and methods, below, but not relevant for the problem ...
}
``
In that `PageModel` it has the property of type `List<Step>`, which is each element representing a step of a wizard, a big form. Each of the classes that implement `Step` are simply _View Models_. And, like mentioned, moving back and forth through the wizard works fine... except for the step which as a list of complex objects, which the model binder seems to ignore. In case it helps, I'll post the code of the model binder below:
---
_WizardModelBinder.cs_
```csharp
public class WizardModelBinder : IModelBinder
{
private Dictionary < Type, (ModelMetadata, IModelBinder) > binders;
public WizardModelBinder(Dictionary < Type, (ModelMetadata, IModelBinder) > binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelTypeValue = bindingContext.ValueProvider.GetValue("StepType").FirstValue;
var modelType = Type.GetType(modelTypeValue, true);
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue.Contains("Step"))
{
(modelMetadata, modelBinder) = binders[modelType];
}
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,
};
}
}
}
public class WizardModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Step))
{
return null;
}
var subclasses = new []
{
typeof(StepWithSimpleProperty),
typeof(StepWithComplexProperty),
};
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 WizardModelBinder(binders);
}
}