用于视图和布局的Asp.net MVC模型

时间:2012-04-03 12:53:36

标签: asp.net-mvc

当我为所有网页提供公共属性时,我一直在努力寻找一种处理Asp.net MVC网站模型的好方法。这些属性将显示在布局(母版页)中。我正在使用一个包含这些属性的“BaseModel”类,我的布局使用此BaseModel作为其模型。

每个其他模型都继承自该BaseModel,并且每个模型都具有相对于它所代表的视图的特定属性。正如您可能已经猜到的那样,我的模型实际上是视图模型,即使这在这里并不相关。

我尝试了不同的方法来初始化BaseModel值

  1. 每个视图中的“手”
  2. 拥有一个具有Initialize虚拟方法的基本控制器(因此特定控制器可以实现特定的常见行为)
  3. 使用覆盖OnActionExecuting以调用Initialize方法的基础控制器
  4. 使用帮助程序类在控制器之外执行
  5. 使用模型工厂
  6. 但这些都没有真正吸引我:

    1. 对我来说似乎很明显,但DRY是足以证明这一点的一个原因(实际上我根本没有尝试过这个解决方案,我只是想让它能够在最后一点循环这一点)。
    2. 我不喜欢那个,因为这意味着每当添加一个新的Controller时,你需要知道它必须从BaseController继承并且你需要调用Initialize方法,更不用说如果你的控制器了已覆盖基础,无论如何都要调用基数以维持值。
    3. 见下一点
    4. 和3.是同一主题的变体,但对第二种解决方案的问题并没有真正的帮助。
    5. 到目前为止我最喜欢的,但现在我必须传递一些变量来设置这些值。我喜欢它的依赖倒置。但是如果我想要提供会话中的值,我需要明确地传递它们以示例,然后我回到原点,因为我必须手动提供它们(作为引用或通过任何类型的接口)
    6. 当然,(差不多)所有这些解决方案都有效,但我正在寻找更好的方法。

      在输入这个问题时,我发现了一条新的路径,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中找到的模型。

      我仍在为此或之前的观点采取任何认真的建议/暗示/建议/模式/建议。

2 个答案:

答案 0 :(得分: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
   }
}

最后,我的模式并不令人满意。然而,它实用,灵活且易于实现不同级别的继承。我还能够注入控制器执行前或后执行,这在我的案例中非常重要。希望这会有所帮助。

答案 1 :(得分:1)

给我@EBarr使用动作过滤器的想法实际上是有效但最终感觉不对,因为没有干净的方法来检索模型而不通过viewbag,或httpcontext项目,或类似的东西。此外,它必须使用其模型装饰每个动作。它也使得回发更难以处理。我仍然认为这个解决方案有优点,在某些特定情况下可能会有用。

所以我回到原点,开始更多地关注这个话题。我来到了以下。首先问题有两个方面

  1. 初始化视图的数据
  2. 渲染数据
  3. 在寻找更多想法的同时,我意识到我并没有从正确的角度看问题。我从“控制器”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):

    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以查看是否无法替换这些部分。