asp.net mvc ViewData - asp.net-mvc-2

I'm having some problems with parsing in some data to a PartialView.
When parsing in a Dictionary the properties Values and Keys is set in the ViewData ...
How can I merge the Dictionary with the ViewData ... so I can access my Dictionary items with the Keys like this:
ViewData["key"] as IList<T>;
Instead of
ViewData["Values] <- Which is a List that Contains my list.
I'm going to use it like this ... just dont want anonymous/magic string names.
<%: Html.EditorFor(x => x.GroupId, "SimpleSelectList", new { Selected = 10}) %>
I'm hoping to do something like this.
<%: Html.EditorFor(x => x.GroupId, "SimpleSelectList", Html.AddViewData(Model.List)) %>
With this extension method:
public static IDictionary AddViewData<T>(this HtmlHelper helper, T item)
{
var dictionary = new Dictionary<string, object>();
dictionary.Add(typeof(T).Name, item);
return dictionary;
}
Then I will always know what the SimpleSelectList template should look for ... and dont have to depends on yet again another magic string ...
Or how do people do this? Just trying to get into the code base and how people do this kind of thing ...

Personally I wouldn't use ViewData atall in this context.
It's a lot cleaner and clearer to have a view model that impliments your dictionary as a property. You can then pass this view model to your view, and to your partial view...or just part of the view model to your partial view (depending on what data you need).

Related

ASP .NET MVC 2 - How do I pass an object from View to Controller w/ Ajax?

I have an object MainObject with a list of objects, SubObjects, among other things. I am trying to have the user click a link on the View to add a new SubObject to the page. However, I am unable to pass the MainObject I am working with into the Action method. The MainObject I currently receive is empty, with all its values set to null. How do I send my controller action the MainObject that was used to render the View originally?
The relevant section of the view looks like this:
<div class="editor-list" id="subObjectsList">
<%: Html.EditorFor(model => model.SubObjects, "~/Views/MainObject/EditorTemplates/SubObjectsList.ascx")%>
</div>
<%: Ajax.ActionLink("Add Ajax subObject", "AddBlanksubObjectToSubObjectsList", new AjaxOptions { UpdateTargetId = "subObjectsList", InsertionMode = InsertionMode.Replace })%>
The relevant function from the controller looks like this:
public ActionResult AddBlanksubObjectToSubObjectsList(MainObject mainobject)
{
mainobject.SubObjects.Add(new SubObject());
return PartialView("~/Views/MainObject/EditorTemplates/SubObjectsList.acsx", mainobject.SubObjects);
}
I ended up with the following:
View:
<div class="editor-list" id="subObjectsList">
<%: Html.EditorFor(model => model.SubObjects, "~/Views/MainObject/EditorTemplates/SubObjectsList.ascx")%>
</div>
<input type="button" name="addSubObject" value="Add New SubObject" onclick="AddNewSubObject('#SubObjectList')" />
Control:
public ActionResult GetNewSubObject()
{
SubObject subObject= new SubObject();
return PartialView("~/Views/TestCase/EditorTemplates/SubObject.ascx", subObject);
}
And, finally, I added this JQuery script:
function AddNewSubObject(subObjectListDiv) {
$.get("/TestCase/GetNewSubObject", function (data) {
//there is one fieldset per SubObject already in the list,
//so this is the index of the new SubObject
var index = $(subObjectListDiv + " > fieldset").size();
//the returned SubObject prefixes its field namess with "[0]."
//but MVC expects a prefix like "SubObjects[0]" -
//plus the index might not be 0, so need to fix that, too
data = data.replace(/name="\[0\]/g, 'name="SubObject[' + index + "]");
//now append the new SubObject to the list
$(subObjectListDiv).append(data);
});
}
If someone has a better way to do this than kludging the MVC syntax for nested objects onto a returned View using JQuery, please post it; I'd love to believe that there is a better way to do this. For now, I'm accepting my answer.

Read DataAnnotations from a collection of models in an MCV2 view

In my MVC2 AdminArea I'd like to create an overview table for each of my domain models.
I am using DataAnnotations like the following for the properties of those domain model objects:
[DisplayName("MyPropertyName")]
public string Name { get; set; }
Now my question is: How can I access the DisplayName Attribute if my view receives a collection of my domain models? I need this to build the table headers which are defined outside of the usual
<% foreach (var item in Model) { %>
loop. Inside this loop I can write
<%: Html.LabelFor(c => item.Name) %>
but is there any way to access this information using the collection of items instead of a concrete instance?
Thanks in advance!
There is a ModelMetaData class that has a static method called FromLambdaExpression. If you call it and pass in your property, along with your ViewData, it will return an instance of ModelMetaData. That class has a DisplayName property that should give you what you need. You can also get other meta data information from this object.
For example, you can create an empty ViewDataDictionary object to get this information. It can be empty because the ModelMetaData doesn't actually use the instance, it just needs the generic class to define the type being used.
//This would typically be just your view model data.
ViewDataDictionary<IEnumerable<Person>> data = new ViewDataDictionary<IEnumerable<Person>>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.First().Name, data);
string displayName = result.DisplayName;
The First() method call doesn't break even if you have no actual Person object because the lambda is simply trying to find the property you want the meta data about. Similarly, you could d this for a single Person object:
//This would typically be just your view model data.
ViewDataDictionary<Person> data = new ViewDataDictionary<Person>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.Name, data);
You could clean this up significantly with a helper or extension method, but this should put you on the right path.
Alright, I followed sgriffinusa's advise (thanks again!) and created a strongly typed HtmlHelper:
public static MvcHtmlString MetaDisplayName<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
return MvcHtmlString.Create(metadata.GetDisplayName());
}
Of course TModel still is a collection of domain models like stated in my inital question but we can call the helper in the view like this:
<%: Html.MetaDisplayName(p => p.First().Name) %>

Creating a generic edit view with ASP.NET, MVC, and Entity Framework

In my database, I have 40 tables that contain only an ID number and a name. My database is accessed using Entity Framework. While I have no trouble editing them each by generating a strongly-typed view and postback methods for each object, I would like to create a more generic method and view for viewing and editing these objects.
I am currently using the following code to access each object. In this case, it is for an object of 'AddressType':
public ActionMethod EditAddressType(int ID)
{
var result = database.AddressType.Single(a => a.ID == ID);
View(result);
}
[HttpPost]
public ActionMethod EditAddressType(int ID, FormCollection formValues)
{
var result = database.AddressType.Single(a => a.ID == ID);
UpdateModel(result);
database.SaveChanges();
return View("SaveSuccess");
}
The view 'EditAddressType' is strongly typed and works fine, but there's a lot of repeated code (one instance of this for each object). I've been told that I need to use reflection, but I'm at a loss for how to implement this. My understanding is that I need to retrieve the object type so I can replace the hardcoded reference to the object, but I'm not sure how to get this information from the postback.
I've had success binding the information to ViewData in the controller and passing that to a ViewPage view that knows to look for this ViewData, but I don't know how to postback the changes to a controller.
Thanks for any help you can give me!
If you are going to edit the object you don't need to refetch it from the database in your POST action. The first thing would of course be to abstract my data access code from the controller:
public class AddressesController: Controller
{
private readonly IAddressesRepository _repository;
public AddressesController(IAddressesRepository repository)
{
_repository = repository;
}
public ActionMethod Edit(int id)
{
var result = _repository.GetAddress(id);
return View(result);
}
[HttpPut]
public ActionMethod Update(AddressViewModel address)
{
_repository.Save(address);
return View("SaveSuccess");
}
}
You will notice that I have renamed some of the actions and accept verbs to make this controller a bit more RESTFul.
The associated view might look like this:
<% using (Html.BeginForm<AddressesController>(c => c.Update(null))) { %>
<%: Html.HttpMethodOverride(HttpVerbs.Put) %>
<%: Html.HiddenFor(model => model.Id) %>
<%: Html.TextBoxFor(model => model.Name) %>
<input type="submit" value="Save" />
<% } %>
As far as the implementation of this IAddressesRepository interface is concerned, that's totally up to you: Entity Framework, NHibernate, XML File, Remote Web Service call, ..., that's an implementation detail that has nothing to do with ASP.NET MVC.

Where and how to load dropdownlists used in masterpage

I'm new to MVC!
I am trying to use two DropDownLists (Cities, Categories) in a PartialView that will be used in MasterPage, meaning they will be visble all the time.
I tried to load them in HomeCOntroller, but that didn't work. I got an Exception.
I read something about making a baseController that the other controllers will inherit from, I have tried that, kind of, but I guess i'm doing something wrong.
This is the only code I got today:
Masterpage
<% Html.RenderPartial("SearchForm"); %>
PartialView (SearchForm.ascx)
<% using (Html.BeginForm("Search", "Search")) { %>
<% } %> // dont know why I need two BeginForms, if I dont have this the other form won't trigger at all! Weird!
<% using (Html.BeginForm("Search", "Search", FormMethod.Get)) { %>
<%= Html.DropDownList("SearchForm.Category", new SelectList(ViewData["Categories"] as IEnumerable, "ID", "Name", "--All categories--")) %>
<%= Html.DropDownList("Search.City", Model.Cities, "--All cities--") %>
<input name="search" type="text" size="16" id="search" />
<input type="submit" id="test" title="Search" />
<% } %>
Two question:
Where and how to load the DropDownLists is the problem. I have tried to load it in the HomeController, but when go to another page then it says that the DDLs is empty and I get a Excecption.
Why do I have to use two forms for the ActionMethod to trigger ?
Hope anyone can help me out!
It sounds like you're only setting the property for a single action result. The Model.Cities data will have to be populated for every single view that needs to use it.
One solution would be to move the population of it to an ActionFilter
public class CityListAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var result = filterContext.Result as ViewResult;
result.ViewData.Model = //populate model
base.OnActionExecuted(filterContext);
}
}
and then add the filter to your controller
[CityList]
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
}
As for the two forms issue, there should be no reason that i can think of that you need an empty form.
Take a look at the html that's being output and make sure it's ok. Also check the action is being generated correcly
Better way to do this, is to create something like MasterController and have action method on it like this:
[ChildActionOnly]
public ActionResult SearchForm()
{
//Get city data, category data etc., create SearchFormModel
return PartialView(model);
}
I recommend you create strongly typed view (SearchForms.ascx of type ViewUserControl<SearchFormModel>). Also it may be a good idea to have a model like this:
public class SearchViewModel
{
public IList<SelectListItem> Cities { get; set; }
public IList<SelectListItem> Categories { get; set; }
}
and use a helper like this: http://github.com/Necroskillz/NecroNetToolkit/blob/master/Source/NecroNet.Toolkit/Mvc/SelectHelper.cs to convert raw data to DDL friendly format beforehand.
In any case, you now use Html.RenderAction() instead of Html.RenderPartial() and specify you want "SearchForm" action from "MasterController".

Handling MVC2 variables with hyphens in their name

I'm working with some third-party software that creates querystring parameters with hyphens in their names. I was taking a look at this SO question and it seems like their solution is very close to what I need but I'm too ignorant to the underlying MVC stuff to figure out how to adapt this to do what I need. Ideally, I'd like to simply replace hyphens with underscores and that would be a good enough solution. If there's a better one, then I'm interested in hearing it.
An example of a URL I want to handle is this:
http://localhost/app/Person/List?First-Name=Bob&My-Age=3
with this Controller:
public ActionResult List(string First_Name, int My_Age)
{
{...}
}
To repeat, I cannot change the querystring being generated so I need to support it with my controller somehow. But how?
For reference, below is the custom RouteHandler that is being used to handle underscores in controller names and action names from the SO question I referenced above that we might be able to modify to accomplish what I want:
public class HyphenatedRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
return base.GetHttpHandler(requestContext);
}
}
Have you tried [Bind(Prefix="First-name")]? It might work...
One way would be with a custom model binder. Another way would be with an action filter. Use the model binder if you want to do this on a specific type. Use the action filter if you want to do this on a specific action or controller. So for the latter method you could do something like:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var keys = filterContext.HttpContext.Request.QueryString.AllKeys.Where(k => k.Contains('-'));
foreach(var k in keys)
{
filterContext.ActionParameters.Add(
new KeyValuePair<string, object>(
k.Replace('-', '_'), filterContext.HttpContext.Request.QueryString[k]));
}
base.OnActionExecuting(filterContext);
}
I had the same problem. In the end rather than doing something too complex I just get the query string parameters using
string First_Name = Request["First-Name"];
You may want to check for NUlls incase the parameter is not there, but this sorted it out for me. You can also include an optional parameter for the ActionResult for test purposes etc..