Can ASP.NET MVC Html Helpers work with descendant classes? - asp.net-mvc-3

If a partial view is based upon a base class, is it possible to check if it is a descendant class and if so, use the descndant class' properties within the Html helpers (LabelFor, EditorFor etc.)?
#model ProjectX.Models.VehicleModel
<div>
#Html.LabelFor(model => model.Fuel)
#Html.TextBoxFor(model => model.Fuel)
</div>
#{
if (Model is CarModel)
{
CarModel car = (CarModel)Model;
#Html.LabelFor(car => car.Doors)
#Html.TextBoxFor(car => car.Doors)
}
}

Yes its possible; try this ...
Model classes
namespace MvcApplication2.Models
{
public class Vehicle
{
public string Fuel { get; set; }
}
public class Car : Vehicle
{
public int Doors { get; set; }
}
}
View
#model MvcApplication2.Models.Vehicle
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div>
#Html.LabelFor(model => model.Fuel)
#Html.TextBoxFor(model => model.Fuel)
</div>
#{
if (Model is MvcApplication2.Models.Car)
{
#Html.LabelFor(model => ((MvcApplication2.Models.Car)model).Doors)
#Html.TextBoxFor(model => ((MvcApplication2.Models.Car)model).Doors)
}
}
Hope this helps.

If your question is whether you can use descendants of Models with #Html, then I see no reason why not. The code you provide should work.

Related

MVC4: ViewModel (with radiobuttonlist) is empty after HttpPost

I'm having a hard time getting the values from a small multiple choice questionnaire posted to the Controller in my MVC4 app:
The model looks like this:
public class Evaluation
{
public int Id { get; set; }
public IEnumerable<MultipleChoiceQuestion> Question { get; set; }
public Remark Rem { get; set; }
}
public class MultipleChoiceQuestion
{
public int Id { get; set; }
public string Question { get; set; }
public MultipleChoiceAnswer Answer { get; set; }
}
public enum MultipleChoiceAnswer
{
DISAGREE,
NEUTRAL,
AGREE,
NA,
}
This is the View (leaving out some markup):
#model Models.Evaluation
#using (Html.BeginForm("EvaluationB", "Evaluation", FormMethod.Post))
{
#foreach (var item in Model.Question)
{
#Html.DisplayFor(model => item.Question)
#Html.EditorFor(model => item.Question, "Enum_RadioButtonList", new { Id = item.Id })
}
#Html.Label("Remark")
#Html.TextAreaFor(model => model.Rem)
<input type="submit" value="Next" />
}
The "Enum_RadioButtonList" is a View a grabbed from here: https://gist.github.com/973482. It seems like the best way to show enum values in a radiobuttonlist (tho their should be an easier way in MVC 4)
The Controller looks like this:
public ActionResult EvaluationA()
{
Models.Evaluation evm = new Models.Evaluation();
evm.Question = db.MultipleChoiceQuestions.ToList(); //feeding the View some predefined questions
return View(evm);
}
public ActionResult EvaluationB(Models.Evaluation ev)
{
if (ModelState.IsValid)
{
// TODO: save model
return View("EvaluationB", evm);
}
return View("EvaluationA", ev);
}
The questions are loaded fine in the View, but for some reason, the model posted to the Controller remains empty after an HttpPost, and i don't understand why.
I did not see the form have mapping for the Id like below:
#Html.HiddenFor(model => model.Id)
Each of the inner collection of question should also have Id so that it is posted along with the form. So inside the foreach loop in your form for each question you can have:
#Html.HiddenFor(model => item.Question.Id)
Also the model has a collection of type MultipleChoiceQuestion. For model binding to the collection the name of the collection elements should have name attribute with ordered numbers as explained in this post http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Ive done some more research stumbled upon this post:
http://dotnetslackers.com/articles/aspnet/Understanding-ASP-NET-MVC-Model-Binding.aspx
The problem was in the naming of the html fields. Ive added my View like this and now i can read out the values in the Controller correctly:
#for (int i = 0; i < 6; i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.Question[i].Question)
</td>
<td class="mult_question">
#Html.EditorFor(m => m.Question[i].Answer, "Enum_RadioButtonList" )
</td>
</tr>
}
<tr>
<td>
#Html.Label("Remark")
#Html.TextAreaFor(m => m.Remark)

Can the properties of a model be accessed indirectly in a Razor view?

Can the properties of a model be accessed indirectly in a Razor view?
So instead of:
#Html.LabelFor(model => model.ColA)
#Html.LabelFor(model => model.ColB)
#Html.LabelFor(model => model.ColC)
Is something like the following possible?
#foreach (var col in Model.Columns)
{
#Html.LabelFor(model => model[col])
}
I should qualify that the model to be used will be an EF model:
public class Record
{
[Key, Display(Name="Column A")]
public string ColA { get; set; }
[Display(Name="Column B")]
public string ColB { get; set; }
[Display(Name="Column C")]
public string ColC { get; set; }
}
public class RecordDbContext : DbContext
{
public DbSet<Record> Records { get; set; }
}
How might I implement 'Columns' to show the display name for the property?
If Model.Columns is a list that would almost work. You would need to change it to this though:
#foreach (var col in Model.Columns)
{
#Html.LabelFor(model => col)
}
Seems like your view model is not adapted to the requirements of your view (which is to loop through some values and display labels about them). You could use a collection in your view model instead of properties as it will allow you to loop:
public IEnumerable<SomeModel> Columns { get; set; }
Then in your view instead of writing a loop you could use a display template:
#model MyViewModel
#Html.DisplayFor(x => x.Columns)
and then define the corresponding template which will automatically be rendered for each element of the Columns collection (~/Views/Shared/DisplayTemplates/SomeModel.cshtml):
#model SomeModel
#Html.LabelFor(x => x.SomeModelProperty)

MVC3 view not updating using EF 4.1

I am having a problem updating a record. For some reason it is not even hitting the post action in the controller and just returning:
"An item with the same key has already been added."
It seems to be behaving as if it is doing an insert rather than an update. I would appreciate a new set of eyes on this. It is probably something very simple that I have missed.
Controller:
// GET: /Manage/Regions/Edit/5
public ActionResult Edit(int id)
{
Region_CU regionEdit = (from r in db.Venues_Regions
where r.RegionsID == id
select new Region_CU
{
RegionsID = r.RegionsID,
Name = r.Name,
CalendarLink = r.CalendarLink,
MapIcon = r.MapIcon,
QtrStart = r.QtrStart,
QtrEnd = r.QtrEnd,
FacebookLikeBox = r.FacebookLikeBox,
FacebookId = r.FacebookId
// region = r
}).Single();
return View(regionEdit);
}
//
// POST: /Manage/Regions/Edit/5
[HttpPost]
public ActionResult Edit(Region_CU r)
{
if (ModelState.IsValid)
{
var v = db.Venues_Regions.First(i => i.RegionsID == r.RegionsID);
//v.RegionsID = r.RegionsID;
v.Name = r.Name;
v.CalendarLink = r.CalendarLink;
v.MapIcon = r.MapIcon;
v.QtrStart = r.QtrStart;
v.QtrEnd = r.QtrEnd;
v.FacebookLikeBox = r.FacebookLikeBox;
v.FacebookId = r.FacebookId;
//Venues_Regions v = new Venues_Regions
//{
// RegionsID = r.RegionsID,
// Name = r.Name,
// CalendarLink = r.CalendarLink,
// MapIcon = r.MapIcon,
// QtrStart = r.QtrStart,
// QtrEnd = r.QtrEnd,
// FacebookLikeBox = r.FacebookLikeBox,
// FacebookId = r.FacebookId
//};
//db.Venues_Regions.Attach(v);
//db.ObjectStateManager.ChangeObjectState(v, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(r);
}
View :
#model THPT_Razor.Areas.Manage.Models.Region_CU
#{
ViewBag.Title = "Edit Region";
}
<h2>Edit</h2>
<link href="#Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="stylesheet" type="text/css" />
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script>
$(document).ready(function () { $('.date').datepicker({ dateFormat: "mm/dd/yy" }); });
</script>
#using (Html.BeginForm()) {
#* #Html.ValidationSummary(true)*#
<fieldset>
<legend>#Html.DisplayFor(model => model.Name)</legend>
#Html.HiddenFor(model => model.RegionsID)
#Html.HiddenFor(model => model.Name)
#Html.HiddenFor(model => model.CalendarLink)
<div class="editor-label">
#Html.Label("Map Icon")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.MapIcon, new SelectList(Model.mapicons,"id","Description"))
</div>
<div class="editor-label">
#Html.Label("Quarter Start")
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.QtrStart, new { #class = "date" })
#Html.ValidationMessageFor(model => model.QtrStart)
</div>
<div class="editor-label">
#Html.Label("Quarter End")
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.QtrEnd, new { #class = "date" })
#Html.ValidationMessageFor(model => model.QtrEnd)
</div>
<div class="editor-label">
#Html.Label("Region Facebook ID")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FacebookId)
#Html.ValidationMessageFor(model => model.FacebookId)
</div>
<div class="editor-label">
#Html.Label("Region Facebook LikeBox Code")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FacebookLikeBox)
#Html.ValidationMessageFor(model => model.FacebookLikeBox)
</div>
<p>
<input type="submit" value="Update" />
</p>
</fieldset>
Edit:
I have received several good suggestions but I guess I have not been clear. Region_CU is not an Entity. Venues_Regions is what I am trying to update. see the comments in the class below for clarification. The original objective was to build a simple wrapper that had the Venues_Regions object and a list object for the map icons. However, the data annotation for the field likebox was not being passed through resulting in the the Venues_Regions object to be broken out. Now when I try to save the update it is not even hitting the http post action. I hope this clears up what I am trying to accomplish and asking for help with. Thanks again for all the help and quick responses.
//create and update
public class Region_CU
{
public Region_CU()
{
}
public List<MapIcon> mapicons { get; set; }
//public Venues_Regions region { get; set; }
// The fields below are what makes up Veunes_Region
// this was broken out from the above Venues_Region
// because the UIHint was not being passed through
public int RegionsID { get; set; }
public string Name { get; set; }
public string CalendarLink { get; set; }
public int MapIcon { get; set; }
public DateTime? QtrStart { get; set; }
public DateTime? QtrEnd { get; set; }
[UIHint("tinymce_jquery_full"), AllowHtml]
public string FacebookLikeBox { get; set; }
public string FacebookId { get; set; }
public string mapIcon { get; set; }
}
Edit #2:
After a good nights sleep the solution presented itself. In the update action all I needed to do was change from the wrapper being passed in to the Venues_Region object being passed in and now everything works.
Thanks for all the help and suggestions.
Thanks in advance for the help,
Chris
Actually all you need to do is load the venues and call TryUpdateModel. You don't even need to pass in the object. Then save the venue. Another approach is to use automapper to copy the fields between objects or use the attach method as mentioned but either way no manual field copying is required.

Html Helper for IEnumerable<T> collection

I have two classes MyClassA and MyClassB:
public class MyClassA
{
public int Age { get; set; }
public string Name { get; set; }
}
public class MyClassB
{
public IEnumerable<MyClassA> Data { get; set; }
}
Now, I want to create custom strongly typed html helper to generate textboxes with names from collection MyClassA, something like this:
#model MyClassB
#Html.MyTextBoxFor(p => p.MapFrom(o => o.Age))
#Html.MyTextBoxFor(p => p.MapFrom(o => o.Name))
... with the output:
<input type="text" name="Age" />
<input type="text" name="Name" />
How can I accomplish this?
PS. I know, I can write sth like this:
#Html.TextBoxFor(p => p.Data.First().Name)
but it feels so wrong and inelegant...
Any ideas?
Let me make sure I understand this correctly... you want to create a textbox for Name and Age for each MyClassA in Data property of MyClassB. If so, then editor templates to the rescue.
Create /Views/Shared/EditorTemplates/MyClassA.cshtml
#model MyClassA
#Html.TextBoxFor(m => m.Name)
#Html.TextBoxFor(m => m.Age)
then in you view:
#model MyClassB
#Html.EditorFor(m => m.Data)

MVC3 Entity Framework 4.1RC how does #Html.DropDownListFor actually work?

OK, I've been over Google and StackOverflow and ASP.net - am I really the only person who doesn't get this?
Rough problem = I have an Employee entity that references an Office entity. I was previously able to create new employees (beat me with a hammer for forgetting how that code worked) but now I just can't create an employee nor edit an existing one.
Now, here's what I learned;
1) Make sure you add the Offices list to the ViewBag at every step
2) That includes a failed POST/edit; call the function to re-populate the ViewBag with the Offices list
3) I think(!!) that you always want to set the Employee.Office, not the Employee.Office.OfficeID; the latter leads to "is part of the object's key information and cannot be modified" errors
So, what I have is;
A controller that has the following method;
private void AddOfficesToViewBag()
{
Dictionary<string, Office> list = new Dictionary<string, Office>();
foreach (Office office in company.GetAllOffices())
list.Add(office.ToString(), office);
SelectList items = new SelectList(list, "Value", "Key");
ViewBag.OfficeList = items;
}
Create pair looking like;
public ActionResult Create()
{
if (company.Offices.Count() < 1)
return RedirectToAction("Create", "Office", (object) "You need to create one or more offices first");
AddOfficesToViewBag();
return View(new Employee());
}
//
// POST: /Employee/Create
[HttpPost]
public ActionResult Create(Employee emp)
{
if (TryUpdateModel<Employee>(emp))
{
company.Employees.Add(emp);
company.SaveChanges();
return RedirectToAction("Index");
}
else
{
AddOfficesToViewBag();
return View(emp);
}
}
and an Edit pair that looks like;
public ActionResult Edit(int id)
{
Employee emp = company.Employees.Single(e => e.EmployeeID == id);
AddOfficesToViewBag();
return View(emp);
}
//
// POST: /Employee/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Employee emp = company.Employees.Single(e => e.EmployeeID == id);
if (TryUpdateModel(emp))
{
company.SaveChanges();
return RedirectToAction("Index");
}
else
{
AddOfficesToViewBag();
return View(emp);
}
}
I'll pick the Edit View, which is pretty much the same as the Create View;
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
Employee
#Html.HiddenFor(model => model.EmployeeID)
<div class="editor-label">
#Html.LabelFor(model => model.Office)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Office, (SelectList) ViewBag.OfficeList)
#Html.ValidationMessageFor(model => model.Office)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Age)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Age)
#Html.ValidationMessageFor(model => model.Age)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
I would say that the Edit, in particular, looks almost there. It manages to bind to the Employee object passed in and sets the dropdown to the appropriate entry.
Viewing the original HTML source shows that the output value is the Office.ToString() value.
The odd thing to me is that some magic is happening that binds Employee->Office to the correct entry, which makes the Edit view work, but there is no corresponding conversion of the selected item (a string, aka object->ToString()) to the original list.
This seems so basic (MVC / EF4 / DropDownList) that I feel I'm missing something incredibly fundamental.
All thoughts appreciated.
Regards
Scott
Based on the following you can
http://forums.asp.net/t/1655622.aspx/1?MVC+3+Razor+DropDownListFor+and+Model+property+from+EFCodeFirst
Do the following:
[HttpPost]
public ActionResult Edit(Guid id, FormCollection collection)
{
CollectionViewModel cvc = new CollectionViewModel();
cvc.Collection = _db.Collections.Where(c => c.CollectionId == id).Include("CollectionType").First();
Guid collectionTypeId = Guid.Parse(collection["CollectionTypeId"].ToString());
cvc.Collection.CollectionType =_db.CollectionTypes.Where(ct =>ct.CollectionTypeId == collectionTypeId).First();
if (TryUpdateModel(cvc))
{
_db.SaveChanges();
return RedirectToAction("Index");
}
}
ViewModel
public class CollectionViewModel
{
public Collection Collection {get; set; }
public Guid CollectionTypeId { get; set; }
public SelectList CollectionTypes { get; set; }
}

Resources