2012-04-03 136 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