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

Seb*_* F. 5 asp.net-mvc

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

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

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

  1. 在每个视图中都是"手"
  2. 有一个具有Initialize虚方法的基本控制器来执行它(因此特定控制器可以实现特定的常见行为)
  3. 有一个基础控制器覆盖OnActionExecuting以调用Initialize方法
  4. 使用辅助类在控制器外部执行
  5. 使用模型工厂

但这些都没有真正吸引我:

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

当然,(几乎)所有这些解决方案都有效,但我正在寻找一种更好的方法.

在输入这个问题时,我发现可能是一条新路径,也可能是构建器模式,但实现也很快成为负担,因为我们可以拥有数十个视图和控制器.

我很乐意接受任何严肃的建议/暗示/建议/模式/建议!

更新

感谢@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;
    } 
}
Run Code Online (Sandbox Code Playgroud)

然后调用它非常简单:

[Model(typeof(HomeModel))]
public ActionResult Index()
{
    var homeModel = TempData["model"] as HomeModel;

    // Add View Specific stuff

    return View(homeModel);
}
Run Code Online (Sandbox Code Playgroud)

它给了我最好的每一个世界.唯一的缺点是找到一种将模型传递回动作的正确方法.

这里使用TempData对象完成,但我也考虑更新可以在ActionParameters中找到的模型.

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

Seb*_* F. 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; }
}   
Run Code Online (Sandbox Code Playgroud)

例如,我的视图模型是这样的:

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); }
    }
}
Run Code Online (Sandbox Code Playgroud)

这在操作中初始化:

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);
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在视图(.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); } }
Run Code Online (Sandbox Code Playgroud)

实际的解决方案有更多代码,我尝试简化以了解这里的想法。它仍然有点原始,需要完善/错误处理,但这样我就可以在我的操作中定义这些部分是什么,它们将使用什么模型等等。它可以轻松测试,并且设置 DI 应该不是问题。

我仍然必须在每个视图中复制 @section 行,这看起来有点痛苦(特别是因为我们不能将这些部分放在部分视图中)。

我正在研究模板化的剃刀委托,看看它是否无法替换这些部分。