2011-09-15 52 views
3

我有一個MVC3網站,我已經設置了測試另一個網站 - 大部分是快速和骯髒的,所以我還沒有去鎮上創建模型和視圖所有視圖的模型類型 - 僅在用戶需要輸入的情況下。匿名類型缺少成員問題的動態視圖 - MVC3

好吧,讓我有一個控制器方法,投影Linq序列並將其設置爲ViewBag

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new { Value = i }); 

在我看來(剃刀C#),那麼我想讀這一點 - 很簡單:

@foreach(dynamic item in ViewBag.SomeData) 
{ 
    @:Number: @item.i 
} 

除,當然,我得到一個RuntimeBinderException,因爲在控制器中創建匿名類型是內部的到web項目的輸出程序集,這裏的實際Razor代碼將運行在由構建管理器生成的不同程序集中,因此,總之DENIED!

很明顯,一個'正確的'模型類型可以解決這個問題 - 但是讓我們說我根本不想這樣做,因爲這是我的特權(!) - 如何最好地保持代碼最小並保持動態在這裏?

回答

4

好抱歉,我只是不同意,這是可怕的等等等等。是的,我永遠不會考慮在生產網站上做這些東西 - 但對於原型,非商業,僅限內部,用於測試目的,我認爲使用這種方法是完美的有效。

這就是我所做的(我強調這只是真正解決了匿名類型的問題);舉起成員並將它們推入ExpandoObject

我最初的變化是使投影多語句返回一個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)) 
    { 
     toReturn[prop.Name] = prop.GetValue(obj, null); 
    } 

    return toReturn; 
    } 
} 

這意味着然後,我可以用我的原代碼 - 但標籤上月底一點點擴展方法調用:

ViewBag.SomeData=Enumerable.Range(1,10).Select(i=> new { Value = i }.ToSafeDynamic()); 

而且你知道什麼 - 我知道這是效率不高,大多數人會說這是醜陋;但它會爲我節省時間,並讓我專注於在我的測試站點中爲我的QA團隊編寫功能,以用於測試真實的東西(其代碼當然是完美無瑕的:))。

+2

使用ExpandoObject的好決定 - 已經忘記了這個選項。 –

+1

@Jon - 謝謝!讓我們面對它吧,它是一種違背幾乎所有我們以前的C#概念的類型,因此很容易被遺忘。哦,順便說一句 - C#深度第二版的岩石。 –

+0

我的藉口是,我很少做任何動態的事情。很高興聽到你喜歡這本書:) –

0

您是否知道構建管理器生成的程序集的名稱?如果是這樣,您應該能夠在控制器組件中應用InternalsVisibleTo,並且它將一切正常。

編輯:一種可能的解決方案:在模型組件中創建一個擴展DynamicObject的公共類型,它通過反射將任何屬性請求代入您的匿名類型。這將是醜陋的,但我認爲它應該工作......有效地使匿名類型公開。

+1

是啊,我發現這裏的解決方案:http://www.heartysoft.com/anonymous-types-c-sharp-4-dynamic,但我不相信輸出程序集的身份在通過Razor Build Engine動態生成時是一致的。 –

+1

是的 - 正如我的想法 - 輸出Razor視圖的程序集限定名稱顯示對標記的任何更改都會強制重新編譯,並且程序集名稱也會更改。 –

+0

@安德拉斯:好的,將編輯... –

1

顯然,一個「適當的」模型類型可以解決這個問題 - 但是我們要說 我根本就不想這樣做,因爲這是我的特權

你不能有這樣的特權(!)。對不起,這絕對沒有任何藉口:-)更不用說,你試圖實現的是不可能的,因爲動態類型是聲明程序集的內部。 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) 

的事情,我們已經從最初的版本改進:

與智能感知
  • 我們強類型的意見(如果你激活編譯的意見,甚至編譯時的安全性)強類型的視圖模型的
  • 用法適應您的意見的具體要求。
  • 擺脫ViewBag /的ViewData這意味着弱類型的,其避免在你的看法寫醜循環顯示模板
  • 用法=>你依靠慣例和框架沒有休息
+1

相信我這個測試的MVC3網站確實遵循了所有這些原則;但是我不得不說,如果你想要測試應用程序以便人們想要交互地測試你的網站(這是一個web服務啓動的事情),那麼我認爲更快速的原型方法是完全合理的;無論如何,如果我想(無限),我可以把它作爲我的特權! –

+2

@Andras Zoltan,爲了實現這個目標,你必須編寫很多管道代碼來對抗編譯器將內部動態類型生成爲內部的事實,在一天結束的時候,你會浪費你很多寶貴的時間來做一些黑客而不是側重於實施。如果你想做快速模型,請使用腳手架。 –

+1

4個擴展方法的語句做到了。我根本不反對你的觀點;但我認爲沒有理由被束縛於每一個最佳實踐(旨在實現可維護和高效的專業最終產品)的最後一點,而這最終是一種輔助「測試工具」。我可以快速開發它,並且可以輕鬆地添加和刪除我的視圖中的信息,而無需擔心類型管理。 –

1

我認爲ToSafeDynamic是一個很好的解決方案。但我想分享一些其他選項,使用開源的ImpromptuInterface(在nuget中),在這種情況下可以很好地工作。

一個選項,作爲基於DynamicObject的代理,ImpromptuGet它只是將調用轉發給annonymous類型,類似於使用動態(它使用相同的api,除了它將上下文設置爲匿名類型的自我,所以internal訪問不會'問題)。

ViewBag.SomeData = Enumerable.Range(1,10) 
          .Select(i => ImpromptuGet.Create(new { Value = i })); 

此選項看起來並不ToSafeDynamic乾淨,但它的,因爲它只是調用屬性使用它們時,而不是複製所有數據前期小的區別。

但是,ImpromptuInterface的更好的解決方案是創建原型動態對象的quick syntax

ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Build.NewObject(Value:i)); 

,將創建一個的expando狀物體,但你也必須創建文字ExpandoObject秒(這在我的測試提供的干將鑄態動態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)); 
+1

+1這是一些很酷的東西,沒有聽說過這個;我在想,當我遇到這個問題時,如果C#支持動態如ExpandoObject的初始化器,那麼問題就很容易解決;使用命名參數是解決這個問題的一個聰明方法。 –

0

如果你只是爲了純粹的quick'n'dirty,你總是可以在這種情況下使用元組。

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new Tuple<int>(i); 

-

@foreach(dynamic item in ViewBag.SomeData) 
{ 
    @:Number: @item.Item1 
} 
相關問題