我有一个MVC3站点,我已经为测试另一个站点而设置了它——大多数站点都很快,而且很脏,所以我没有去城里为所有视图创建模型和视图模型类型——只有在需要用户输入的地方。
好的,我有一个控制器方法,它投影一个Linq序列并将其设置为ViewBag
。
ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new { Value = i });
在我看来(Razor C#(我想读这篇文章——很简单:
@foreach(dynamic item in ViewBag.SomeData)
{
@:Number: @item.i
}
当然,我得到的是RuntimeBinderException
,因为在控制器中创建的匿名类型是web项目输出程序集的内部,而这里的实际Razor代码将在构建管理器生成的另一个程序集中运行,所以,总而言之,DENIED
显然,一个"合适"的模型类型可以解决这个问题,但我只是不想这样做,因为这是我的特权(!(-如何最好地将代码保持在最低限度并保持动态?
这就是我所做的(我强调这只是真正解决了匿名类型的问题(;将构件抬出并将它们推入CCD_ 3。
我最初的更改是使投影成为一个返回ExpandoObject
:的多语句
ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> {
var toReturn = new ExpandoObject();
toReturn.Value = i;
return toReturn;
});
它几乎和匿名类型一样短,只是不那么干净。
但后来我想知道我是否可以从匿名类型中获取公开可读的成员(该类型是内部的,但它生成的属性是公共的(:
public static class SO7429957
{
public static dynamic ToSafeDynamic(this object obj)
{
//would be nice to restrict to anonymous types - but alas no.
IDictionary<string, object> toReturn = new ExpandoObject();
foreach (var prop in obj.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead)) // watch out for types with indexers
{
toReturn[prop.Name] = prop.GetValue(obj, null);
}
return toReturn;
}
}
这意味着我可以使用我的原始代码,但最后添加了一个小的扩展方法调用:
ViewBag.SomeData=Enumerable.Range(1,10).Select(i=> new { Value = i }.ToSafeDynamic());
它是不高效的,而且它绝对应该不要用于严肃的生产代码。但这是一个很好的时间节省。
显然,一个"合适"的模型类型可以解决这个问题,但让我们说我只是不想那样做,因为这是我的特权(!(
你不能有这样的特权。很抱歉,这绝对没有任何借口:-(更不用说你试图实现的是不可能的,因为动态类型是声明程序集的内部。Razor视图在运行时由ASP.NET引擎编译为单独的动态程序集。
所以回到主题:永远不要将匿名对象作为模型传递给视图。始终定义使用视图模型。像这样:
public class MyViewModel
{
public int Value { get; set; }
}
然后:
public ActionResult Index()
{
var model = Enumerable.Range(1, 10).Select(i => new MyViewModel { Value = i });
return View(model);
}
然后使用强类型视图:
@model IEnumerable<MyViewModel>
@Html.DisplayForModel()
以及在相应的显示模板中,该模板将自动为集合的每个元素呈现,这样您就不必在视图中编写任何循环(~/Views/Shared/DisplayTemplates/MyViewModel.cshtml
(:
@model MyViewModel
@:Number: @Html.DisplayFor(x => x.Value)
我们从最初的版本改进的东西:
- 我们有带Intellisense的强类型视图(如果您激活视图的编译,甚至编译时安全(
- 使用适合视图特定要求的强类型视图模型
- 消除暗示弱类型的ViewBag/ViewData
- 使用显示模板可以避免在视图中编写难看的循环=>您依赖约定,框架完成其余工作
我认为ToSafeDynamic
是一个很好的解决方案。但我想分享一些使用开源ImpromptuInterface
(在nuget中(的其他选项,这些选项在这种情况下会很好地工作。
一个选项是基于DynamicObject的代理ImpromptuGet,它只会将调用转发到匿名类型,类似于使用dynamic(它使用相同的api,只是它将上下文设置为匿名类型的自身,因此internal
访问无关紧要(。
ViewBag.SomeData = Enumerable.Range(1,10)
.Select(i => ImpromptuGet.Create(new { Value = i }));
这个选项看起来不像ToSafeDynamic那样干净,但它有一个小区别,那就是它只在使用属性时调用属性,而不是预先复制所有数据。
然而,ImpromptuInterface
的一个更好的解决方案是它用于创建原型动态对象的快速语法。
ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Build.NewObject(Value:i));
这将创建一个类似expando的对象,但您也可以选择创建字面ExpandoObject
s(在我的测试中,它在getter上提供的性能与转换为动态的POCO对象相同(。
ViewBag.SomeData = Enumerable.Range(1,10)
.Select(i => Build<ExpandoObject>.NewObject(Value:i));
此外,创建设置部分可以存储在一个或多个临时变量或字段中,以进一步缩短lambda。
var Expando =Build<ExpandoObject>.NewObject;
ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Expando(Value:i));
如果你只是追求纯粹的快速死亡,那么在这种情况下你总是可以使用元组。
ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new Tuple<int>(i);
-
@foreach(dynamic item in ViewBag.SomeData)
{
@:Number: @item.Item1
}
您知道生成管理器生成的程序集的名称吗?如果是这样,您应该能够在控制器部件中应用InternalsVisibleTo
,并且一切都会正常工作。
编辑:一个可能的解决方案是:在模型程序集中创建一个扩展DynamicObject
的公共类型,该类型通过反射将对属性的任何请求代理到您的匿名类型。这会很难看,但我认为它应该有效。。。有效地公开了匿名类型。
ASP.NET MVC动态视图部分
这个怎么样?