Effective ViewBag exception in ASP.NET MVC

How do you deal with avoiding the ViewBag due to the risk of dynamic errors, but also avoiding populating the new ViewModel and passing it back to the view every time. For example, I do not want to change this in order to display the general data usually populated in the ViewBag.

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

to

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

If in the pipeline I add a property like ViewBag, which is regular and strongly typed, but it should be elegantly presented in the controller, as well as in the view. I would prefer to do this when I don't need a specific ViewModel all the time ...

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

And on the presentation side, instead of @ ((IMessageCollection) ViewBag.Messages) .Errors id, it rather has something like @ Messages.Errors, which is strongly typed and accessible everywhere. Also, I don't want to just expose it in the code block at the top of my Razor view.

In WebForms, I would do something like this base page, and then create a user control that can hide or show on pages as needed. When the controller is disconnected from the view, I am not sure how to replicate this behavior.

Is this possible, or what is the best design approach?

Thanks Scott

+5
architecture asp.net-mvc viewmodel viewbag
source share
3 answers

The razor looks pretty simplistic. You interact with one model that is strongly typed. Everything that you want, strictly typing your opinion, should be on your model. If you have something that you don’t want on your model, or one-time, then the ViewBag provided as a common trap for all non-model data, so it is dynamic. To be strongly typed, this would limit the ability to be a trick.

In short and simple: if you want to be strongly typed, add messages to your view model. Otherwise, stick with the ViewBag . This is your choice.

+8
source share

I agree with Chris's answer, and personally, I would have thrown it in my bag.

But just to play the devil's advocate, technically, you can bend the rules ...

Edit: Just thinking about it, you will probably replace the HttpContext.Items below with a ViewBag to technically still use the ViewBag for storage, but just add a wrapper to give it a warm, secure, strongly typed feel.

eg. you can have something like this:

 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; } } } } 

Then simply enter it into your controller as follows:

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

Or you might have a BaseController with a getter shortcut ... e.g.

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

Then in your controller, what inherits from it

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

To get this in your view, simply change your web.config (you may need to do this in several places (i.e. your main web.config file, view directory web.config and view directories of the web.config areas)

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

Then in your presentations you can:

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

In ASP.NET MVC, you have ViewBag , ViewData and TempData at your disposal (see this blog post for more information). ViewBag is a dynamic wrapper around the ViewData dictionary. If you execute ViewBag.Prop = "value" , this is equivalent to ViewData["Prop"] = "value" . When you use the Model property in a view, you get ViewData.Model . See for yourself:

 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; } } } 

We can reach your end using ViewBag or ViewData to preserve your special properties. The first step is to create custom WebViewPage<TModel> output with the desired property:

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

Now go to your view and replace the @model YourModelClass line (first line) with the following:

 @inherits CustomWebViewPage<YourModelClass> 

Now you can use the Messages property in your view.

 @String.Join(", ", Messages) 

To use it in your controllers, you probably want to get from Controller and add a property there.

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

Now, if you exit this controller, you can use your new property. Everything that you specify in the list will also be available to you in the view.

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

I used the ViewBag because it made the getter property shorter. You can do the same with ViewData if you want ( ViewData["Messages"] ).

This is not exactly the same as that implemented by Model , because someone may accidentally overwrite your property if they use a key that you save, but it is close enough to be functionally equivalent if you just make sure to use a unique the key.

If you dig deeper, you can get from ViewDataDictionary and place your property there, and then override some of the controller and view methods to use it instead. Then your property will be exactly the same as Model . But I will leave it to you - I do not think it is worth it.

0
source share

All Articles