我可以将匿名类型传递给ASP.NET MVC视图吗?

47

我刚开始接触ASP.NET MVC,现在它处于Beta版本。在我的代码中,我运行了一个简单的LINQ to SQL查询来获取结果列表,并将其传递给我的视图。类似这样:

var ords = from o in db.Orders
           where o.OrderDate == DateTime.Today
           select o;

return View(ords);

然而,在我的看法中,我意识到我需要为每个订单访问客户的姓名。我开始使用o.Customer.Name,但我相当确定这将为每个订单执行单独的查询(因为LINQ采用惰性加载)。

减少查询数量的逻辑方法是同时选择客户名称。类似于:

var ords = from o in db.Orders
           from c in db.Customers
           where o.OrderDate == DateTime.Today
               and o.CustomerID == c.CustomerID
           select new { o.OrderID, /* ... */, c.CustomerName };

return View(ords);

现在我的变量“ords”是一个匿名类型的IEnumerable。

是否有可能以这样的方式声明ASP.NET MVC视图,使其接受一个IEnumerable作为其视图数据,其中T由从控制器传递的参数定义,或者我必须定义一个具体类型来从查询中填充?

11个回答

26

你能将它传递到视图吗?可以,但是你的视图将不会是强类型的。但是辅助工具还是有效的。例如:

public ActionResult Foo() {
  return View(new {Something="Hey, it worked!"});
}

//Using a normal ViewPage

<%= Html.TextBox("Something") %>

那个文本框应该将“嘿,它起作用了!”渲染为值。
那么,你能定义一个视图,在其中T由从控制器传递给它的内容定义吗?当然可以,但显然不能在编译时完成。
好好想一想。当你为视图声明模型类型时,这是为了让你在视图中获得智能提示。这意味着类型必须在编译时确定。但问题是,我们是否可以从运行时给定的内容中确定类型。当然可以,但无法保留强类型。
如果你甚至不知道类型,那么如何获得智能提示?在运行时,控制器可能会向视图传递任何类型。我们甚至无法分析代码并猜测,因为操作过滤器可能会更改传递给视图的对象。
我希望这样澄清了答案,而不会更加混淆。 :)

如果有人知道的话,那一定是你,Phil。感谢你对像我这样的菜鸟如此亲切可靠! - Matt Hamilton
这个回答解决了问题吗?我有同样的问题,这个回答没有帮助。你能否展示一下如何循环遍历或ds并在视图中显示? - KP.
11
它并没有回答问题!令人惊讶的是,它对于文本框有效,但如果我只想呈现这个值,我该怎么做? <%= Model.Something %> 不起作用,你会得到一个“object”不包含“Something”的定义异常。 - PawelRoman
@Robert 可能是因为它回答了这个问题。问题是,你能把它传递给视图吗?可以。你能以这样的方式做到视图的通用参数类型由控制器传递给它来确定吗?不行。我会更新我的答案并提供更多细节。 :) - Haacked
1
在ASP.NET MVC Futures中还有另一种解决方案,称为DynamicViewPage。http://t.co/9RVYMBB - user571646

16

你可以将匿名类型传递给视图,只需记得将模型转换为动态类型。

你可以这样做:

return View(new { 
    MyItem = "Hello", 
    SomethingElse = 42, 
    Third = new MyClass(42, "Yes") })

在视图的顶部,您可以这样做(这里使用 Razor)

@{
    string myItem = (dynamic)Model.MyItem;
    int somethingElse = (dynamic)Model.SomethingElse;
    MyClass third = (dynamic)Model.Third;
}

或者您可以像这样从ViewData中转换它们:

@{
    var myItem = ViewData.Eval("MyItem") as string
    var somethingElse = ViewData.Eval("SomethingElse") as int?
    var third = ViewData.Eval("Third") as MyClass 
}

11
好的,你第一个例子不起作用,在MVC3中至少不起作用。惊讶吧!首先,在未经类型化的视图中,Model已经是dynamic类型,不需要进行强制转换。其次,使用你的代码,我得到了一个RuntimeBinderException异常,其中包含消息“*'object' does not contain a definition of MyItem*”。有什么想法吗? - Fyodor Soikin
8
我刚刚找到了一个解释。事实证明,匿名类型是内部的,这意味着它们的属性无法在定义程序集之外被查看。请看这里:http://www.gregshackles.com/2010/09/anonymous-view-models-in-asp-net-mvc-using-dynamics/ - Fyodor Soikin
在 ASP.NET Core .NET 5 中:Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:“object”不包含“myItem”的定义。 - Lei Yang
我过去10年没有使用.NET,所以我无法回答为什么这在.NET5 Core上不起作用 :P - Lasse Skindstad Ebert

8

在.NET 4.0中,匿名类型可以轻松地转换为ExpandoObjects,从而解决了所有问题,只需要承担转换本身的开销即可。 请查看这里


4
这里是一篇关于将匿名类型传递到视图并绑定数据的文章。感谢您的阅读。点击这里阅读全文。

4

就我所知,今晚我发现了 DataLoadOptions 类及其 LoadWith 方法。我能够告诉我的 LINQ to SQL DataContext 在检索 Orders 行时总是加载 Customers 行,因此原始查询现在可以一次性获取我需要的所有内容。


哇...非常感谢——我已经疯狂地寻找了几个小时。我知道它一定存在,但不确定在哪里找。谢谢啊! - Andrew Flanagan

1

记住:匿名类型是内部的,这意味着它们的属性不能在定义它们的程序集外部看到。

最好通过将匿名类型转换为动态类型,并使用扩展方法将其传递给您的View,而不是传递匿名对象。

public class AwesomeController : Controller
{
    // Other actions omitted...
    public ActionResult SlotCreationSucceeded(string email, string roles)
    {
        return View("SlotCreationSucceeded", new { email, roles }.ToDynamic());
    }
}

该扩展方法将如下所示:

public static class DynamicExtensions
{
    public static dynamic ToDynamic(this object value)
    {
        IDictionary<string, object> expando = new ExpandoObject();

        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
            expando.Add(property.Name, property.GetValue(value));

        return (ExpandoObject) expando;
    }
}

虽然如此,您仍然能够传递一个匿名的对象,但是您需要稍后将其转换为dynamic对象。

public class AwesomeController : Controller
{
    // Other actions omitted...
    public ActionResult SlotCreationSucceeded(string email, string roles)
    {
        return View("SlotCreationSucceeded", new { email, roles });
    }
}

视图:
@{
    var anonymousModel = DynamicUtil.ToAnonymous(Model, new { email = default(string), roles = default(string) });
}

<h1>@anonymousModel.email</h1>
<h2>@anonymousModel.roles</h2>

这个帮助方法看起来像这样:


public class DynamicUtil
{
    public static T ToAnonymous<T>(ExpandoObject source, T sample)
        where T : class
    {
        var dict = (IDictionary<string, object>) source;

        var ctor = sample.GetType().GetConstructors().Single();

        var parameters = ctor.GetParameters();

        var parameterValues = parameters.Select(p => dict[p.Name]).ToArray();

        return (T) ctor.Invoke(parameterValues);
    }
}

我已经尝试过这个,但它一直给出RuntimeBinderException错误。 - Lewis86

1

你可以编写一个具有与匿名类型相同属性的类,并将匿名类型转换为手写类型。缺点是当你在Linq查询中进行投影更改时,你必须更新这个类。


1
那样完全违背了从操作方法使用匿名类型的目的。此时,您可以直接返回一个类实例。 - Peter

1

这篇文章展示了如何从方法中返回匿名类型,但它可能不适合您的需求。

另一个选择是将匿名类型转换为JSON(JavaScriptSerializer可以做到),然后将该JSON返回给视图,您需要使用一些jQuery等工具来处理它。

我一直在使用Linq将我的数据“塑造”成我视图所需的JSON格式,并取得了巨大的成功。


JSON的概念听起来很有趣,但如果我要添加代码来实现它,那我倒不如定义一个包含我的订单和客户属性的类,并放弃使用匿名类型的想法了。不过还是点赞+1。 - Matt Hamilton
同意。我的主要JSON使用场景是从ajax调用返回一个JSON对象,用于填充网格(例如Flexigrid),该网格需要以特定格式呈现数据,Linq非常擅长帮助我将数据塑造成该格式。 - zadam

0

我也遇到了同样的问题... 经过思考,我得出结论,最正确和最可扩展的解决方案是,在发送给视图之前序列化此匿名类型。 因此,您可以使用相同的方法来填充页面并使用JSON填充页面


0

你可以尝试传递一个对象并使用反射来获取你想要的结果。可以参考ObjectDumper.cs(包含在csharpexamples.zip中)作为实例。


是的,我在想使用反射来破解匿名类型并将其视为Dictionary<string, object>可能是唯一的解决方案。如果是这种情况,有点让人失望。 - Matt Hamilton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接