2012-04-03 189 views
5

我一直在試圖找到一種很好的方式來處理我們的Asp.net MVC網站的模型,當有所有頁面的共同屬性。這些屬性將顯示在佈局(主頁面)中。我使用一個「BaseModel」類來保存這些屬性,我的Layout使用這個BaseModel作爲它的模型。Asp.net MVC模型的視圖和佈局

每個其他模型都從該BaseModel繼承,並且每個模型都具有相對於它所表示的視圖的特定屬性。正如你可能已經猜到的那樣,我的模型實際上是視圖模型,即使這在這裏不太相關。

我曾嘗試不同的方法來初始化BaseModel在每個視圖

  • 擁有一個具有初始化虛擬方法做一個基本的控制器值

    1. 通過「手」(這樣特定的控制器可以實現特定的對於爲例共同的行爲)
    2. 具有基礎controlelr是覆蓋OnActionExecuting調用初始化方法
    3. 使用一個輔助類做控制器之外
    4. 使用模型廠

    但無那些真正吸引我:

    1. 似乎是顯而易見的我,但乾的理由一個足夠的理由認爲(其實我從來沒有嘗試過的解決方案可言,我只是把它放在最後一點能夠循環)。
    2. 我不喜歡那個,因爲這意味着無論何時添加一個新的Controller,都需要知道它必須從BaseController繼承,並且需要調用Initialize方法,更不用說如果您的控制器已經覆蓋了基本的一個,無論如何調用基地來維護這些值。
    3. 請參閱下一點
    4. 和3.是同一主題的變體,但對第二個解決方案的問題沒有幫助。
    5. 到目前爲止我的最愛,但現在我必須通過幾個變量來設置這些值。我喜歡它的依賴性倒置。但是,如果我想提供會話中的值,我需要將它們明確地傳遞給例子,然後我回到原點,因爲我必須手動提供它們(作爲參考或通過任何類型的接口)

    當然,(幾乎)所有這些解決方案都有效,但我正在尋找一種更好的方法來實現。

    在輸入這個問題時,我發現可能有一個新的路徑builder pattern,但實現也可能很快成爲一個負擔,因爲我們可以有幾十個視圖和控制器。

    我會很樂意接受任何嚴肅的建議/提示/建議/模式/建議!

    更新

    感謝@EBarr我想出了另一種解決方案,使用ActionFilterAttribute(不生產代碼,這樣做是在5分鐘內):

    public class ModelAttribute : ActionFilterAttribute 
    { 
        public Type ModelType { get; private set; } 
    
        public ModelAttribute(string typeName) : this(Type.GetType(typeName)) { } 
    
        public ModelAttribute(Type modelType) 
        { 
         if(modelType == null) { throw new ArgumentNullException("modelType"); } 
    
         ModelType = modelType; 
         if (!typeof(BaseModel).IsAssignableFrom(ModelType)) 
         { 
          throw new ArgumentException("model type should inherit BaseModel"); 
         } 
        } 
    
        public override void OnActionExecuting(ActionExecutingContext filterContext) 
        { 
         var model = ModelFactory.GetModel(ModelType); 
    
         var foo = filterContext.RequestContext.HttpContext.Session["foo"] as Foo; 
    
         model.Foo = foo; 
         model.Bar = somevalue; 
    
         filterContext.Controller.TempData["model"] = model; 
        } 
    } 
    

    稱它是那麼很簡單:

    [Model(typeof(HomeModel))] 
    public ActionResult Index() 
    { 
        var homeModel = TempData["model"] as HomeModel; 
    
        // Add View Specific stuff 
    
        return View(homeModel); 
    } 
    

    它給了我最好的每一個世界。唯一的缺點是要找到一種合適的方式將模型傳遞迴動作。

    這裏使用TempData對象完成,但我也考慮更新可以在ActionParameters中找到的模型。

    我仍然採取任何嚴重的建議/提示/建議/模式/建議,或以前的觀點。

  • 回答

    1

    給了我@EBarr使用動作過濾器的想法實際上是在工作,但最終感覺錯了,因爲沒有通過viewbag或httpcontext項目或類似物品來檢索模型的乾淨方法。此外,它強制性地用它的模型來裝飾每一個動作。這也使回發更難以處理。我仍然相信這個解決方案有優點,並且在某些特定情況下可能會有用。

    所以我回到原點,開始尋找更多的話題。我來到以下。首先這個問題有兩個方面

    1. 初始化數據視圖
    2. 渲染數據

    雖然尋找更多的想法,我意識到,我不看,從正確的角度思考問題。我從「控制器」POV看它,而模型的最終客戶端是視圖。我還被提醒說,佈局/母版頁不是一個視圖,不應該有一個與之相關的模型。這種見解讓我感覺到了對我來說是正確的道路。因爲這意味着佈局的每個「動態」部分都應該在其外部進行處理。當然,由於它們的靈活性,部分似乎是最適合的。

    在我所做的測試解決方案中,我只有4個不同的部分,有些是強制性的,有些不是。部分的問題在於,您需要將它們添加到每個頁面上,這很快會導致更新/修改的痛苦。爲了解決這個問題,我想這:

    public interface IViewModel 
    { 
        KeyValuePair<string, PartialViewData>[] Sections { get; } 
    } 
    
    public class PartialViewData 
    { 
        public string PartialViewName { get; set; } 
        public object PartialViewModel { get; set; } 
        public ViewDataDictionary ViewData { get; set; } 
    } 
    

    對於爲例,我的視圖模型是這樣的:

    public class HomeViewModel : IViewModel 
    { 
        public Article[] Articles { get; set; }    // Article is just a dummy class 
        public string QuickContactMessage { get; set; }  // just here to try things 
    
        public HomeViewModel() { Articles = new Article[0]; } 
    
        private Dictionary<string, PartialViewData> _Sections = new Dictionary<string, PartialViewData>(); 
        public KeyValuePair<string, PartialViewData>[] Sections 
        { 
         get { return _Sections.ToArray(); } 
         set { _Sections = value.ToDictionary(item => item.Key, item => item.Value); } 
        } 
    } 
    

    這怎麼被初始化的動作:

    public ActionResult Index() 
    { 
        var hvm = ModelFactory.Get<HomeViewModel>(); // Does not much, basicaly a new HomeViewModel(); 
    
        hvm.Sections = LayoutHelper.GetCommonSections().ToArray(); // more on this just after 
        hvm.Articles = ArticlesProvider.GetArticles(); // ArticlesProvider could support DI 
    
        return View(hvm); 
    } 
    

    LayoutHelper是控制器上的財產(如果需要可以進行DI'ED):

    public class DefaultLayoutHelper 
    { 
        private Controller Controller; 
        public DefaultLayoutHelper(Controller controller) { Controller = controller; } 
    
        public Dictionary<string, PartialViewData> GetCommonSections(QuickContactModel quickContactModel = null) 
        { 
         var sections = new Dictionary<string, PartialViewData>(); 
         // those calls were made in methods in the solution, I removed it to reduce the length of the answer 
         sections.Add("header", 
            Controller.UserLoggedIn() // simple extension that check if there is a user logged in 
            ? new PartialViewData { PartialViewName = "HeaderLoggedIn", PartialViewModel = new HeaderLoggedInViewModel { Username = "Bishop" } } 
            : new PartialViewData { PartialViewName = "HeaderNotLoggedIn", PartialViewModel = new HeaderLoggedOutViewModel() }); 
         sections.Add("quotes", new PartialViewData { PartialViewName = "Quotes" }); 
         sections.Add("quickcontact", new PartialViewData { PartialViewName = "QuickContactForm", PartialViewModel = model ?? new QuickContactModel() }); 
         return sections; 
        } 
    } 
    

    並在視圖(.cshtml):

    @section  quotes { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "quotes").Value); } } 
    @section  login { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "header").Value); } } 
    @section  footer { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "footer").Value); } } 
    

    實際的解決方案具有更多的代碼,我試圖簡化到只是得到的想法在這裏。它仍然有點粗糙,需要拋光/錯誤處理,但是我可以在我的動作中定義哪些部分將會是什麼,他們將使用什麼模型等等。它可以很容易地測試和設置DI不應該是一個問題。

    我仍然需要在每個視圖中重複@section行,這看起來有點痛苦(特別是因爲我們無法將部分放在局部視圖中)。

    我正在研究templated razor delegates以查看是否無法替換這些部分。

    2

    我經歷了幾乎完全相同的過程,因爲我進入MVC。你說得對,沒有一個解決方案感覺很棒。

    最後我用了一系列基本模型。由於各種原因,我有幾種不同類型的基礎模型,但邏輯應該適用於單一基礎類型。我的大多數觀點模型都是從其中一個基礎繼承而來的。然後,根據需要/時機,我填寫模型的基本部分ActionExecutingOnActionExecuted

    我的代碼片段,應該使流程清晰:

    if (filterContext.ActionParameters.ContainsKey("model")) { 
        var tempModel = (System.Object)filterContext.ActionParameters["model"]; 
    
        if (typeof(BaseModel_SuperLight).IsAssignableFrom(tempModel.GetType())) { 
         //do stuff required by light weight model 
        } 
    
        if (typeof(BaseModel_RegularWeight).IsAssignableFrom(tempModel.GetType())) { 
         //do more costly stuff for regular weight model here 
        } 
    } 
    

    在結束我的模式並沒有感到太滿意。然而,它是實用的,靈活且容易實現不同級別的繼承。我也能夠在控制器執行前或控制後執行,這在我的情況下非常重要。希望這可以幫助。

    +0

    感謝您的反饋。雖然它不完全符合我的要求(您的解決方案是3的不同實現),但它實際上給了我一個想法。我只是嘗試使用一個自定義的ActionFilterAttribute,似乎可以做到這一點。我仍然需要設計一種「乾淨」的方式來讓模型回到動作中(現在我正在使用TempData)。我會更新這個問題來反映這一點。 – 2012-04-03 14:02:03

    +0

    是的,它是#3的一個版本。我想過屬性,但是需要裝飾每個控制器/動作(取決於需要)。在OnActionExecuting和OnActionExecuted中保存我的代碼保存在一個基本控制器中。在實踐中,對於任何更大的MVC項目,您將擁有一個基本控制器,所以我不介意這麼多。它還允許我基於模型類型運行邏輯,而不是綁定執行操作的邏輯。 – EBarr 2012-04-03 14:07:35

    +0

    RE:抓取模型:在你的動作過濾器屬性中,你將最終實現相同的事件(執行和執行),但是你可以從filterContext中取出模型,不需要把它交回,只需找到它並填寫你需要的東西。 – EBarr 2012-04-03 14:29:23