您如何处理避免ViewBag由于其错误的风险是动态的,但也避免不得不填充一个新的ViewModel,并把它传回给视图每次。例如,我不想为了暴露通常塞在ViewBag中的公共数据而改变跟随。
[HttpGet]
void Index()
{
return View();
}
[HttpGet]
void Index()
{
var messages = new MessageCollection();
messages.AddError("Uh oh!");
return View(messages);
}
在管道中,我将添加一个像ViewBag这样的属性,它是自定义的和强类型的,但它在控制器和视图中优雅地暴露。我宁愿这样做,当我不需要一个特定的ViewModel所有的时间…
[HttpGet]
void Index()
{
Messages.AddError("Uh oh!");
return View();
}
在视图侧,而不是@((IMessageCollection)ViewBag.Messages)。我宁愿使用@Messages之类的东西。错误是强类型的,在任何地方都可以使用。而且,我不想在Razor视图顶部的代码块中强制转换它。
在WebForms中,我会做一些事情,比如把它放在一个基本页面,然后有一个用户控件,可以根据需要在页面上隐藏或显示。随着控制器与视图解耦,我不确定如何复制类似的行为。
这是可能的吗?或者最好的设计方法是什么?
谢谢,斯科特
Razor视图相当简单。您与单个模型交互,该模型是强类型的。因此,视图中需要强类型的任何内容都需要在模型中。如果您的模型中有您不想要的东西,或者这是一次性的,那么ViewBag
是作为所有非模型数据的通用捕获器提供的,这就是为什么它是动态的。强类型会限制它成为包罗万象的能力。
简短:如果你想要强类型的添加消息到你的视图模型。否则,坚持使用ViewBag
。
我同意Chris的回答,我个人会把它扔进viewbag。
但是,从技术上讲,你可以改变规则…
编辑:现在考虑一下,你可以用ViewBag
替换HttpContext.Items
,这样你在技术上仍然使用ViewBag进行存储,但只是添加一个包装器来给它温暖安全的强类型感觉。
。你可以这样写:
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();
}
或者你可以有一个带有快捷getter的BaseController…例如
protected MessageCollection Messages { get { return MessageCollection.Current; } }
然后在你的控制器中继承它
[HttpGet]
public ActionResult Index()
{
Messages.AddError("Uh oh!");
return View();
}
要在您的视图中获得它,只需更改您的web。你可能需要在几个地方这样做(即你的主web。配置,查看目录web。配置和区域视图目录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>
. NET MVC,您可以使用ViewBag
, ViewData
和TempData
(有关更多信息,请参阅此博客文章)。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; } }
}
我们可以通过使用ViewBag
或ViewData
来保存您的特殊属性来实现您的目的。第一步是用您想要的属性创建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是因为它使属性getter更短。如果你愿意,你也可以对ViewData
(ViewData["Messages"]
)做同样的事情。
这与Model
的实现方式并不完全相同,因为如果有人碰巧使用了您正在保存的密钥,他们可能会意外地覆盖您的属性,但如果您确保使用唯一的密钥,则它足够接近,在功能上是等效的。
如果你深入挖掘,你可能能够从ViewDataDictionary
派生并把你的属性放在那里,然后重写一些控制器和视图方法来使用它。那么您的属性将与Model
完全相同。但是我把它留给你——我认为不值得。