Html.Editor() helper in ASP.NET MVC 3 does not work as expected with array in model - asp.net-mvc-3

In my ASP.NET MVC 3 application I have classes like the following:
public class Localization<T>
{
public int VersionID { get; set; }
public T Value { get; set; }
...
}
public class Localizable<T>
{
public Localization<T>[] Name { get; set; }
...
}
Then, I have the following view:
#model dynamic
...
#for (int i = 0; i < VersionCount; i++)
{
...
#Html.Editor(string.Format("Name[{0}].Value", i))
...
}
Now, when I display this view, passing a subclass of Localizable<string> as the model, the textboxes for the strings are rendered, but they are empty. If I replace #Html.Editor(string.Format("Name[{0}].Value", i)) with #InputExtensions.TextBox(Html, string.Format("Name[{0}].Value", i), Model.Name[i].Value), the textboxes are correctly filled with values from the model. However, using TextBox instead of Editor is not an option for me, because I want to use different editor templates for different types of T. So, what am I doing wrong, or is it a bug in MVC, and is there any workaround?

you can use attribute UIHint("MyUIHintName").
public class Localizable<T>
{
[UIHint("MyUIHintName")]
public Localization<T>[] Name { get; set; }
...
}
Then you need to create folder Views/Shared/EditorTemplates/. Next you need create Razor View
Views/Shared/EditorTemplates/MyUIHintName.cshtml
In this view you can write logic for every type, for example:
#model dynamic
#if(ViewData.ModelMetadata.ModelType.Name=="string")
{
//Do something
}
#if(ViewData.ModelMetadata.ModelType.Name=="int")
{
//Do something
}

Related

How to update hierarchical ViewModel?

I am stuck with this problem.
I have a model AssessmentModel defined like this:
public class AssessmentModel
{
public Respondent Respondent { get; set; }
public List<CompetencyModel> Competencies { get; set; }
}
public class CompetencyModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ResultModel> Results { get; set; }
}
public class ResultModel
{
public int Id { get; set; }
public int Score { get; set; }
}
All I need is to set value to the Score property of ResultModel.
Score is the only editable property here.
And I have just 1 View only, this view has a #model List, it displays a list of CompetencyModel items with Edit button for each one.
When I click the Edit button, the Id of CompetencyModel is passed to the same View, and the View draws an Edit form for ResultModel items that belong to the selected CompetencyModel.
However the form for ResultModel items exists on the same View, and the model of the View is still #model List.
How can I get to the Score property by using bindable Html.EditorFor(m=>m.Score) helper for each ResultModel item?
The View is defined like this:
#model List<CompetencyModel>
#foreach(var comp in Model)
{
<p>#comp.Name</p>
Edit
}
In the controller I set ViewBag.CurrentId = comp.Id, and at the bottom of the View:
if(ViewBag.CurrentId != null) //draw a form for ResultModel items
{
// What should I do now?
// how cant I use Html.EditorFor(m=>...) if the Model is still List<CompetencyModel>
}
I need to get to a single ResultModel entity to set a value to a Score property.
Thank you.
You should be able to get this done using Linq. Consider having the following code segment in the your last if statement
var result = Model.Results.FirstOrDefault(r => r.Id == ViewBag.CurrentId);
I dont have a IDE with me, so watchout for syntext errors

How to keep a collection of items for dropdown list in MVC model?

to keep things simple, I have a model Survey with the following properties:
class SurveyItem {
public string Question { get; set; }
public string SelectedAnswerCode { get; set; }
public List<Answer> Answers { get; set; }
}
where Answer is like:
class Answer {
public int AnswerCode { get; set; }
public string AnswerText { get; set; }
}
Answers is used to build a dropdown listbox of possible answers for (a user selects one)
In my View I use a Model of IEnumerable
where for each question I have a list of answers to choose from.
I prefill this collection and pass to my View. When I click submit, it goes back to the controller for validation. If the model is not valid, I pass it to the same View for a user to fix his answers, like usual.
Question - Answers collection used for dropdown list is not preserved in the model when I submit. I use HiddenFor, EditorFor and DropDownListFor for single value properties, but, how do I keep a collection of possible answers in the Model?
P.S>
Thanks.
P.S. I am using single line code #Html.DropDownListFor to render the dropdown in my EditorTemplate:
#Html.DropDownListFor(model => model.SelectedAnswerCode,
new SelectList(Model.Answers, "AnswerCode", "AnswerText", 0))
You'll need to add virtual to the Answers declaration.
class SurveyItem {
public string Question { get; set; }
public string SelectedAnswerCode { get; set; }
public virtual List<Answer> Answers { get; set; }
}
This seems to do the trick: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
Basically in your view do something like this:
#for(int i = 0; i < Model.Answers.Count; i++)
{
#Html.Hidden(string.Format("Answers[{0}].AnswerCode", i), Model.Answers[i].AnswerCode)
#Html.Hidden(string.Format("Answers[{0}].AnswerText", i), Model.Answers[i].AnswerText)
#Html.RadioButton("SelectedAnswerCode", Model.Answers[i].AnswerCode)
#Model.Answers[i].AnswerText
}
EDIT:
Alternatively, you can create your own HtmlHelper extension. For example:
public static class CustomHtmlHelperExtensions
{
public static MvcHtmlString HiddenForSurveyAnswers(this HtmlHelper htmlHelper, IEnumerable<Models.Answer> answers)
{
var html = new StringBuilder();
int index = 0;
foreach (var answer in answers)
{
html.AppendLine(htmlHelper.Hidden(string.Format("Answers[{0}].AnswerCode", index), answer.AnswerCode).ToString());
html.AppendLine(htmlHelper.Hidden(string.Format("Answers[{0}].AnswerText", index), answer.AnswerText).ToString());
index++;
}
return MvcHtmlString.Create(html.ToString());
}
}
Then add an #using YourMvcApplicationNamespace to the top of the view and then use the extension like:
#Html.HiddenForSurveyAnswers(Model.Answers)
With MVC, you can write an Editor Template to preserve the Model.Answers as well :)
Save a view named Answer.chtml in \Views\Shared\EditorTemplates.
Add the following code:
#model Answer
#Html.HiddenFor(item => item.AnswerCode)
#Html.HiddenFor(item => item.AnswerText)
Then in you original view, add it:
#Html.EditorFor(model => model.Answers)
#Html.DropDownListFor(model => model.SelectedAnswerCode, new SelectList(Model.Answers, "AnswerCode", "AnswerText", 0))
In this manner you don't have to worry about writing foreach statements or worry about their ids.
Hope it helps.

Self referring hierarchical data binding

I'm using Extensions for ASP.NET MVC Q3 2011(open source version) with Razor engine and Asp.net MVC3 architecture.
I want to bind a grid to a entity in my database which has self referring structure (like a tree). Therefore, I can not define it like examples with fixed levels, because I do not know how many levels this grid has. So, I want to bind my grid dynamically to a model.
public class Category : Entity
{
public virtual int Id {private set; get; }
public virtual string Title { set; get; }
public virtual string Description { set; get; }
public virtual string ParentsPath { set; get; }
public virtual IList<Category> Children { get; private set; }
public virtual Category Parent { get; set; }
}
I'm also using Fluent NHibernate and because of that I store children and parent in my entity.
Is there a way to do this? I couldn't find anything in documentations.
Am I missing something? because in Ajax and Winform components, it has been implemented.
Thanks.
You cannot do it automatically, You need a foreach iteration and generating the items and then add them to the tree manually.
I did it for PanelBar when I needed to show a hierarchicy.
Here is an example :
#(Html.Telerik().PanelBar()
.Name("Details")
.ExpandMode(PanelBarExpandMode.Multiple)
.Items(items =>
{
var parent = Model.ParentWorkItem;
List<WorkItem> lst = new List<WorkItem>();
while (parent != null)
{
lst.Add(parent);
parent = parent.ParentWorkItem;
};
for (int i = lst.Count-1; i >=0;i-- )
{
parent = lst[i];
items.Add()
.Expanded(false)
.Text("...")
.LoadContentFrom(...);
}
items.Add()
.Expanded(true)
.Text(...)
.Content(....);
})
)
Hope to be helpful.

Two render bodies in layout page?

I understand that only 1 RenderBody can exist in the MVC3 layout page however I want to attempt to create another. Maybe I'm looking at it the wrong way... Ideally I want to add a testimonial section that pulls in from the DB and display 1 testimonial at a time and a different 1 for each page refresh or new page. What is the best way to go about this?
Controller
CategoryDBContext db = new CategoryDBContext();
public ActionResult Testimonial(int id)
{
TestimonialModel model = db.Testimonials.Find(id);
return View(model);
}
Model
public class TestimonialModel
{
public int ID { get; set; }
public int CategoryID { get; set; }
public string Data { get; set; }
}
public class CategoryDBContext : DbContext
{
public DbSet<TestimonialModel> Testimonials { get; set; }
}
The View is in a folder called CategoryData.
You need to be use:
Layout:
#RenderSection("Testimonial", false) #*false means that this section is not required*#
and in you View
#section Testimonial{
}
I would use #Html.Action()
Here is a great blog post about using them: https://www.c-sharpcorner.com/article/html-action-and-html-renderaction-in-Asp-Net-mvc/
This would allow you to have a TestimonialController that can take in values, query for data and return a partial view.

How to use CheckBox in View _CreateOrEdit.cshtml for an integer or character database field

MVC 3, EntityFramework 4.1, Database First, Razor customization:
I have an old database that sometimes uses Int16 or Char types for a field that must appear as a CheckBox in the MVC _CreateOrEdit.cshtml View. If it is an Int, 1=true and 0=false. If it is a Char, "Y"=true and "N"=false. This is too much for the Entity Framework to convert automatically. For the Details View, I can use:
#Html.CheckBox("SampleChkInt", Model.SampleChkInt==1?true:false)
But this won't work in place of EditorFor in the _CreateOrEdit.cshtml View.
How to do this? I was thinking of a custom HtmlHelper, but the examples I've found don't show me how to tell EntityFramework to update the database properly. There are still other such customizations that I might like to do, where the MVC View does not match the database cleanly enough for EntityFramework to do an update. Answering this question would be a good example. I am working on a sample project, using the following automatically generated (so I can't make changes to it) model class:
namespace AaWeb.Models
{
using System;
using System.Collections.Generic;
public partial class Sample
{
public int SampleId { get; set; }
public Nullable<bool> SampleChkBit { get; set; }
public Nullable<short> SampleChkInt { get; set; }
public Nullable<System.DateTime> SampleDate { get; set; }
public string SampleHtml { get; set; }
public Nullable<int> SampleInt { get; set; }
public Nullable<short> SampleYesNo { get; set; }
public string Title { get; set; }
public byte[] ConcurrencyToken { get; set; }
}
}
I figured it out. Do not need a model binder or Html Helper extension:
In _CreateOrEdit.cshtml, I made up a new name SampleChkIntBool for the checkbox, and set it according to the value of the model SampleChkInt:
#Html.CheckBox("SampleChkIntBool", Model == null ? false : ( Model.SampleChkInt == 1 ? true : false ), new { #value = "true" })
Then, in the [HttpPost] Create and Edit methods of the Sample.Controller, I use Request["SampleChkIntBool"] to get the value of SampleChkIntBool and use it to set the model SampleChkInt before saving:
string value = Request["SampleChkIntBool"];
// #Html.CheckBox always generates a hidden field of same name and value false after checkbox,
// so that something is always returned, even if the checkbox is not checked.
// Because of this, the returned string is "true,false" if checked, and I only look at the first value.
if (value.Substring(0, 4) == "true") { sample.SampleChkInt = 1; } else { sample.SampleChkInt = 0; }
I believe a custom model binder would be in order here to handle the various mappings to your model.
ASP.NET MVC Model Binder for Generic Type
etc
etc
Here is the way to go from checkbox to database, without the special code in the controller:
// The following statement added to the Application_Start method of Global.asax.cs is what makes this class apply to a specific entity:
// ModelBinders.Binders.Add(typeof(AaWeb.Models.Sample), new AaWeb.Models.SampleBinder());
// There are two ways to do this, choose one:
// 1. Declare a class that extends IModelBinder, and supply all values of the entity (a big bother).
// 2. Declare a class extending DefaultModelBinder, and check for and supply only the exceptions (much better).
// This must supply all values of the entity:
//public class SampleBinder : IModelBinder
//{
// public object BindModel(ControllerContext cc, ModelBindingContext mbc)
// {
// Sample samp = new Sample();
// samp.SampleId = System.Convert.ToInt32(cc.HttpContext.Request.Form["SampleId"]);
// // Continue to specify all of the rest of the values of the Sample entity from the form, as done in the above statement.
// // ...
// return samp;
// }
//}
// This must check the property names and supply appropriate values from the FormCollection.
// The base.BindProperty must be executed at the end, to make sure everything not specified is take care of.
public class SampleBinder : DefaultModelBinder
{
protected override void BindProperty( ControllerContext cc, ModelBindingContext mbc, System.ComponentModel.PropertyDescriptor pd)
{
if (pd.Name == "SampleChkInt")
{
// This converts the "true" or "false" of a checkbox to an integer 1 or 0 for the database.
pd.SetValue(mbc.Model, (Nullable<Int16>)(cc.HttpContext.Request.Form["SampleChkIntBool"].Substring(0, 4) == "true" ? 1 : 0));
// To do the same in the reverse direction, from database to view, use pd.GetValue(Sample object).
return;
}
// Need the following to get all of the values not specified in this BindProperty method:
base.BindProperty(cc, mbc, pd);
}
}

Resources