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)
Related
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)
I am using ASP.NET MVC 3 TextBoxFor in a form and would like to use type="email" for easier input for at least some mobile devices but cannot find how to set it using TextBoxFor. Is this not easily possible?
In View
#Html.LabelFor(m => m.Email)
#Html.TextBoxFor(m => m.Email)
In model
[StringLength(50)]
public string Email { get; set; }
(Already using a data annotation to protect size constraint in DB)
Try to use
#Html.TextBoxFor(m => m.Email, new { #type = "email" })
http://msdn.microsoft.com/en-us/library/ee703538.aspx (htmlAttributes)
You're using it the wrong way.
Use EditorFor in View:
#Html.LabelFor(m => m.Email)
#Html.EditorFor(m => m.Email)
In model, add the [DataType( DataType.EmailAddress )] Data Annotations property:
[DataType( DataType.EmailAddress )]
public string Email { get; set; }
Try adding [DataType( DataType.EmailAddress )] to the email property.
[DataType( DataType.EmailAddress )]
[StringLength(50)]
public string Email { get; set; }
[StringLength(50)]
[DataType(DataType.EmailAddress, ErrorMessage = "Invalid Email Address")]
public string EmailAddress { get; set; }
Try to add this. I think it works.
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)
I have a table where each tr is grouped by having their input elements name set to a value that is unique for each row.
For example,
<td>
<input data-field="opps" type="text" value="somevalue" name="#item.Code" />
</td>
<td>
<input data-field="asc" type="text" value="somevalue2" name="#item.Code" />
</td>
On POST
[HttpPost]
public ActionResult Update(FormCollection collection)
{
try
{
//doin work on collection...order assumed static
return RedirectToAction("Index");
}
catch
{
return View();
}
}
my System.Web.MVC.FormCollection is grouped in the same order I define the <td>. I don't like to assume order, but without access to my data-field, I'm not sure what else I can do (maybe I could append the data-field value as a prefix to the name and put it all together with a custom collection and Regex..but that seems nutty).
Is there a way to access the data-field? This way I'm not fragile to re-ordering or adding new columns in the View.
Let's say you have a class (model) defined like this:
public class MyModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
In you controller, you might have an action called Create, like so:
[HttpGet]
public ViewResult Create()
{
MyModel sampleModel = new MyModel();
return View(sampleModel);
}
[HttpPost]
public ActionResult Create(MyModel sampleModel)
{
if (ModelState.IsValid)
{
TempData["Error"] = "There were errors. Please correct the problem and submit again";
return View(sampleModel);
}
// At this point everything is fine and you can access data in your sampleModel
if (sampleModel.Age >= 16)
{
return RedirectToAction("LegalAccess");
}
else
{
TempData["Error"] = "You must be 16 or over to access this site";
return RedirectToAction("AgeRestriction");
}
}
When you create a strongly typed view that uses MyModel as model you might define it something like this:
#model MyModel
#{
Layout = "~/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
<br />
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<br />
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<input type="submit" value="Submit" />
}
When you submit this form, the model binder will in the background copy data from this form using Request.Form into an object of type MyModel which it creates in the background. This new object is passed to an action that handles HTTP POST method. With this you get strongly typed object and you don't have to worry about the order of items in FormCollection.
I hope I helped answer your question.
BTW. I wrote this without Visual Studio, so I hope there are not errors. :-)
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.