MVC model binding keeps values NULL - c#

I'm trying to get custom model binding to work, but for some reason the values aren't set. The code seems ligit when comparing it to working code, but still it doesnt bind. I guess it some trivial thing i'm missing.
Custom model:
//Cluster is from Entity Framework
//BaseViewModelAdmin defines:
public List<KeyValuePair<string, string>> MenuItems;
public IPrincipal CurrentUser = null;
public Foundation Foundation; //also from Entity Framework
public class AdminClusterCreateModel : BaseViewModelAdmin
{
public Cluster Item;
public AdminClusterCreateModel()
{
Item = new Cluster();
}
}
The view form looks like:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Cluster</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Active)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Active)
#Html.ValidationMessageFor(model => model.Item.Active)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Name)
#Html.ValidationMessageFor(model => model.Item.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And the controller:
[HttpPost]
public ActionResult Create(AdminClusterCreateModel model, FormCollection form)
{
if(ModelState.IsValid) //true
{
var test = form["Item.Name"]; //Value is correct from form (EG: Test)
UpdateModel(model); //no error
}
//At this point model.Item.Name = null <--- WHY?
return View(model);
}
Cluster on request
public partial class Cluster
{
public Cluster()
{
this.Team = new HashSet<Team>();
}
public long Id { get; set; }
public System.DateTime Created { get; set; }
public System.DateTime Modified { get; set; }
public bool Active { get; set; }
public long FoundationId { get; set; }
public string Name { get; set; }
public virtual Foundation Foundation { get; set; }
public virtual ICollection<Team> Team { get; set; }
}

DefaultModelBinder works explicitly on 'Properties', not on 'Fields'
Changing public Cluster Item to public Cluster Item {get; set;} in AdminClusterCreateModel should do the trick.
public class AdminClusterCreateModel : BaseViewModelAdmin
{
public Cluster Item {get; set;}
public AdminClusterCreateModel()
{
Item = new Cluster();
}
}
Regards

This is a trivial and a corner case, yet if it might help someone:
If your model has a property, named model this to will cause the DefaultModelBinder to return a null.
public class VehicleModel
{
public string Model { get; set; }
}

Related

Submitting nested class within a model to a controller

I am trying to submit a form with a model that has a nested class within it. However when I get the data to the controller, the nested class has null fields.
Model:
public class Person
{
public string Name { get; set; }
public ExtraStuff Stuff { get; set; } = new ExtraStuff();
}
public class ExtraStuff
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
View:
#model ProjectName.Models.Person
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model => model.Stuff.Field1)
#Html.TextBoxFor(model => model.Stuff.Field2)
Controller:
public ActionResult ActionName (Person data)
{
data.Name //this is fine
data.Stuff.Field1 //comes in empty
data.Stuff.Field2 //comes in empty
}
I'm wondering if you're doing some custom form submission, because there doesn't seem to be anything wrong with the code provided. If you make a form like the following it should work:
#using (Html.BeginForm("ActionName", "MyController"))
{
#Html.TextBoxFor(model => model.TopValue)
#Html.TextBoxFor(model => model.Nested.SomeValue)
<input type="submit" value="Submit"/>
}
Take a look at this fiddle for example.

Writing a list to database

For a school assignment I need to make a poll with ASP.NET
The problem I get when trying to write the answers in the database is that only one question and one answer gets written into it.
This is the View
#model CinemaJamV2.WebUIV2.Models.EnqueteModel
#{
ViewBag.Title = "Enquete";
}
<h2>Enquete</h2>
#Html.ValidationSummary(true)
#using (Html.BeginForm("Enquete", "Enquete", new { vraag = "vraag", antwoord = "antwoord", naam = "naam", cijfer = "cijfer" }))
{
<div class="col-md-12">
#for(var i=0;i< Model.enquetevragen.Count();i++)
{
<div class="thumbnail">
#Html.LabelFor(model => model.enquetevragen[i].vraag, new { htmlAttributes = new { #class = "form-control" } })
#Html.EditorFor(model => model.enquete.antwoord, new { htmlAttributes = new { #class = "form-control" } })
#Html.EditorFor(model => model.enquete.cijfer, new { htmlAttributes = new { #class = "form-control" } })
</div>
}
</div>
<div class="col-md-12">
<p>Naam <input type="text" name="naam" /> </p>
<input type="submit" name="submit" value="Verzend" />
</div>
}
This is the Controller:
namespace CinemaJamV2.WebUIV2.Controllers
{
public class EnqueteController : Controller
{
private IRepository<Enquete> repository;
private IRepository<EnqueteVraag> a_repository;
private CineJamContext db = new CineJamContext();
public EnqueteController(IRepository<Enquete> a_model, IRepository<EnqueteVraag> vraag_model)
{
repository = a_model;
a_repository = vraag_model;
}
[HttpGet]
public ActionResult Enquete()
{
EnqueteModel enquetevragen = new EnqueteModel
{
enquetevragen = a_repository.List
};
return View(enquetevragen);
}
[HttpPost]
public ActionResult Enquete(Enquete enquete)
{
if (ModelState.IsValid)
{
db.Enquetes.Add(enquete);
db.SaveChanges();
return RedirectToAction("Enquete");
}
return View(enquete);
}
}
}
The ModelView:
namespace CinemaJamV2.WebUIV2.Models
{
public class EnqueteModel
{
public List<Enquete> enquetes {get; set;}
public Enquete enquete { get; set; }
public List<EnqueteVraag> enquetevragen { get; set; }
}
}
And this is the Model Enquete which should contain all the given answers:
namespace CinemaJamV2.Domain.Entities
{
[Table("Enquete")]
public partial class Enquete : IEntity
{
public int Id { get; set; }
[StringLength(1000)]
public string vraag { get; set; }
[StringLength(1000)]
//[Required]
public string antwoord { get; set; }
public int? cijfer {get; set;}
[StringLength(50)]
//[Required]
public string naam { get; set; }
}
}
This Model contains all the Questions
namespace CinemaJamV2.Domain.Entities
{
[Table("EnqueteVraag")]
public partial class EnqueteVraag : IEntity
{
public int Id { get; set; }
[StringLength(1000)]
public string vraag { get; set; }
}
}
The action for POST has only one instance of the Model as its parameter. You need to read this: Model binding to a list
YOu need to use view model that will have list of Enquete and then in post method again you need to do for loop and save it to database.
See following links for samples.
http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx
http://www.c-sharpcorner.com/UploadFile/pmfawas/Asp-Net-mvc-how-to-post-a-collection/
http://www.codeproject.com/Tips/855577/List-of-Model-Object-Post-to-Controller-in-ASP-NET

Cannot combine two models into a ViewModel

I have been trying to get two models to appear in a single view using ViewModels but am failing.
I have simple view with 3 fields right now (Comment, Name, Department) and and a model to match it. I have a controller that returns an empty view and when you submit the form, the empty model is filled in and passed back to the controller. I now want to turn the department field into a drop down and have deduced (maybe incorrectly?) that I should create another model with the static values in it and then pass the ViewModel to the view via the controller but when I attempt to do this, it fails:
View:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="fieldset">
<fieldset>
<legend>CommentDb</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Comment)
</div>
<div>
#Html.TextAreaFor(model => model.Comment, new {style = "width: 900px; height:200px;", autocomplete = "off" })
#Html.ValidationMessageFor(model => model.Comment)
</div>
<div class="editor-label">
#Html.Label("Your Name")
#*#Html.LabelFor(model => model.CommentByName)*#
#Html.TextBoxFor(model => model.CommentByName, new { autocomplete = "off", placeholder = "Optional" } )
#Html.ValidationMessageFor(model => model.CommentByName)
</div>
<div class="editor-label">
#Html.Label("Your Department", new { #class = "display-label" })
#*#Html.LabelFor(model => model.Department)*#
#Html.TextBoxFor(model => model.Department, new { autocomplete = "off", placeholder = "Optional" })
#Html.ValidationMessageFor(model => model.Department)
</div>
</fieldset>
<br />
<br />
</div>
<div>
<input type="submit" value="Create" id="submit"/>
</div>
}
Department drop down model:
namespace SuggestionBox.Models
{
public class DropDownModel
{
public String Departments { get; set; }
public String SetDropDownList()
{
Departments = "Engineering";
return Departments;
}
}
}
Database model:
namespace SuggestionBox.Models
{
public class CommentModel
{
[Key]
public int CommentiD { get; set; }
public string CommentByName { get; set; }
public string Department { get; set; }
[Required]
public string Comment { get; set; }
public DateTime InsertDate { get; set; }
}
}
namespace SuggestionBox.Models
{
public class CommentDbContext : DbContext
{
public CommentDbContext() : base()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<CommentDbContext, SuggestionBox.Migrations.Configuration>());
}
public DbSet<CommentModel> Comments { get; set; }
}
}
My attempted ViewModel:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public DropDownModel Departments { get; set; }
public SuggestionBoxViewModel()
{
Departments = new DropDownModel();
Departments.SetDropDownList();
Comments = new CommentModel();
}
}
}
The Controller:
public ViewResult Index()
{
SuggestionBoxViewModel vm = new SuggestionBoxViewModel();
return View(vm);
}
In return View(vm);, the IDE says: Arguement type 'SuggestionBox.ViewModels.SuggestionBoxViewModel' is not assignable to the model type 'SuggestionBox.Models.CommentModel'
Any idea what I am doing wrong here?
Cheers.
You are making a simple task into something overly complex. Your view model should contain only the properties used in the view, and should not contain methods. Populating your view model properties is the responsibility of the controller.
View model
public class CommentModelVM
{
[Required]
public string Comment { get; set; }
public string CommentByName { get; set; }
[Display(Name="Your Department")] // add attributes associated with the view
public string Department { get; set; }
public SelectList DepartmentList { get; set } // to populate the dropdown options
}
Controller
public ActionResult Create()
{
CommentModelVM model = new CommentModelVM();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(CommentModelVM model)
{
if (!ModelState.IsValid())
{
// Repopulate options and return view
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(CommentModelVM model)
{
List<string> departments = // create your list of departments here (from database or static list)
model.DepartmentList = new SelectList(departments);
}
View
....
#Html.LabelFor(m => m.Department)
#Html.DropDownListFor(m => m.Department, Model.DepartmentList)
....
I just want to start by saying that generally it is a bad idea to name properties or classes for our ViewModels out the view controls that they are bound to. For example: DropDownModel. Doing this creates confusion since Models and ViewModels don't represent a UI component they represent entities and data that the view uses to render its UI controls.
With that being said to answer you question I don't see the need for a ViewModel class to represent the static list of departments for your dropdown. I think you should just add a new list of departments to your SuggestionBoxViewModel class like this:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public IEnumerable<string> Departments { get; set; }
public string SelectedDepartment { get; set; }
public SuggestionBoxViewModel()
{
Departments = new [] {"Engineering","Sales"};
Comments = new CommentModel();
}
public int CommentiD
{
get { return Comments.CommentiD; }
}
public string CommentByName
{
get { return Comments.CommentByName; }
}
}
}
Then in your view all you have to do is bind the dropdown to the list of departments. Like this:
At the top of your view:
#model SuggestionBox.ViewModels.SuggestionBoxViewModel
Then where you want the dropdown to display:
#Html.DropDownListFor(m => m.SelectedDepartment, new SelectList(Model.Departments))
And that's it! I hope this helps.
Your View seems to expect a Comment Model.
If you wanted to bind to a ViewModel, then you would have to implement all the properties that Comment exposes.
So your ViewModel might look like this:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public DropDownModel Departments { get; set; }
public SuggestionBoxViewModel()
{
Departments = new DropDownModel();
Departments.SetDropDownList();
Comments = new CommentModel();
}
public int CommentiD
{
get { return Comments.CommentiD; }
}
public string CommentByName
{
get { return Comments.CommentByName; }
}
...etc.
}
}

Regex on data annotation behaves differently on sub properties

I have the following model on my website
[Table("UserProfile")]
public class UserProfile
{
[Key]
public int UserId { get; set; }
[DisplayName("E-Mail")]
[MaxLength(60)]
[RegularExpression(#"[a-zA-Z0-9_\\.-]+#([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}", ErrorMessage = "Invalid e-mail")]
public string MailAddress { get; set; }
public UserInfo userInfo{ get; set; }
}
public class UserInfo
{
[Key, ForeignKey("UserProfile")]
public int UserProfileUserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
[UIHint("PhoneNumbers")]
public List<PhoneNumber> PhoneNumbers { get; set; }
[...]
}
public class PhoneNumber
{
public int Id { get; set; }
[MaxLength(20)]
[DisplayName("Téléphone")]
[RegularExpression(#"^[+]?[0-9\s]{2,20}$", ErrorMessage = "Invalid")]
public string Phone { get; set; }
public int UserInfoId { get; set; }
[ForeignKey("UserInfoId")]
public Physician UserInfo { get; set; }
}
When I register a user, I have the following view:
<div class="editor-label">
#Html.LabelFor(model => model.MailAddressTemp)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAddressTemp)
#Html.ValidationMessageFor(model => model.MailAddressTemp)
</div>
And the validation is ok
However, when I try to modify the mail address from another view
<div class="editor-label">
#Html.LabelFor(model => model.UserProfile.MailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserProfile.MailAddress)
#Html.ValidationMessageFor(model => model.UserProfile.MailAddress)
</div>
It doesn't work with the exact same mail address
Can someone explain it to me why regex validation doesn't work on sub properties?
Btw, I do have validation for the phone numbers during the registration and the modification of the account.
Thank you
To make the answer more elaborate:
You can't have client side validation on nested objects.
You can replace DataAnnotations with some third-party validation library like e.g. FluentValidatio.
In general DataAnnotations are used for simple scenarios. In case of more complex scenarios like this with nested objects you could implement IValidatableObject interface.

Get string from DropDownList

I have XML file which contains my data and I want from dropdownlist save choose string to this xml.
In my view I have this:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>MatchXML</legend>
...
<div class="editor-label">
#Html.LabelFor(model => model.Team)
</div>
<div class="editor-field">
#Html.DropDownList("Team", (SelectList)ViewBag.Team, String.Empty)
#Html.ValidationMessageFor(model => model.Team)
</div>
...
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
In controller:
public ActionResult Pridat()
{
ViewBag.Team = new SelectList(repo.GetTeams(), "Name", "Name");
return View();
}
[HttpPost]
public ActionResult Pridat(MatchXML match, string Team)
{
if (ModelState.IsValid)
{
try
{
ViewBag.Team = new SelectList(repo.GetTeams(), "Name", "Name");
match.Team = repo.GetTeamByName(Team);
repo.AddMatch(match);
return RedirectToAction("Index");
}
catch (Exception ex)
{
//error msg for failed insert in XML file
ModelState.AddModelError("", "Error creating record. " + ex.Message);
}
}
return View(match);
}
Models looks:
public class MatchXML
{
public int MatchXMLID { get; set; }
public string Opponent { get; set; }
public DateTime MatchDate { get; set; }
public string Result { get; set; }
public Team Team { get; set; }
public int Round { get; set; }
}
public class Team
{
public int TeamID { get; set; }
public string Name { get; set; }
public virtual User Coach { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
I was trying do some modification to do this but it is not working. I can do it with TeamID and saving ID but I want in xml save string (name of team). Thanks for help
Edit:
I updated show code of controller and view method.
You are binding the dropdown to the Team complex property (first argument of the DropDownList helper). This doesn't make sense. You can bind only to scalar values. I would also recommend you using the strongly typed version of the helper:
#Html.DropDownListFor(x => x.Team.TeamID, (SelectList)ViewBag.Team, String.Empty)
This way you will populate the TeamID property in the POST action with the selected value from the dropdown.
Also replace:
#Html.ValidationMessageFor(model => model.Team)
with:
#Html.ValidationMessageFor(model => model.Team.TeamID)

Categories

Resources