当我为所有网页提供公共属性时,我一直在努力寻找一种处理Asp.net MVC网站模型的好方法。这些属性将显示在布局(母版页)中。我正在使用一个包含这些属性的“BaseModel”类,我的布局使用此BaseModel作为其模型。
每个其他模型都继承自该BaseModel,并且每个模型都具有相对于它所代表的视图的特定属性。正如您可能已经猜到的那样,我的模型实际上是视图模型,即使这在这里并不相关。
我尝试了不同的方法来初始化BaseModel值
但这些都没有真正吸引我:
当然,(差不多)所有这些解决方案都有效,但我正在寻找更好的方法。
在输入这个问题时,我发现了一条新的路径,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中找到的模型。
我仍在为此或之前的观点采取任何认真的建议/暗示/建议/模式/建议。
答案 0 :(得分:2)
我经历了与进入MVC几乎完全相同的过程。而且你是对的,没有一个解决方案感觉那么棒。
最后我使用了一系列基础模型。由于各种原因,我有几种不同类型的基本模型,但逻辑应该适用于单一基本类型。我的大多数视图模型都是从其中一个基础继承而来的。然后,根据需要/时间,我在ActionExecuting
或OnActionExecuted
中填充模型的基础部分。
我的代码片段应该让流程清晰:
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项目,或类似的东西。此外,它必须使用其模型装饰每个动作。它也使得回发更难以处理。我仍然认为这个解决方案有优点,在某些特定情况下可能会有用。
所以我回到原点,开始更多地关注这个话题。我来到了以下。首先问题有两个方面
在寻找更多想法的同时,我意识到我并没有从正确的角度看问题。我从“控制器”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以查看是否无法替换这些部分。