我有一个IIdentifiable
界面:
public interface IIdentifiable
{
int Id { get; set; }
}
一个简单的课程Foo
:
public class Foo : IIdentifiable
{
public int Id { get; set; }
public string Name { get; set; }
}
当我有一个页面需要添加一组Foo
并指定一个作为默认值时,我会有一个这样的视图模型:
public class BarViewModel
{
public IList<Foo> Foos { get; set; }
public Foo DefaultFoo { get; set; }
}
所以实际上我只想 传递Ids而不是隐藏输入中的实际完整对象(这只是讨厌且不需要)。所有关注的事情(至少在数据库中)都在Bar和它的Foo.Ids和默认的Foo.Id之间。
我希望能够轻松添加一个能够接受所有IIdentifiable
的模型绑定器,然后设置Id,如果只为值提供者设置了int
。我遇到的问题是我不能做类似下面的事情并让它设置Id(因为模型绑定器不看衍生类型链......呃):
ModelBinders.Binders[typeof(IIdentifiable)] = new IdentifiableModelBinder();
所以我决定扩展DefaultModelProvider
以允许此功能,如果类型是IIdentifiable
并且值提供程序中找到的值只是一个字符串/ int,那么创建模型并设置Id
属性为匹配值:
public class DefaultWithIdentifiableModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelType = bindingContext.ModelType;
bool isList = false;
// Determine the real type of the array or another generic type.
if (modelType.IsArray)
{
modelType = modelType.GetElementType();
isList = true;
}
else if (modelType.IsGenericType)
{
var genericType = modelType.GetGenericTypeDefinition();
if (genericType == typeof(IEnumerable<>) || genericType == typeof(IList<>) || genericType == typeof(ICollection<>))
{
modelType = modelType.GetGenericArguments()[0];
isList = true;
}
}
// The real model type isn't identifiable so use the default binder.
if (!typeof(IIdentifiable).IsAssignableFrom(modelType))
{
return base.BindModel(controllerContext, bindingContext);
}
// Get the value provider for the model name.
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
// Get the string values from the value provider.
var stringValues = valueProviderResult != null ? (IEnumerable<string>)valueProviderResult.RawValue : Enumerable.Empty<string>();
int tempFirstId = -1;
// If the first element is an integer, we assume that only the Ids are supplied
// and therefore we parse the list.
// Otherwise, use the default binder.
if (stringValues.Any() && int.TryParse(stringValues.First(), out tempFirstId))
{
var listType = typeof(List<>).MakeGenericType(new Type[] { modelType });
var items = (IList)base.CreateModel(controllerContext, bindingContext, listType);
// Create each identifiable object and set the Id.
foreach (var id in stringValues)
{
var item = (IIdentifiable)Activator.CreateInstance(modelType);
item.Id = int.Parse(id);
items.Add(item);
}
if (items.Count == 0)
{
return null;
}
// Determine the correct result to return.
if (bindingContext.ModelType.IsArray)
{
var array = Array.CreateInstance(modelType, items.Count);
items.CopyTo(array, 0);
return array;
}
else if (isList)
{
return items;
}
else
{
return items[0];
}
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
我只是不确定这对我正在尝试做的事情是否真的有必要。如果有人可以对这种类型的模型绑定留下反馈/建议/改进,那将非常感激。
编辑:这是一个简单的例子:
订单包含许多项目,因此,与ORM和数据库一起使用时,只需要确实,而不是加载项目的整个对象图表。因此,项目类加载了Id,然后该项目可以添加到订单的项目列表中:
public class Order
{
List<Item> Items { get; set; }
}
var order = new Order();
order.Items.Add(new Item() { Id=2 });
order.Items.Add(new Item() { Id=5 });
这是因为完成订单的回发不会发送整个 Item
,它只是发送ID。
这是我的核心要求。当发生回发时,我需要从回发中构建Id中的Items。无论这是一个视图模型还是实际的域模型,我仍然需要一种使用Id集轻松地从int转换到域模型的方法。有意义吗?
答案 0 :(得分:2)
我遇到了与以下解决方案类似的问题。
首先,创建您的界面。在下面的示例中,其名称为 IInterface 。
然后,如下所示创建您的 CustomBinderProvider ;
public class IInterfaceBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType.GetInterface(nameof(IInterface)) != null)
{
return new BinderTypeModelBinder(typeof(IInterface));
}
return null;
}
}
现在,您可以按以下方式实现您的活页夹;
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Do something
var model = (IInterface)Activator.CreateInstance(bindingContext.ModelType);
// Do something
bindingContext.Model = model;
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.FromResult(model);
}
最后,您必须按如下所示将提供程序插入 Startup 类;
services.AddMvcCore(x =>
{
x.ModelBinderProviders.Insert(0, new IInterfaceBinderProvider());
})
答案 1 :(得分:1)
这似乎是一种体面的方式,但不是很容易扩展。如果您不太可能在不同的界面上出现同样的情况,那么这是一个很好的解决方案。
另一种方法是在启动时使用反射来查找实现IIdentifiable
的所有类型,并为所有类型分配自定义模型绑定器。
答案 2 :(得分:1)
好吧,假设你需要Foo
及其数据。因此,您为其保存Name
(可能与其他属性一起),并将持久性中的所有数据作为Foo
实例检索,这正是您的域实体。它包含与Foo
。
另一方面,你有一个View,你唯一想要操作的是Id
,所以你创建了包含Id
作为属性的ViewModel(可能还有更多Ids,如ParentFooId
例如)这是你的ViewModel 。它仅包含特定于View的数据 - 它类似于View和Controller之间的接口。
这样一切都是用DefaultModelBinder
完成的。例如,如果您有:
id
(类型int
)参数或通过表单发布; BarViewModel
实例作为控制器操作的参数; Id
BarViewModel
的属性
然后根据请求,barViewModel.Id
属性的值将是您的RouteData(或您的表单)中的值,因为DefaultModelBinder
能够做到这一点。而且您只为非常不寻常的情况创建自定义IModelBinder
。
我只是没有理由让事情过于复杂。
有道理吗?