我有一个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; }
}因此,在现实中,我只想传递‘d,而不是在隐藏输入中传递实际的完整对象(这是讨厌的,不需要的)。所有bar所关心的(至少在数据库中)都在一个Bar和它的Foo.Ids和默认的Foo.Id之间。
我希望能够轻松地添加一个能够接受所有IIdentifiable的模型绑定器,然后如果只为值提供程序设置一个int,则设置Id。我遇到的问题是,我无法执行如下操作并让它设置Id (因为模型绑定器不查看派生类型chain...ugh):
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);
}
}
}我只是不确定这是否真的是我想要做的事。如果有人能在这种类型的模型绑定上留下反馈/建议/改进,我们将不胜感激。
编辑:下面是一个简单的示例:
订单有许多项,因此,与其加载项目的整个对象图,实际上只需要Id才能用于ORM和数据库中。因此,item类使用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,而是发送it。
这是我的核心要求。当回发发生时,我需要从回发的Id构建项目。不管这是一个视图模型还是实际的域模型,我仍然需要一种从ints到Id设置的域模型的转换方法。讲得通?
发布于 2010-12-26 04:36:33
这似乎是一种不错的方式,但并不是很容易扩展。如果您不太可能在不同的接口中出现相同的情况,那么这是一个很好的解决方案。
另一种方法是在启动时使用反射查找实现IIdentifiable的所有类型,并为所有这些类型分配自定义模型绑定。
发布于 2019-04-26 13:44:05
我经历过类似的问题,如下面的解决方案。
首先,创建您的界面。在下面的示例中,它的名称是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);
} 最后,您必须将提供程序插入到启动类,如下所示;
services.AddMvcCore(x =>
{
x.ModelBinderProviders.Insert(0, new IInterfaceBinderProvider());
})发布于 2010-12-26 07:39:29
那么,假设您需要Foo及其数据。因此,您为它保存Name (可能与其他属性一起),并将持久性中的所有数据作为Foo实例检索,这正是,也就是您的域实体用于的内容。它包含与Foo相关的所有数据。
另一方面,您有一个视图,您只想使用Id来操作,所以您可以创建包含Id作为属性的ViewModel (例如,可能还有更多的Ids,比如ParentFooId ),这就是,您的ViewModel for。它只包含特定于您的视图的数据-它类似于您的视图和控制器之间的接口。
这样,所有的事情都是用DefaultModelBinder完成的。例如,如果您有:
在您的form;
BarViewModel字典中使用的id (类型为int)参数,或者通过BarViewModel
实例将其作为控制器的BarViewModel的Action;
Id属性的参数发布然后,根据请求,该barViewModel.Id属性的值将是来自RouteData (或表单)的值,因为DefaultModelBinder能够做到这一点。而且您只为非常不寻常的场景创建自定义IModelBinder。
我看不出有什么理由让事情变得过于复杂。
合乎道理?
https://stackoverflow.com/questions/4532598
复制相似问题