2013-03-22 89 views
5

如何避免ViewBag由于其动态错误的风险,而且避免每次都填充新的ViewModel并将其传递回视图。例如,我不想一定要改变后面的内容来暴露通常填充在ViewBag中的常见数据。有效地避免ASP.NET MVC中的ViewBag

[HttpGet] 
void Index() 
{ 
    return View(); 
} 

[HttpGet] 
void Index() 
{ 
    var messages = new MessageCollection(); 
    messages.AddError("Uh oh!"); 

    return View(messages); 
} 

凡在管道我想补充像ViewBag属性是自定义的强类型,但有它在控制器优雅暴露,也是观。我宁愿做时,我并不需要一个特定的视图模型所有的时间...

[HttpGet] 
void Index() 
{ 
    Messages.AddError("Uh oh!"); 

    return View(); 
} 

而且在观察侧,而不是@((IMessageCollection)ViewBag.Messages).Errors ID,而有像@ Messages.Errors,强类型和可用的地方。另外,我不想只在我的Razor视图顶部的代码块中将其展开。

在WebForms中,我会做一些类似于把这个基本页面,然后有一个usercontrol,可以隐藏或显示在页面上根据需要。随着控制器与视图分离,我不知道如何复制类似的行为。

这是可能的或者什么是最好的设计方法?

感谢, 斯科特

+0

你真的有2个选择:ViewBag和在你的模型中的专用属性 – 2013-03-22 20:58:21

回答

8

剃刀意见是相当简单的。您可以使用强类型的单一模型进行交互。然后,您需要在您的视图中强烈键入任何您需要的模型。如果你有一些你不想要的模型或者是一次性的,那么ViewBag作为所有非模型数据的通用catch-all提供,这就是为什么它是动态的。强类型会限制它成为一个全能型的能力。

简而言之:如果你想强类型的添加消息到你的视图模型。否则,坚持ViewBag。那些是你的选择。

+0

伟大的解释! – ledgeJumper 2013-03-22 21:25:59

+0

是的,我认为这可能是最好的建议,我会看到其他人来到这个问题跟随以及。我真的很想看看MVC架构中是否有某些东西允许将它扩展到将你自己的捕获全部放在你知道那些将始终如一的地方。谢谢! – Scott 2013-03-23 23:22:07

+0

请记住,至少达到MVC5(可能是MVC6 /内核)获取/设置ViewBag的任何属性(例如ViewBag.Title)会导致框架内部的异常被适当抑制和处理。你可以通过禁用'just my code'选项来看到这一点。这是“动态”实施的固有设计,虽然“有效”,但却是实现流量密集型网站高性能的严重障碍。更多的想法:http://mvolo.com/fix-the-3-high-cpu-performance-problems-for-iis-aspnet-apps/ – xDisruptor 2017-06-21 08:10:07

1

我同意克里斯的回答,我个人会将它扔进观察袋。

但是为了倡导鬼子,从技术上讲,你可以通融一下......

编辑:想到它现在,你也许可以用以下替换ViewBag这样HttpContext.Items你在技术上仍在使用ViewBag存储,但只是添加一个包装,给它温暖的安全强烈的感觉。

E.g.你可以有这样的事情:

namespace Your.Namespace 
{ 
    public class MessageCollection : IMessageCollection 
    { 
     public IList<string> Errors { get; protected set; } 
     protected MessageCollection() 
     { 
      //Initialization stuff here 
      Errors = new List<string>(); 
     } 

     private const string HttpContextKey = "__MessageCollection"; 
     public static MessageCollection Current 
     { 
      get 
      { 
       var httpContext = HttpContext.Current; 
       if (httpContext == null) throw new InvalidOperationException("MessageCollection must be used in the context of a web application."); 

       if (httpContext.Items[HttpContextKey] == null) 
       { 
        httpContext.Items[HttpContextKey] = new MessageCollection(); 
       } 

       return httpContext.Items[HttpContextKey] as MessageCollection; 
      } 
     } 
    } 
} 

然后,只需把它在你的控制器是这样的:

[HttpGet] 
public ActionResult Index() 
{ 
    MessageCollection.Current.AddError("Uh oh!"); 

    return View(); 
} 

或者你可以有一个BaseController与快捷吸气...例如

protected MessageCollection Messages { get { return MessageCollection.Current; } } 

然后在你的控制器不是继承它

[HttpGet] 
public ActionResult Index() 
{ 
    Messages.AddError("Uh oh!"); 

    return View(); 
} 

为了得到它在你看来,简单地改变你的web.config(你可能需要做这在一些地方(即你的主web.config文件,查看目录的web.config和区域的观点目录的web.config)

<system.web.webPages.razor> 
    <!-- blah --> 
    <pages pageBaseType="System.Web.Mvc.WebViewPage"> 
    <namespaces> 
     <!-- blah --> 
     <add namespace="Your.Namespace" /> 
    </namespaces> 
    </pages> 
</system.web.webPages.razor> 

然后在你的意见,你应该能够做到:

<div class="messages"> 
    @foreach (var error in MessageCollection.Current.Errors) 
    { 
     <span>@error</span> 
    } 
</div> 
+0

这实际上是我带走的“解决方法”但我不想把它作为问题的一部分,因为害怕很快关闭新的创意。我与你和Chris都在同一页面上,但是我希望MVC架构中可以有一些扩展点来处理这个我不知道的东西。 – Scott 2013-03-23 22:36:59

0

在ASP.NET MVC中,您可以随时使用ViewBag,ViewDataTempData(有关更多信息,请参阅this blog post)。 ViewBag是围绕ViewData字典的动态封装。如果你这样做ViewBag.Prop = "value"它相当于ViewData["Prop"] = "value"。当您在视图中使用Model媒体资源时,您正在检索ViewData.Model。寻找自己:

public abstract class WebViewPage<TModel> : WebViewPage 
{ 
    private ViewDataDictionary<TModel> _viewData; 
    public new AjaxHelper<TModel> Ajax { get; set; } 
    public new HtmlHelper<TModel> Html { get; set; } 
    public new TModel Model { get { return ViewData.Model; } } 
} 

我们可以通过使用ViewBagViewData握住你的特殊性能实现您的结束。第一步是创建WebViewPage<TModel>自定义导出你想要的属性:

public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> 
{ 
    public IList<string> Messages 
    { 
     get { return ViewBag.Messages ?? (ViewBag.Messages = new List<string>()); } 
    } 
} 

现在去到您的视图,并替换为以下行@model YourModelClass(第一行):

@inherits CustomWebViewPage<YourModelClass> 

您现在可以在视图中使用Messages属性。

@String.Join(", ", Messages) 

要在控制器上使用它,你可能会想从Controller派生并添加属性那里。

public abstract class CustomControllerBase : Controller 
{ 
    public IList<string> Messages 
    { 
     get 
     { 
      return ViewBag.Messages ?? (ViewBag.Messages = new List<string>()); 
     } 
    } 
} 

现在,如果您从该控制器派生,则可以使用您的新属性。您在列表中添加的任何内容也将在视图中提供给您。

public class ExampleController : CustomControllerBase 
{ 
    public ActionResult Index() 
    { 
     Messages.Add("This is a message"); 
     return View(); 
    } 
} 

我使用ViewBag,因为它使属性获得更短。如果您愿意(ViewData["Messages"]),您可以使用ViewData做同样的事情。

这与Model的实施方式并不完全相同,因为如果您碰巧使用了您要保存的密钥,某人可能意外覆盖了您的财产,但它足够接近以至于在功能上等同于您确保使用一个唯一的密钥。

如果您深入挖掘,您可能可以从ViewDataDictionary派生出来,然后将您的属性放在那里,然后重写一些控制器和视图方法以代替它。那么你的财产将与Model完全一样。但我会把它留给你 - 我认为这不值得。