7

您如何处理避免 ViewBag 的问题,因为它存在动态出错的风险,但也避免每次都必须填充新的 ViewModel 并将其传递回视图。例如,我不想更改以下内容以公开通常填充在 ViewBag 中的常见数据。

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

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

    return View(messages);
}

我将在管道中的哪个位置添加一个像 ViewBag 这样的自定义且强类型的属性,但它会优雅地暴露在 Controller 和 View 中。当我不需要特定的 ViewModel 时,我宁愿这样做......

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

    return View();
}

在视图方面,而不是 @((IMessageCollection)ViewBag.Messages).Errors id 而是具有类似 @Messages.Errors 的东西,它是强类型的并且随处可用。此外,我不想只是将它投射到我的 Razor 视图顶部的代码块中。

在 WebForms 中,我会做一些事情,比如把它作为一个基本页面,然后有一个用户控件,可以根据需要在页面上隐藏或显示。控制器与视图分离后,我不确定如何复制类似的行为。

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

谢谢,斯科特

4

3 回答 3

10

Razor 视图相当简单。您与单个模型进行交互,该模型是强类型的。那么,任何你想在视图中强类型化的东西都需要在你的模型上。如果您的模型中有一些您不想要的东西或者是一次性的,那么ViewBag将作为所有非模型数据的通用包罗万象提供,这就是为什么它是动态的。强类型会限制它成为一个包罗万象的能力。

简短而简单:如果您想要强类型的消息添加到您的视图模型。否则,坚持ViewBag. 这些是你的选择。

于 2013-03-22T20:58:42.327 回答
2

我同意 Chris 的回答,我个人会将其放入 viewbag 中。

但只是为了扮演魔鬼的拥护者,从技术上讲,你可以改变规则......

编辑:现在想一想,您可能可以HttpContext.Items在下面替换为,ViewBag这样您在技术上仍然使用 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();
}

或者你可以有一个带有快捷获取器的 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>
于 2013-03-22T21:22:12.167 回答
0

在 ASP.NET MVC 中,您可以随意使用ViewBag、、ViewDataTempData(有关更多信息,请参阅此博客文章)。这ViewBagViewData字典的动态包装器。如果你这样做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 是因为它使属性 getter 更短。ViewData如果您愿意,您可以使用 ( ) 做同样的事情ViewData["Messages"]

这与实现方式并不完全相同,Model因为如果有人碰巧使用您正在保存的密钥,他们可能会意外覆盖您的属性,但如果您确保使用唯一的密钥,它就足够接近以在功能上等效。

如果您深入挖掘,您可能能够从中派生ViewDataDictionary并将您的属性放在那里,然后覆盖一些控制器和视图方法来使用它。那么您的属性将与 完全相同Model。但我会把它留给你——我认为这不值得。

于 2015-05-11T19:45:59.333 回答