Weird MVC Model behavior in EditorTemplate - asp.net

How is it possible that in a razor EditorTemplate the following commands generate a different value for the same ViewModel:
#Html.TextAreaFor(model => model.Value)
#Model.Value
And no, in the Value get property, the value is not changed
Update 1:
Sorry guys for the short message, you know, tired, frustrated...
Made a sample, got rid of all the partials and templates.
Give the textbox number 1, hit add, number 2, hit add, number 3, hit add.
The remove number 2.
The result is an out of sync between the textbox and the displayed value.
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
TestModel testModel = TestModel.Create();
Session["model"] = testModel;
return View("Index", testModel);
}
[HttpPost]
public ActionResult Submit(TestModel submitModel, string submit)
{
// merge values in current form
var testModel = Session["model"] as TestModel;
if (testModel == null) throw new Exception("No current model found.");
testModel.MergeFieldValues(submitModel);
if (submit.StartsWith("Add_"))
{
var rowGroupId = Guid.Parse(submit.Substring("Add_".Length));
TestRowGroup rowGroup = testModel.Groups.SelectMany(g => g.RowGroups).Single(rg => rg.RowGroupId == rowGroupId);
rowGroup.AddFieldRow();
}
if (submit.StartsWith("Del_"))
{
var fieldRowId = Guid.Parse(submit.Substring("Del_".Length));
testModel.RemoveFieldRow(fieldRowId);
}
return View("Index", testModel);
}
}
Model:
public class TestModel
{
public List<TestGroup> Groups { get; set; }
public static TestModel Create()
{
var testModel = new TestModel { Groups = new List<TestGroup>() };
var grp = new TestGroup { RowGroups = new List<TestRowGroup>() };
var rowGrp = new TestRowGroup { RowGroupId = Guid.NewGuid(), FieldRows = new List<TestFieldRow>() };
var fldRow = new TestFieldRow { FieldRowId = Guid.NewGuid(), Fields = new List<TestFormField>() };
var fld = new TestFormField { FieldId = Guid.NewGuid() };
fldRow.Fields.Add(fld);
rowGrp.FieldRows.Add(fldRow);
grp.RowGroups.Add(rowGrp);
testModel.Groups.Add(grp);
return testModel;
}
public void MergeFieldValues(TestModel src)
{
foreach (var srcField in src.Groups.SelectMany(g => g.RowGroups.SelectMany(rg => rg.FieldRows.SelectMany(fr => fr.Fields))))
{
var destField = Groups.SelectMany(g => g.RowGroups.SelectMany(rg => rg.FieldRows.SelectMany(fr => fr.Fields))).FirstOrDefault(f => f.FieldId == srcField.FieldId);
if (destField == null) throw new Exception("Field not found during merge");
destField.Value = srcField.Value;
}
}
public void RemoveFieldRow(Guid fieldRowId)
{
foreach (var group in Groups)
{
foreach (var rowGroup in group.RowGroups)
{
rowGroup.FieldRows.RemoveAll(fieldRow => fieldRow.FieldRowId == fieldRowId);
}
}
}
}
public class TestGroup
{
public List<TestRowGroup> RowGroups { get; set; }
}
public class TestRowGroup
{
public List<TestFieldRow> FieldRows { get; set; }
public Guid RowGroupId { get; set; }
public void AddFieldRow()
{
var newRow = new TestFieldRow
{
Fields = new List<TestFormField>()
};
newRow.FieldRowId = Guid.NewGuid();
var fld = new TestFormField { FieldId = Guid.NewGuid() };
newRow.Fields.Add(fld);
FieldRows.Add(newRow);
}
}
public class TestFieldRow
{
public Guid FieldRowId { get; set; }
public List<TestFormField> Fields { get; set; }
}
public class TestFormField
{
public Guid FieldId { get; set; }
public string Value { get; set; }
}
View:
#model FieldTest.Models.TestModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
#using (Html.BeginForm("Submit", "Home", FormMethod.Post))
{
for (int g = 0; g < Model.Groups.Count; g++)
{
for (int rg = 0; rg < Model.Groups[g].RowGroups.Count; rg++)
{
for (int fr = 0; fr < Model.Groups[g].RowGroups[rg].FieldRows.Count; fr++)
{
for (int f = 0; f < Model.Groups[g].RowGroups[rg].FieldRows[fr].Fields.Count; f++)
{
#Html.HiddenFor(model => model.Groups[g].RowGroups[rg].FieldRows[fr].Fields[f].FieldId)
#Model.Groups[g].RowGroups[rg].FieldRows[fr].Fields[f].Value
#Html.TextBoxFor(model => model.Groups[g].RowGroups[rg].FieldRows[fr].Fields[f].Value)
<button onclick="return confirm('Are you sure you would like to remove this row?');" type="submit" value="#string.Format("Del_{0}", Model.Groups[g].RowGroups[rg].FieldRows[fr].FieldRowId)" name="submit">Remove</button>
<hr />
}
}
<button type="submit" value="#string.Format("Add_{0}", Model.Groups[g].RowGroups[rg].RowGroupId)" name="submit">Add</button>
}
}
<input type="submit" value="Submit" name="submit" />
}
</body>
</html>

More importantly, are you sure this is an EditorTemplate problem? If you put the code in your main view, does it also happen? Did you try that? Or did you assume it was an EditorTemplate problem?
Since you neglected to provide any context for your question, all we can do is guess. More than likely, you have modified the contents of the view model in a post operation, and are now surprised that the Html helpers are using the old value rather than the value from the model.
If so, this is "by design", and a well documented (hundreds if not thousands of questions already here on SO about this issue). The MVC Helpers prefer the contents of the ModelState over the model itself in post operations. You have to clear the ModelState in order to work around it.

Related

Can not pass the model back to controller from view in ASP.NET MVC. Model is null when received

I have two model classes
public class Item
{
public string Name;
public SubItem SubObject { get; set; }
}
public class SubItem
{
public int Age { get; set; }
public string Work { get; set; }
}
From my code I have something similar to this
public IActionResult ViewAndSend()
{
Item item = new Item
{
Name = "MyName",
SubObject = new SubItem
{
Age = 29,
Work = "Microsoft and google"
},
};
return View(item);
}
and my ViewAndSend.cshtml looks like this
#model TestNavigation.Models.Item
<h2>ViewAndSend example</h2>
<p>#(String.Format("{0} {1}", "Name", Model.Name))</p>
<p>#(String.Format("{0} {1}", "Age", Model.SubObject.Age))</p>
<p>#(String.Format("{0} {1}", "Work", Model.SubObject.Work))</p>
#using (Html.BeginForm("SendSubItem", "Home", FormMethod.Post))
{
#Html.HiddenFor(m => m.SubObject.Age)
#Html.HiddenFor(m => m.SubObject.Work)
<input type="submit" value="Next" />
}
My SendSubItem method looks like this
[HttpPost]
public IActionResult SendSubItem(SubItem subItem)
{
int age = subItem.Age; //age is 0
var work = subItem.Work; // work is null
return View();
}
The ViewAndSend.cshtml prints the correct values. However the SendSubItem method gets an object with 0 as Age and null for Work.
Can anyone explain what I am doing wrong?
You should use Item as the receiving type.
[HttpPost]
public IActionResult SendSubItem(Item item)
{
int age = item.SubObject.Age;
var work = item.SubObject.Work;
return View();
}

Write to sql server of enum type? DropDownListFor| ASP.NET

I have a dropdownlist in view. It gets values ​​from the enum, because I can't do otherwise. (If someone gives me an idea / example of how to do it, I will gladly accept it)
I'm just writing this application, and I have a question whether the current way I did it, or this way can be saved to the database?
Database
Column Name ||||| Data Type ||||| Allow Nulls
PrzyczynaNieobecnosci | varchar(20) | true
Model
public partial class Karta_Model
{
public Urlopy? PrzyczynaNieobecnosci { get; set; }
}
public enum Urlopy
{
a,
b,
c
}
public partial class ParentView
{
public List<Karta_Model> Model1 { get; set; }
}
View
#using AppEcp.Models
#model ParentView
#Html.DropDownListFor(m => m.Model1[nr_rows].PrzyczynaNieobecnosci, new SelectList(Enum.GetValues(typeof(Urlopy))), " ", new { #class = "form-control" })
Demo on dotnet fiddle
Model
public partial class Karta_Model
{
public List<string> PrzyczynaNieobecnosci { get; set; }
}
public partial class ParentView
{
public List<Karta_Model> Model1 { get; set; }
}
In Controller:
[HttpGet]
public ActionResult Index()
{
return View(new ParentView()
{
Model1 = new List<Karta_Model>
{
new Karta_Model { PrzyczynaNieobecnosci = new List<string> { "a", "b"}},
new Karta_Model { PrzyczynaNieobecnosci = new List<string> { "c", "d"}}
}
});
}
In View
You should get the correct number of item in the list like below
#Html.DropDownListFor(m => m.Model1[0].PrzyczynaNieobecnosci, new SelectList(Model.Model1[0].PrzyczynaNieobecnosci), " ", new { #class = "form-control" })
#Html.DropDownListFor(m => m.Model1[0].PrzyczynaNieobecnosci, new SelectList(Model.Model1[1].PrzyczynaNieobecnosci), " ", new { #class = "form-control" })

Viewmodel nested checkbox not binding on post

I have a subscription form that contains a matrix of options. The form can be seen in screenshot Subscription table
I am having trouble with ASP.NET MVC generating appropriate ID's and then on postback having the binder populate the model with the form selections.
The add on name is down the left side and when posted back the collection of SubscriptionInputModel.Addons get populated ok. But SubscriptionInputModel.Addons[i].SubscriptionLevelCombos is null as seen in debug screenshot
The current code is using CheckBoxFor but I've also tried manually generating ID's in format:
#Html.CheckBox("addon[" + a + "].SubscriptionLevelCombo[" + i + "].AddonSelected", addon.SubscriptionLevelCombos[i].AddonSelected)
Neither format has worked and also experimented while debugging but no luck. I would appreciate any ideas. Worst case I assume I would need to read the raw form collection?
I assume the level of nested object shouldn't matter as it is all object path notation and array indexes in html tag names?
Here are snippets of current code to help illustrate what exists.
View Models
public class SubscriptionInputModel
{
//other stuff to come
//....
//add on's, listed down left of table
public List<SubscriptionInputAddonModel> Addons;
}
public class SubscriptionInputAddonModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Note { get; set; }
public List<SubscriptionInputAddonComboModel> SubscriptionLevelCombos { get; set; }
}
public class SubscriptionInputAddonComboModel
{
public int? Id { get; set; }
public decimal? AddonCost { get; set; }
public CostTimeUnitOption? CostTimeUnit { get; set; }
public bool? IsComplimentaryBySubscriptionLevel { get; set; }
public string ComboText { get; set; }
public bool AddonSelected { get; set; }
public int? AddonId { get; set; }
}
SubscriptionController
[Route("identity/subscription")]
// GET: Subscription
public ActionResult Index()
{
SubscriptionInputModel model = new SubscriptionInputModel();
ArrbOneDbContext db = new ArrbOneDbContext();
List<SubscriptionInputAddonModel> addons = Mapper.Map<Addon[], List<SubscriptionInputAddonModel>>(db.Addons.OrderBy(a => a.OrderPosition).ToArray());
model.Addons = addons;
foreach(var addon in model.Addons)
{
var addonCombos = db.Database.SqlQuery<SubscriptionInputAddonComboModel>(#"SELECT SLA.Id, AddonCost, CostTimeUnit, IsComplimentaryBySubscriptionLevel, ComboText, AddonId
FROM SubscriptionLevel L
LEFT OUTER JOIN SubscriptionLevelAddon SLA ON L.Id = SLA.SubscriptionLevelId AND SLA.AddonId = #p0
ORDER BY L.OrderPosition", addon.Id);
addon.SubscriptionLevelCombos = addonCombos.ToList();
}
return View(model);
}
[Route("identity/subscription")]
[ValidateAntiForgeryToken]
[HttpPost]
// POST: Subscription
public ActionResult Index(SubscriptionInputModel model)
{
ArrbOneDbContext db = new ArrbOneDbContext();
List<SubscriptionInputAddonModel> addons = Mapper.Map<Addon[], List<SubscriptionInputAddonModel>>(db.Addons.OrderBy(a => a.OrderPosition).ToArray());
model.Addons = addons;
//debug breakpoint to inspect returned model values
return View();
}
Index.cshtml
#model Identity_Server._Code.ViewModel.Subscription.SubscriptionInputModel
#{
ViewBag.Title = "Subscription";
}
#using (Html.BeginForm("Index", "Subscription", new { signin = Request.QueryString["signin"] }, FormMethod.Post))
{
#Html.ValidationSummary("Please correct the following errors")
#Html.AntiForgeryToken()
...
// ADD ONs ----------------------------------------------------------------------------------
#for (int a = 0; a < Model.Addons.Count; a++)
{
var addon = Model.Addons[a];
<tr>
<td class="text-left">#addon.Name
<div class="SubscriptionItemNote">#addon.Note
#Html.HiddenFor(m => m.Addons[a].Id)
</div>
</td>
#for (int i = 0; i < addon.SubscriptionLevelCombos.Count; i++)
{
<td>
#if (addon.SubscriptionLevelCombos[i].Id.HasValue)
{
if (addon.SubscriptionLevelCombos[i].AddonCost.HasValue && addon.SubscriptionLevelCombos[i].AddonCost.Value > 0)
{
#Html.Raw("<div>+ " + #addon.SubscriptionLevelCombos[i].AddonCost.Value.ToString("0.##") + " / " + #addon.SubscriptionLevelCombos[i].CostTimeUnit.Value.ToString() + "</div>")
}
else if (addon.SubscriptionLevelCombos[i].IsComplimentaryBySubscriptionLevel.HasValue && #addon.SubscriptionLevelCombos[i].IsComplimentaryBySubscriptionLevel.Value)
{
<span class="glyphicon glyphicon-ok"></span>
}
if (!string.IsNullOrEmpty(addon.SubscriptionLevelCombos[i].ComboText))
{
<div>#addon.SubscriptionLevelCombos[i].ComboText</div>
}
if (addon.SubscriptionLevelCombos[i].AddonCost.HasValue && addon.SubscriptionLevelCombos[i].AddonCost.Value > 0)
{
#Html.HiddenFor(m => m.Addons[a].SubscriptionLevelCombos[i].Id)
#Html.CheckBoxFor(m => m.Addons[a].SubscriptionLevelCombos[i].AddonSelected)
}
}
</td>
}
</tr>
}

Model Item type ambiguity when using PagedList

I am developing a web survey application in ASP.Net Mvc3. I use PagedList in my application to paginate the questions page alone.
I get the following error:
The model item passed into the dictionary is of type
'PagedList.PagedList`1[SWSSMVC.Models.ViewModels.QuestionViewModel]',
but this dictionary requires a model item of type
'PagedList.IPagedList`1[SWSSMVC.Models.ViewModels.QuestionListViewModel]'.
There is a question which is of similar nature. The solution says not to specify anonymous type, as far as I understood. Can someone point out where in my code I have anonymous type? I believe I have typed all my variables with appropriate models.
This is the question Controller:
public class QuestionController : SessionController
{
DBManager dbmgr = new DBManager();
//
// GET: /Question/
public ActionResult Index(string currentSection, string currentPage, int? page)
{
int j;
SectionSession = currentSection;
PageSession = currentPage;
var questionList = new QuestionListViewModel();
int questionCount = dbmgr.getQuestionCount(currentPage);
var question = new QuestionViewModel();
for(int i=1 ; i<=questionCount; i++)
{
int questionid = dbmgr.getQuestionid(currentPage, i);
string questiontext = dbmgr.getQuestion(questionid);
List<string> oldchoices = dbmgr.getChoicesAns(questionid);
ChoiceViewModel choice = new ChoiceViewModel();
question = new QuestionViewModel { QuestionId = questionid, QuestionText = questiontext, Answer = oldchoices.Last()};
for (j = 0; j < oldchoices.Count() - 1; j++)
{
if (oldchoices[j] != null)
{
question.Choices.Add(new ChoiceViewModel { ChoiceId = j, ChoiceText = oldchoices[j] });
}
}
questionList.Questions.Add(question);
}
int pageSize = 3;
int pageNumber = (page ?? 1);
return View(questionList.Questions.ToPagedList(pageNumber, pageSize));
}
There are two models:
public class QuestionViewModel
{
public int QuestionId { get; set; }
public string QuestionText { get; set; }
public List<ChoiceViewModel> Choices { get; set; }
public string Answer { get; set; }
[Required]
public string SelectedAnswer { get; set; }
public QuestionViewModel()
{
Choices = new List<ChoiceViewModel>();
}
}
public class QuestionListViewModel
{
public List<QuestionViewModel> Questions { set; get; }
public QuestionListViewModel()
{
Questions = new List<QuestionViewModel>();
}
}
I am entering my part- Index View code for the above Question Controller
#model PagedList.IPagedList<SWSSMVC.Models.ViewModels.QuestionListViewModel>
#{
ViewBag.Title = "Index";
}
<h2>Questions</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
#foreach (var item in Model)
{
#Html.EditorFor(x => item.Questions)
}
I also have a Editor Template like this
#model SWSSMVC.Models.ViewModels.QuestionViewModel
<div>
#Html.HiddenFor(x => x.QuestionId)
<h3> #Model.QuestionText </h3>
#foreach (var a in Model.Choices)
{
<p>
#Html.RadioButtonFor(b => b.SelectedAnswer, a.ChoiceText) #a.ChoiceText
</p>
}
</div>
I tried to run through the code a couple of times and having hard time figuring it out. I also do not know how I could have made the questionList as a LINQ variable, given that, my questionList is inturn constructed with questions and choices from a separate model.
Creator of PagedList here. The problem is that this line:
return View(questionList.Questions.ToPagedList(pageNumber, pageSize));
Is sending a model of type IPagedList down to the page (because the extension method is being applied to a type of List), but your page says it is expecting:
#model PagedList.IPagedList<SWSSMVC.Models.ViewModels.QuestionListViewModel>
Changing your view code to say this instead should fix it:
#model PagedList.IPagedList<SWSSMVC.Models.ViewModels.QuestionViewModel>

Checkbox not set in ASP.NET MVC 3

I'm trying to initialize my checkbox in controller like the code below, but in the view it's not selected whether it's true or false
controller :
foreach (var item in AssignedUsers)
{
if (dc.App_UserTasks.Any(u => u.UserId == item.UserId && u.TaskId == ProjectTask.Id))
{
Users.Single(u => u.Id == item.Id).IsChecked = true;
}
else
{
Users.Single(u => u.Id == item.Id).IsChecked = false;
}
}
view:
#for (int i = 0; i < Model.Responsibles.Count; i++)
{
#Html.CheckBoxFor(u => u.Responsibles[i].IsChecked)
}
send model from controller to view :
var EPT = new EditProjectTaskModel
{
ProjectId = ProjectTask.ProjectId,
Title = ProjectTask.Title,
ProjectName = ProjectTask.App_Project.ProjectName,
Id = ProjectTask.Id,
Description = ProjectTask.Description,
EstimatedTime = ProjectTask.EstimatedTime,
Status = ProjectTask.Status,
Responsibles = Users.ToList()
};
return PartialView("_EditProjectTask", EPT);
Assuming your User ViewModel looks like this
public class UserViewModel
{
public string Name { set;get;}
public int UserId { set;get;}
public bool IsSelected { set;get;}
}
And you have your main view model has a collection of this UserViewModel
public class EditProjectTaskModel
{
public List<UserViewModel > Responsibles { set; get; }
public EditProjectTaskModel()
{
if(this.Responsibles ==null)
this.Responsibles =new List<UserViewModel >();
}
}
Create an editor template called Responsibles.cshtml with the below content
#model YourNameSpace.UserViewModel
#Html.CheckBoxFor(x => x.IsSelected)
#Html.LabelFor(x => x.IsSelected, Model.Name)
#Html.HiddenFor(x => x.UserId)
Now include that in your main view like this, instead of the loop
#model EditProjectTaskModel
#using (Html.BeginForm())
{
//other elements
#Html.EditorFor(m=>m.Responsibles)
<input type="submit" value="Save" />
}
If you want to get the selected checkboxes on a form submit.
[HttpPost]
public ActionResult Save(EditProjectTaskModel model)
{
List<int> userIDs=new List<int>();
foreach (UserViewModel user in model.Responsibles)
{
if (user.IsSelected)
{
//you can get the selected user id's here
userIDs.Add(user.UserId);
}
}
}

Resources