我有以下类在我的ASP。NET MVC5应用程序:
public abstract class BaseItem
{ }
public class DerivedItem1 : BaseItem
{ }
public class DerivedItem2 : BaseItem
{ }
public interface IItemViewModel<T>
where T : BaseItem
{
T Item { get; set; }
}
public class ItemViewModel<T> : IItemViewModel<T>
where T : BaseItem
{
public T Item { get; set; }
}
我的控制器是这样的:
[AutoAssignItem]
public class DerivedItem1Controller : ItemControllerBase<DerivedItem1>
{
public ActionResult Index()
{
var model = new ItemViewModel<DerivedItem1>();
// I'd like to avoid setting the Item property here
// and instead delegate that task to my filter
// itemService.GetCurrentItems returns an instance
// of type DerivedItem1.
// model.Item = itemService.GetCurrentItem();
return View(model);
}
}
[AutoAssignItem]
public class DerivedItem2Controller : ItemControllerBase<DerivedItem2>
{
public ActionResult Index()
{
var model = new ItemViewModel<DerivedItem2>();
// I'd like to avoid setting the Item property here
// and instead delegate that task to my filter
// itemService.GetCurrentItems returns an instance
// of type DerivedItem2.
// model.Item = itemService.GetCurrentItem();
return View(model);
}
}
我有一个AutoAssignItem动作过滤器,我想在我的视图模型上设置Item属性,可以是类型ItemViewModel
public class AutoAssignItem : ActionFilterAttribute, IActionFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewModel = filterContext.Controller.ViewData.Model;
// The viewModel type here could either be
// ItemViewModel<DerivedItem1> or ItemViewModel<DerivedItem2>
// So I try passing in BaseItem as the type parameter and cast
var model = viewModel as IItemViewModel<BaseItem>;
// But model is always null :(
if (model == null)
{
return;
}
// Here I can also try and get the implemented interface type
// var interfaceType = viewModel.GetType().GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IItemViewModel<>)).SingleOrDefault();
// And try converting it but this would require the viewModel
// to implement the IConvertible interface which I want to avoid
// var model = Convert.ChangeType(viewModel, interfaceType);
// If model is not null, then set the Item property
// through a service based on contextual information
// Here itemService.GetCurrentItem() would return an Item
// with the correct type such as DerivedItem1 if the action
// on the DerivedItem1Controller had run
model.Item = itemService.GetCurrentItem();
}
}
注意BaseItem将有几个派生类,而不是像上面的例子那样只有两个。我的问题是我如何转换视图模型,以便我可以访问和设置Item属性?
如果我在IItemViewModel
作为旁白,我试图复制通用控制器和视图模型结构,通常是使用Episerver CMS API时实现的。不同之处在于,在CMS中,一切都是关于页面的。所以控制器看起来像:
public class HomePageController : PageControllerBase<HomePage>
{
public ActionResult Index(HomePage currentPage)
{
var model = new PageViewModel<HomePage>();
model.CurrentPage = currentPage;
return View(model);
}
}
听起来好像您正在尝试创建一个操作过滤器,它将对多个操作应用相同的项设置规则,以便您不必在每个操作中重复相同的项设置逻辑。
我使用了您在一个新的MVC 5项目中提供的所有代码,并进行了以下更改,以便我可以看到由我的名为"Test"的操作创建的页面中发生了什么。"
public abstract class BaseItem
{
public String Type { get; set; }
public String Note { get; set; }
public abstract void Alter();
}
public class DerivedItem1 : BaseItem
{
public DerivedItem1()
{
Type = "DerivedItem1";
}
public override void Alter()
{
Note = "Altered by the code specific to DerivedItem1";
}
}
public class DerivedItem2 : BaseItem
{
public DerivedItem2()
{
Type = "DerivedItem2";
}
public override void Alter()
{
Note = "Altered by the code specific to DerivedItem2";
}
}
public class MyActionFilter : ActionFilterAttribute, IActionFilter
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewModel = filterContext.Controller.ViewData.Model;
var model = viewModel as IItemViewModel<BaseItem>;
// model is always null :(
if (model == null)
{
return;
}
model.Item = new DerivedItem2(); // Set the Item property here;
}
}
在Home控制器中,我创建了以下Test动作:
[MyActionFilter]
public ActionResult Test()
{
var vm = new ItemViewModel<BaseItem>();
vm.Item = new DerivedItem1();
return View(vm);
}
在Test动作的视图中:
@model ActionFilterTest.ViewModels.ItemViewModel<ActionFilterTest.Models.BaseItem>
<h4>BaseItem</h4>
<hr />
<label for="item">Model.Item.Type: </label>
<div name="item">@Model.Item.Type</div>
@{ Model.Item.Alter(); }
<label for="note">Model.Item.Note:</label>"
<div name="note">@Model.Item.Note</div>
当我浏览到Home/Test时,它显示Model.Item.Type
是DerivedItem2
,而不是DerivedItem1
,并且Note
是由正确的派生类中的重写方法分配的,即使Alter()
是在被声明为ItemViewModel<BaseItem>
类型的对象上调用的,这是我所期望的。这不是你想要的吗?
:我有一种感觉,您不会认为下面的建议是一个非常灵活的解决方案,因为您需要为每个派生类型创建一个if
分支。你可以通过反射来塑造某种动态投射。我真的不想去那里。在我读过的文章中似乎有一个共识,那就是动态类型转换有些毫无意义,甚至是不明智的,因为它试图绕过编译时类型检查机制,该机制旨在防止运行时错误并保持完整性。以下更改考虑了您在最新评论中提供的新细节,并在我的项目中正常工作。
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewModel = filterContext.Controller.ViewData.Model;
var model = viewModel as IItemViewModel<BaseItem>;
if (viewModel.GetType() == typeof(ItemViewModel<DerivedItem1>))
{
((ItemViewModel<DerivedItem1>)viewModel).Item = new DerivedItem1();
}
else if (viewModel.GetType() == typeof(ItemViewModel<DerivedItem2>))
{
((ItemViewModel<DerivedItem2>)viewModel).Item = new DerivedItem2();
}
else
{
throw new InvalidCastException("Unsupported cast from type: " + viewModel.GetType().FullName);
}
}
然后我从HomeController
中删除了Test
动作,并添加了以下内容:
[MyActionFilter]
public ActionResult Test1()
{
var vm = new ItemViewModel<DerivedItem1>();
return View(vm);
}
[MyActionFilter]
public ActionResult Test2()
{
var vm = new ItemViewModel<DerivedItem2>();
return View(vm);
}
使用以下视图:
@model ActionFilterTest.ViewModels.ItemViewModel<ActionFilterTest.Models.DerivedItem1>
<label for="type">Model.Item.Type:</label>
<div name="type">@Model.Item.Type</div>
@{ Model.Item.Alter(); }
<label for="note">Model.Item.Note:</label>"
<div name="note">@Model.Item.Note</div>
:
@model ActionFilterTest.ViewModels.ItemViewModel<ActionFilterTest.Models.DerivedItem2>
<label for="type">Model.Item.Type:</label>
<div name="type">@Model.Item.Type</div>
@{ Model.Item.Alter(); }
<label for="note">Model.Item.Note:</label>"
<div name="note">@Model.Item.Note</div>
作为一种变通方法,我决定通过重写基控制器的OnResultExecuting
方法而不是在操作过滤器中设置视图模型的Item属性。这样,我就可以访问视图模型的类型参数并进行相应的类型转换:)
public abstract ItemControllerBase<T> : Controller
where T : BaseItem
{
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
var viewModel = filterContext.Controller.ViewData.Model;
var model = viewModel as IItemViewModel<T>;
if (model == null)
{
return;
}
model.Item = itemService.GetCurrentItem();
}
}