How ViewBag in ASP.NET MVC works - asp.net

How does the ASP.NET MVC's ViewBag work? MSDN says it is just an Object, which intrigues me, how does "Magic" properties such as ViewBag.Foo and magic strings ViewBag["Hello"] actually work?
Also, how can I make one and use it in my ASP.NET WebForms app?
Examples would be really appreciated!

ViewBag is of type dynamic but, is internally an System.Dynamic.ExpandoObject()
It is declared like this:
dynamic ViewBag = new System.Dynamic.ExpandoObject();
which is why you can do :
ViewBag.Foo = "Bar";
A Sample Expander Object Code:
public class ExpanderObject : DynamicObject, IDynamicMetaObjectProvider
{
public Dictionary<string, object> objectDictionary;
public ExpanderObject()
{
objectDictionary = new Dictionary<string, object>();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
object val;
if (objectDictionary.TryGetValue(binder.Name, out val))
{
result = val;
return true;
}
result = null;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
try
{
objectDictionary[binder.Name] = value;
return true;
}
catch (Exception ex)
{
return false;
}
}
}

It's a dynamic object, meaning you can add properties to it in the controller, and read them later in the view, because you are essentially creating the object as you do, a feature of the dynamic type. See this MSDN article on dynamics. See this article on it's usage in relation to MVC.
If you wanted to use this for web forms, add a dynamic property to a base page class like so:
public class BasePage : Page
{
public dynamic ViewBagProperty
{
get;
set;
}
}
Have all of your pages inherit from this. You should be able to, in your ASP.NET markup, do:
<%= ViewBagProperty.X %>
That should work. If not, there are ways to work around it.

The ViewBag is an System.Dynamic.ExpandoObject as suggested. The properties in the ViewBag are essentially KeyValue pairs, where you access the value by the key. In this sense these are equivalent:
ViewBag.Foo = "Bar";
ViewBag["Foo"] = "Bar";

ViewBag is used to pass data from Controller Action to view to render the data that being passed. Now you can pass data using between Controller Action and View either by using ViewBag or ViewData.
ViewBag: It is type of Dynamic object, that means you can add new fields to viewbag dynamically and access these fields in the View. You need to initialize the object of viewbag at the time of creating new fields.
e.g:
1. Creating ViewBag:
ViewBag.FirstName="John";
Accessing View:
#ViewBag.FirstName.

ViewBag is of type dynamic. More, you cannot do ViewBag["Foo"]. You will get exception - Cannot apply indexing with [] to an expression of type 'System.Dynamic.DynamicObject'.
Internal implementation of ViewBag actually stores Foo into ViewData["Foo"] (type of ViewDataDictionary), so those 2 are interchangeable. ViewData["Foo"] and ViewBag.Foo.
And scope. ViewBag and ViewData are ment to pass data between Controller's Actions and View it renders.

ViewBag is a dynamic type that allow you to dynamically set or get values and allow you to add any number of additional fields without a strongly-typed class
They allow you to pass data from controller to view.
In controller......
public ActionResult Index()
{
ViewBag.victor = "My name is Victor";
return View();
}
In view
#foreach(string a in ViewBag.victor)
{
.........
}
What I have learnt is that both should have the save dynamic name property ie ViewBag.victor

public dynamic ViewBag
{
get
{
if (_viewBag == null)
{
_viewBag = new DynamicViewData(() => ViewData);
}
return _viewBag;
}
}

Related

Access session on view page in ASP.Net MVC

Is there any way to access session on view page by creating common method in controller also want to access session in controller by common method in ASP.Net MVC.
are you using razor view engine?
View:
#{ var sessionVar = Session["key"]; //it's object }
Controller:
public ActionResult Method() {
var sessionVar = this.Session["..."]; //
}
the common way to call this object is: HttpContext.Current.Session
I don't know what you meant by 'common'. the provided session object is common for user session no matter where you will call for it.
But in fact you shouldn't try to use session it's ugly - try to do some search about ViewBag / ViewData and then try to search why you shouldn't use them as well. :)
Am new to MVC but I think I know what you're trying to do. You don't need to create a 'common' method to achieve that. #trn Solution should work but it might throw Null reference exceptions if accessed in "View" without using "HttpContext.Current.Session" object for two possible reasons:
Session["key"] is null.
incorrect reference to Session["key"] in View and/or Controller.
Using "HttpContext.Current.Session" object is great way to achieve what you're looking for and make sure the Session["key"] is not null. For example:
View:
#{
var sessionVar = Session["key"]; // might throw a NullReferenceException
}
Alternative for View:
#{
var sessionVar = HttpContext.Currrent.Session["key"]; // assuming correct reference to Session["key"]
}
Controller:
Public ActionResult someMethod()
{
var sessionVar = System.Web.HttpContext.Current.Session["key"];
}

How to call ValidationAttributes manually? (DataAnnotations and ModelState)

We have a need within some of our logic to iterate through the properties of a model to auto-bind properties and want to extend the functionality to include the new dataannotations in C# 4.0.
At the moment, I basically iterate over each property loading in all ValidationAttribute instances and attempting to validate using the Validate/IsValid function, but this doesn't seem to be working for me.
As an example I have a model such as:
public class HobbyModel
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Do not allow empty strings")]
[DisplayName("Hobby")]
[DataType(DataType.Text)]
public string Hobby
{
get;
set;
}
}
And the code to check the attributes is:
object[] attributes = propertyInfo.GetCustomAttributes(true);
TypeConverter typeConverter =
TypeDescriptor.GetConverter(typeof(ValidationAttribute));
bool isValid = false;
foreach (object attr in attributes)
{
ValidationAttribute attrib = attr as ValidationAttribute;
if (attrib != null)
{
attrib.Validate(obj, propertyInfo.Name);
}
}
I've debugged the code and the model does have 3 attributes, 2 of which are derived from ValidationAttribute, but when the code passes through the Validate function (with a empty or null value) it does thrown an exception as expected.
I'm expecting I'm doing something silly, so am wondering whether anyone has used this functionality and could help.
Thanks in advance,
Jamie
This is because you are passing the source object to the Validate method, instead of the property value. The following is more likely to work as expected (though obviously not for indexed properties):
attrib.Validate(propertyInfo.GetValue(obj, null), propertyInfo.Name);
You would certainly have an easier time using the Validator class as Steven suggested, though.
You do use the System.ComponentModel.DataAnnotations.Validator class to validate objects.

Trouble passing complex data between view and controller in ASP.NET MVC

Here's a simplification of my real models in ASP.NET MVC, that I think will help focus in on the problem:
Let's say I have these two domain objects:
public class ObjectA
{
public ObjectB ObjectB;
}
public class ObjectB
{
}
I also have a view that will allow me to create a new ObjectA and that includes selecting one ObjectB from a list of possible ObjectBs.
I have created a new class to decorate ObjectA with this list of possibilities, this is really my view model I guess.
public class ObjectAViewModel
{
public ObjectA ObjectA { get; private set; }
public SelectList PossibleSelectionsForObjectB { get; private set; }
public ObjectAViewModel(ObjectA objectA, IEnumerable<Location> possibleObjectBs)
{
ObjectA = objectA;
PossibleSelectionsForObjectB = new SelectList(possibleObjectBs, ObjectA.ObjectB);
}
}
Now, what is the best way to construct my view and controller to allow a user to select an ObjectB in the view, and then have the controller save ObjectA with that ObjectB selection (ObjectB already exists and is saved)?
I tried creating a strongly-typed view of type, ObjectAViewModel, and binding a Html.DropDownList to the Model.PossibleSelectionsForObjectB. This is fine, and the I can select the object just fine. But getting it back to the controller is where I am struggling.
Attempted solution 1:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ObjectAViewModel objectAViewModel)
This problem here is that the objectAViewModel.ObjectA.ObjectB property is null. I was thinking the DropDownList which is bound to this property, would update the model when the user selected this in the view, but it's not for some reason.
Attempted solution 2:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ObjectA objectA)
This problem here is that the ObjectA.ObjectB property is null. Again, I thought maybe the DropDownList selection would update this.
I have also tried using the UpdateModel method in each of the above solutions, with no luck. Does anyone have any ideas? I'm guessing I'm missing a binding or something somewhere...
Thanks!
I use code as follows:
[HttpPost]
public ActionResult Create([Bind(Exclude = "Id")]ObjectA objectAToCreate)
{
try
{
Repository.AddObjectA(objectAToCreate);
return RedirectToAction("Details", new { id = objectAToCreate.Id });
}
catch
{
return View();
}
}
With the following code in a Repository (Entity Framework specific):
public void AddObjectA(ObjectA objectAToAdd)
{
objectAToAdd.ObjectB = GetObjectB(objectAToAdd.ObjectB.Id);
_entities.AddToObjectAs(objectAToAdd);
_entities.SaveChanges();
}
public void GetObjectB(int id)
{
return _entities.ObjectBs.FirstOrDefault(m => m.id == id);
}
As per your commments, it is essentially reloading the object from the underlying data service, however I didn't find the need to use the ModelState to access the attempted value.
This is based on a view coded along these lines:
<p>
<%= Html.LabelFor( f => f.ObjectB.Id) %>
<%= Html.DropDownList("ObjectB.Id", new SelectList((IEnumerable)ViewData["ObjectBList"], "Id", "Descriptor"),"") %>
<%= Html.ValidationFor( f => f.ObjectB, "*") %>
</p>
Note that this could be improved to use a strongly typed ViewModel (which I believe you already do) and also to create a custom Editor Template for ObjectB such that the call could be made using:
<%= Html.EditorFor( f => f.ObjectB ) %>
After some more research it doesn't look like this is a case ASP.NET MVC will take care of for me. Perhaps there is a data service binding model I can use (so MVC would automatically grab the appropriate object out of memory, based on what was selected in the dropdown), but for now, I can fix this by handling it in the controller:
Get the selected item from the dropdown using Controller.ModelState
Reload that ObjectB from the underlying data service
Assign that ObjectB to ObjectA.ObjectB
Save ObjectA
So my controller method looks like this now:
Edited based on the comment from LukLed
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ObjectA objectA, string objectBStr)
{
ObjectB objectB = _objBService.Get(objectBStr);
objectA.ObjectB = objectB;
_objAService.Save(objectA);
return RedirectToAction("Details", new { id = objectA.Id });
}

ASP.NET - Avoid hardcoding paths

I'm looking for a best practice solution that aims to reduce the amount of URLs that are hard-coded in an ASP.NET application.
For example, when viewing a product details screen, performing an edit on these details, and then submitting the changes, the user is redirected back to the product listing screen. Instead of coding the following:
Response.Redirect("~/products/list.aspx?category=books");
I would like to have a solution in place that allows me to do something like this:
Pages.GotoProductList("books");
where Pages is a member of the common base class.
I'm just spit-balling here, and would love to hear any other way in which anyone has managed their application redirects.
EDIT
I ended up creating the following solution: I already had a common base class, to which I added a Pages enum (thanks Mark), with each item having a System.ComponentModel.DescriptionAttribute attribute containing the page's URL:
public enum Pages
{
[Description("~/secure/default.aspx")]
Landing,
[Description("~/secure/modelling/default.aspx")]
ModellingHome,
[Description("~/secure/reports/default.aspx")]
ReportsHome,
[Description("~/error.aspx")]
Error
}
Then I created a few overloaded methods to handle different scenarios. I used reflection to get the URL of the page through it's Description attribute, and I pass query-string parameters as an anonymous type (also using reflection to add each property as a query-string parameter):
private string GetEnumDescription(Enum value)
{
Type type = value.GetType();
string name = Enum.GetName(type, value);
if (name != null)
{
FieldInfo field = type.GetField(name);
if (field != null)
{
DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attr != null)
return attr.Description;
}
}
return null;
}
protected string GetPageUrl(Enums.Pages target, object variables)
{
var sb = new StringBuilder();
sb.Append(UrlHelper.ResolveUrl(Helper.GetEnumDescription(target)));
if (variables != null)
{
sb.Append("?");
var properties = (variables.GetType()).GetProperties();
foreach (var property in properties)
sb.Append(string.Format("{0}={1}&", property.Name, property.GetValue(variables, null)));
}
return sb.ToString();
}
protected void GotoPage(Enums.Pages target, object variables, bool useTransfer)
{
if(useTransfer)
HttpContext.Current.Server.Transfer(GetPageUrl(target, variables));
else
HttpContext.Current.Response.Redirect(GetPageUrl(target, variables));
}
A typical call would then look like so:
GotoPage(Enums.Pages.Landing, new {id = 12, category = "books"});
Comments?
I'd suggest that you derive your own class ("MyPageClass") from the Page class and include this method there:
public class MyPageClass : Page
{
private const string productListPagePath = "~/products/list.aspx?category=";
protected void GotoProductList(string category)
{
Response.Redirect(productListPagePath + category);
}
}
Then, in your codebehind, make sure that your page derives from this class:
public partial class Default : MyPageClass
{
...
}
within that, you can redirect just by using:
GotoProductList("Books");
Now, this is a bit limited as is since you'll undoubtedly have a variety of other pages like the ProductList page. You could give each one of them its own method in your page class but this is kind of grody and not smoothly extensible.
I solve a problem kind of like this by keeping a db table with a page name/file name mapping in it (I'm calling external, dynamically added HTML files, not ASPX files so my needs are a bit different but I think the principles apply). Your call would then use either a string or, better yet, an enum to redirect:
protected void GoToPage(PageTypeEnum pgType, string category)
{
//Get the enum-to-page mapping from a table or a dictionary object stored in the Application space on startup
Response.Redirect(GetPageString(pgType) + category); // *something* like this
}
From your page your call would be: GoToPage(enumProductList, "Books");
The nice thing is that the call is to a function defined in an ancestor class (no need to pass around or create manager objects) and the path is pretty obvious (intellisense will limit your ranges if you use an enum).
Good luck!
You have a wealth of options availible, and they all start with creating a mapping dictionary, whereas you can reference a keyword to a hard URL. Whether you chose to store it in a configuration file or database lookup table, your options are endless.
You have a huge number of options available here. Database table or XML file are probably the most commonly used examples.
// Please note i have not included any error handling code.
public class RoutingHelper
{
private NameValueCollecton routes;
private void LoadRoutes()
{
//Get your routes from db or config file
routes = /* what ever your source is*/
}
public void RedirectToSection(string section)
{
if(routes == null) LoadRoutes();
Response.Redirect(routes[section]);
}
}
This is just sample code, and it can be implemented any way you wish. The main question you need to think about is where you want to store the mappings. A simple xml file could do it:
`<mappings>
<map name="Books" value="/products.aspx/section=books"/>
...
</mappings>`
and then just load that into your routes collection.
public class BasePage : Page
{
public virtual string GetVirtualUrl()
{
throw new NotImplementedException();
}
public void PageRedirect<T>() where T : BasePage, new()
{
T page = new T();
Response.Redirect(page.GetVirtualUrl());
}
}
public partial class SomePage1 : BasePage
{
protected void Page_Load()
{
// Redirect to SomePage2.aspx
PageRedirect<SomePage2>();
}
}
public partial class SomePage2 : BasePage
{
public override string GetVirtualUrl()
{
return "~/Folder/SomePage2.aspx";
}
}

Proper way of using FormCollection in ASP.NET MVC2 Create Method?

I am currently developing an application with the new ASP.NET MVC2 framework. Originally I started writing this application in the ASP.NET MVC1 and I'm basically just updating it to MVC2.
My problem here is, that I don't really get the concept of the FormCollection object vs. the old Typed object.
This is my current code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
try
{
Member member = new Member();
member.FirstName = collection["FirstName"];
member.LastName = collection["LastName"];
member.Address = collection["Address"];
// ...
return RedirectToAction("Details", new { id = member.id });
}
catch
{
return View("Error");
}
}
This is the Code from the MVC1 application:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Member member)
{
try
{
memberRepository.Add(member);
memberRepository.Save();
return RedirectToAction("Details", new { id = member.id });
}
catch
{
}
return View(new MemberFormViewModel(member, memberRepository));
}
What are the benefits of switching to FormCollection in MVC2 and more importantly - how is it used properly?
You had the FormCollection object in v1 as well. But it is more preferred to use a typed object. So if you are already doing that, then continue doing so.
By using FormCollection, you wind up manually matching your post data or query string key/values into values to use in your code using string typing (resulting in stringly-typed code), when instead the built-in Model Binding can do this for you if you use form models, aka "typed objects."
I think by using the FormCollection, you would probably also lose the ability to use the handy Data Annotation (slash Validation) attributes on your model objects as well, which are designed for use with typed object model binding.
Additionally, unit testing can become much more cumbersome once you start touching your controller.Request.Form. You might find yourself having to mock and setup an HttpContextBase, and an HttpRequestBase just to have that mock request's .Form property return the NameValueCollection that you are wanting your test to see. Contrast this to letting model binding do the work for you such as:
// Arrange
var myModel = new MyModel( Property1 = "value1", Property2 = "value2");
// Act
var myResult = myController.MyActionMethod(myModel);
// Assert
// whatever you want the outcome to be
In summary, I would recommend against using FormCollection to the maximum extent possible.

Resources