ASP.NET MVC ViewModel Property is null - c#

I have the following ViewModel:
public class ProjectViewModel
{
public Project Project { get; set; }
public Customer Customer { get; set; }
}
The Customer property is only used to link a new Project to the Customer, so I don't include this property in my Create view, which looks like this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Project</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Project.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Project.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Project.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
When I post the form, the following method in my ProjectsController is triggered:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Project,Customer")] ProjectViewModel vm)
{
var project = Mapper.Map<Project>(vm);
if (ModelState.IsValid)
{
_db.Create(project);
return RedirectToAction("Index");
}
return View(vm);
}
This is where the unexpected behaviour occurs. Now, when I examine the vm property, the Customer property is null.
The question
How can I still keep the Customer property filled, while not using it in the view?

If you want to persist your Customer data, then you need to set all the fields as hidden elements otherwise they will be lost in the redirect.
#Html.HiddenFor(model => model.Customer.Property1) ...etc...

The short answer is that you can't. When data is posted back, the only data that is included is what is in the form on the HTML page.
Your best bet is to either use a session variable or look up the data again in the post handler, or alternatively, serialize the data to hidden fields.

To make sure the Customer property is never null when initializing ProjectViewModel, you could add a constructor to your ProjectViewModel class initializing the Customer property to a new Customer() like so:
public class ProjectViewModel
{
// Constructor
public ProjectViewModel()
{
Customer = new Customer();
}
public Customer Customer { get; set; }
}

Related

Pass Data from Partial View to Parent View

I have a Profile page with a Postcode look up feature from https://postcodes.io. So far I have a Partial View with Ajax Form using the following code.
#using (Ajax.BeginForm("_CityLookUp", "Home", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "div1",
InsertionMode = InsertionMode.Replace
}))
{
<div id="div1">
<div class="form-group">
#Html.LabelFor(m => m.PostCode, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.PostCode, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(m => m.County, new { #class = "form-control", id = "County" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(m => m.City, new { #class = "form-control", id = "City" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Look Up" />
</div>
</div>
</div>
}
This works. I then rendered the Partial Page on the main profile page with the main profile form.
I just want to assign the value from the two other hidden fields to fields in the main page.
Partial Controller
[HttpGet]
public PartialViewResult _CityLookUp()
{
return PartialView();
}
[HttpPost]
public PartialViewResult _CityLookUp(string postcode)
{
var client = new PostcodesIOClient();
var result = client.Lookup(postcode);
var jst = new AddressLookupViewModel();
jst.City = result.AdminDistrict;
jst.County = result.AdminCounty;
jst.PostCode = postcode;
ViewBag.City = jst.City;
ViewBag.County = jst.County;
var results = new BooksViewModel();
results.City = jst.City;
results.County = jst.County;
return PartialView(jst);
}
I have tried a new view model and assigning the results to the view model but it didn't work. Tried using JavaScript to get the value of the hidden field, no luck. If you'd rather do with a separate method, please explain how you would implement it.
Parent View
#model TestingView.Models.ParentViewModel
<div class="container">
#Html.Partial("_CityLookUp")
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(v => v.City, new { #class = "form-control", id = "City" })
</div>
</div>
</div>
PartialViewModel
namespace TestingView.Models
{
public class AddressLookupViewModel
{
public string PostCode { get; set; }
public string County { get; set; }
public string City { get; set; }
}
}
Parent View Model
namespace TestingView.Models
{
public class ParentViewModel
{
public string City { get; set; }
public string County { get; set; }
}
}
Side question: For some reason, when I hover over
#Html.HiddenFor(m => m.City, new { #class = "form-control", id = "City" })
on the parent view, it references the AddressLookUpViewModel and not the BooksViewModel in the parent view.. I have added both View Models.
I will answer this two ways; first using the approach you were initially asking about and then how I would prefer to implement this.
Solution 1
With this approach we will copy the value of the City element from the partial view to the parent view.
First thing we need to fix up is when you view the profile right now, there will be two elements with an Id of "City"; one from the parent page and one from the partial. That's invalid markup and it will cause problems for any potential solution.
Rename the Id attribute in the parent view:
#Html.TextBoxFor(v => v.City, new { #class = "form-control", id = "Profile_City" })
Update the partial view to call a js function when it successfully retreives a set of postcode values:
#using (Ajax.BeginForm("_CityLookUp", "Home", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "div1",
InsertionMode = InsertionMode.Replace,
OnSuccess = "UpdateProfile" // Add this
}))
Add a js function, UpdateProfile, to the end of the parent view:
#section scripts {
<script>
function UpdateProfile()
{
var City = $("#City").val();
alert(City);
$("#Profile_City").val(City);
}
</script>
}
That should be all that's required. The alert is just there for debugging.
The #section scripts code will be injected in to your _Layout.cshtml where it calls #RenderSection("scripts", required: false), part of the default MVC project.
One problem that might crop up going forward is when you build the parent view into a form you might be tempted to nest the form elements for layout reasons but nested form elements aren't permitted.
Solution 2
This approach uses jQuery's ajax() method to fetch the data and directly populate the relevant fields on a form.
Set up the model.
namespace TestingView.Models
{
public class ProfileViewModel
{
public string PostCode { get; set; }
public string County { get; set; }
public string City { get; set; }
}
}
This is a copy of AddressLookupViewModel as it contains all the necessary fields. I have simply renamed it to suggest its use is for the main profile form itself.
Create the view.
The view now has a single Html.BeingForm(), with the Look Up button bound to an ajax function rather than submitting an ajax form.
Its not 100% clear to me whether you want the user to be able to edit the County and City fields after a look up. In the code below they can.
#model TestingView.Models.ProfileViewModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(m => m.PostCode, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.PostCode, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="button" class="btn btn-primary" onclick="Lookup()">Look Up</button>
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.County, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.County, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.City, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.City, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
</div>
}
#section scripts {
<script>
function Lookup() {
$.ajax("/Home/CityLookup", {
type: "POST",
dataType: 'json',
data: {
postcode: $("#PostCode").val()
}
})
.success(function (response) {
console.log("Success");
console.log(response);
var cityObj = response;
$("#City").val(cityObj.City);
console.log("City " + cityObj.City);
$("#County").val(cityObj.County);
})
.error(function () {
alert("There was a problem looking up the postcode");
})
};
</script>
}
Create a controller to service the ajax request. This is heavily based on your POST _CityLookUp controller. Note the Action type is JsonResult and the return converts the jst object in to a JSON string. Using JSON makes the lookup extensible; we can return multiple properties as an object and unpack them to a javascript object for use client-side.
[HttpPost]
public JsonResult CityLookup(string postcode)
{
var client = new PostcodesIOClient();
var result = client.Lookup(postcode);
var jst = new AddressLookupViewModel();
jst.City = result.AdminDistrict;
jst.County = result.AdminCounty;
jst.PostCode = postcode;
return Json(jst);
}

Asp.net MVC multiple select for List property

I'm fairly new to ASP.Net MVC so forgive me for anything that should just be obvious.
I have an object that contains a property that is a list. I only don't know how I should implement this in the create.
this is the object:
public class TeamMember
{
public int TeamMemberId { get; set; }
public string FristName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string Biographie { get; set; }
public virtual Image Image { get; set; }
public virtual List<DanGrade> DanGrades { get; set; }
}
In the create view I want to be able to select multiple Dangrades.
I tried to modify an editor Template for it that looks like this:
#using BudoschoolTonNeuhaus.Models
#model BudoschoolTonNeuhaus.Models.TeamMember
#{
var db = new ApplicationDbContext();
var danGrades = db.DanGrades.ToList();
}
<select multiple name="#ViewData.TemplateInfo.HtmlFieldPrefix" class="dropdown">
#foreach (var dan in danGrades)
{
<option value="#">
#dan.DanGradeId: #dan.BudoSport, #dan.Grade
</option>
}
</select>
but this does not give the result that I thought it would, its just showing mutiple dangrade labels in the create view that you can see here:
#model BudoschoolTonNeuhaus.Models.TeamMember
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Admin_Layout.cshtml";
}
<div class="wrapper">
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TeamMember</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.FristName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FristName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FristName, "", new { #class = "text-danger" })
</div>
</div>
.... // controls for other properties of model
<div class="form-group">
#Html.LabelFor(model => model.DanGrades, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DanGrades, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DanGrades, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Image, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="file" id="Image" name="Image" hidden />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
</div>
current HTML output:
Thanks for you help in advance!
To create a <select multiple> you use the ListBoxFor() method in your view.
But your model needs two properties to generate a listbox, a IEnumerable<int> to bind the selected values to (assumes the ID proeprty of DanGrade is typeof int), and an IEnumerable<SelectListItem> to display the <option> elements.
You editing data, so always start with a view model
public class TeamMemberVM
{
public int? TeamMemberId { get; set; }
....
[Display(Name = "DanGrades")]
public IEnumerable<int> SelectedDanGrades { get; set; }
public IEnumerable<SelectListItem> DanGradesList { get; set; }
}
and your view will be
#model yourAssembly.TeamMemberVM
....
#Html.ListBoxFor(m => m.SelectedDanGrades, Model.DanGradesList, new { #class="dropdown" })
and your controller methods will be
public ActionResult Create()
{
TeamMemberVM model = new TeamMemberVM();
ConfigureViewModel(model);
// For an Edit method, your would set the existing selected items here
model.SelectedDanGrades = ...
return View(model);
}
public ActionResult Create(TeamMemberVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model); // repopulate the SelectList
return View(model);
}
// model.SelectedDanGrades contains the ID's of the selected options
// Initialize an instance of your data model, set its properties based on the view model
// Save and redirect
}
private void ConfigureViewModel(TeamMemberVM model)
{
IEnumerable<DanGrade> danGrades = db.DanGrades();
model.DanGradesList = danGrades.Select(x => new SelectListItem
{
Value = x.DanGradeId.ToString(),
Text = x.??? // the name of the property you want to use for the display text
});
}
Note also that your view has a file input so your view model needs a HttpPostedFileBase property to bind the file to
public HttpPostedFileBase Image { get; set; }
and in the view
#Html.TextBoxFor(m => m.Image, { new type ="file" })
Shouldn't your model be like that ?
[UIHint("NameOfTheEditorTemplate")]
public virtual List<DanGrade> DanGrades { get; set; }
Be sure to put the EditorTemplate under one of these two paths
~/Views/Shared/EditorTemplates
~/Views/Controller_Name/EditorTemplates
As explained in this post
So you are trying to save a list of custom objects inside your object. First of all, know that if you try to save teammember to a database your list of objects will not save. I've experienced this same issue and its needs some special configuring to get just that to work.
Second you can't select custom objects from a < select >. Select returns string[] to your controller. So objects, no. You can't return complex items like that using select directly.
What you can do is return a string[] and use the individual strings (maybe it contains name, maybe it contains id?) and then use that array to pull each object to your teammember object in the controller from the dangrade db context (I'm assuming that is where they are stored).
So for example if you Go back to your controller and add (string[] dangrades) to your parameters. Your parameters now looks something like this (string[] dangrades, Bind[blahblah] ... teammember).
Now after referencing the other database you can do as follows
teammember.Dangrades = new list<Dangrade>();
foreach(string item in dangrades)
{
var dangradeselected = from x in db.dangrades where x.name = item select x;
var dangradefromlinq = dangradeselected.tolist();
teammember.Dangrades.Add(dangradefromlinq[0]);
}
If you had previously stored dangrades in some other format (ie not a database) then you will have to append your code, or ask specifically with that for a better answer.
Also don't forget to give your select and id= (lookup html attributes) so that the controller can recognize it.
You can probably make this (pseudo)code a little neater. Also don't forget about possible null values.
If you want to save a list of items for each teamember you can also look into having 2 databases. I'm not sure if this is recommended. But you can have one for teammembers, and one for dangrades. In the case of dangrades you would add an additional property called grouping id that would match the id of your teammember. So when you pull up your teammember you could also pull up all related dawngrades that match its database id.
That's everything I can think of. If you find a simpler solution by all means go with that.

Object reference not set to an instance of an object MVC

I already saw a bunch of these posts but none helped me because most weren't applied to C# and MVC.
I have the Create for an object named TipoImovel. This object has an auto-generated ID (int), a description (string - tipoImovel) and then the possibly NULL value to a sub-TipoImovel (int? and then the reference TipoImovel). If it's confusing think of it like you create a House (TipoImovel). You can then create a Pool (also TipoImovel) and say it's a sub-type of TipoImovel making it a Pool which is subTipoImovel of House (House with pool). Sorry for the names but they are in my native language. If any questions arise around them please say.
Now here's the code:
TipoImovel.cs
public class TipoImovel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Display(Name = "Tipo de Imóvel")]
[StringLength(20)]
public string tipoImovel { get; set; }
[Display(Name = "Sub-Tipo de:")]
public int? tipoImovelID { get; set; }
[Display(Name = "Sub-Tipo de:")]
public virtual TipoImovel subTipoImovel { get; set; }
}
TipoImovelController.cs (GET and POST methods)
// GET: TipoImovel/Create
public ActionResult Create()
{
ViewBag.tipoImovelID = new SelectList(db.TipoImovel, "ID", "tipoImovel");
return View();
}
// POST: TipoImovel/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,tipoImovel,tipoImovelID")] TipoImovel TipoImovel)
{
if (ModelState.IsValid)
{
db.TipoImovel.Add(TipoImovel);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.tipoImovelID = new SelectList(db.TipoImovel, "ID", "tipoImovel", TipoImovel.tipoImovelID);
return View(TipoImovel);
}
Create.cshtml
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TipoImovel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.tipoImovel, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.tipoImovel, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.tipoImovel, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.tipoImovelID, "tipoImovelID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("tipoImovelID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.subTipoImovel, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
The error comes up because the TipoImovel I receive in the POST method comes as NULL. The form is properly created retreiving any (manually introduced) existing TipoImovel and showing them in the ComboBox but upon hitting "Create" it crashes.
I've been around this problem for 2 days and I can't fix it. Any help is appreciated!
EDIT: Pic of generated HTML:
Your model has a property string TipoImovel but you have also named the parameter of your POST method TipoImovel (and even more confusing, your class is also named TipoImovel)
Change the name of the parameter so that it dos not match one of the properties of your model, say
public ActionResult Create(TipoImovel model)
{
....
}

MVC5 view drop down list from ViewBag

I'm new to MVC5/C# (fresh off a Silverlight project) and have a web application (not ASP.net) that I'm working on. I can't figure out how to get the value from a dropdown list that is populated from a ViewBag and not the model. Everything I've seen is geared towards ASP.NET and/or populating the dropdown from the model.
I have this model for shifts:
public class Shift
{
public Guid ShiftID { get; set; }
public string AreaOfOperation { get; set; }
public string UserName { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
And this for AreaOfOperations:
public class AreaOfOperations
{
public Guid AreaOfOperationsID { get; set; }
public String AreaOfOperation { get; set; }
public bool InUse { get; set; }
}
The relevant controller code, which populates the view nicely with a working dropdown:
public ActionResult Create(DateTime? datetime)
{
List<AreaOfOperations> list = db.AreaOfOperations.Where(i => i.InUse == true).OrderBy(aoo => aoo.AreaOfOperation).ToList();
ViewBag.DropDownAOOs = new SelectList(list, "AreaOfOperationsID", "AreaOfOperation");
Shift shift = new Shift();
shift.ShiftID = Guid.NewGuid();
shift.StartTime = DateTime.Now;
shift.UserName = User.Identity.Name;
return View(shift);
}
// POST: Shifts/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ShiftID,AreaOfOperations,UserName,StartTime")] Shift shift)
{
try
{
if (ModelState.IsValid)
{
shift.ShiftID = Guid.NewGuid();
db.Shifts.Add(shift);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(shift);
}
And my view:
#model CRMgr5.Models.Shift
#{
ViewBag.Title = "Start Shift";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Shift</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.AreaOfOperations, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("AreaOfOperation", ViewBag.DropDownAOOs as SelectList, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UserName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UserName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StartTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StartTime, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StartTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input id="btnStartShift" type="submit" value="Start Shift" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Any help would be greatly appreciated. Thanks.
In the drop down list you named your select as "AreaOfOperation" but the model property is called "AreaOfOperations." Hence the binder will not be able to bind it.
As someone here already suggested you should use strongly typed html helpers such as DropDownListFor:
#Html.DropDownListFor(m => m.AreaOfOperations, ViewBag.DropDownAOOs as SelectList)
You did it for the label not sure why you opted not to use it when generating a drop down list?
I just recreated the whole thing and it worked fine
I removed the s of AreaOfOperations in your Bind Attribute
[Bind(Include = "ShiftID,AreaOfOperation(s),UserName,StartTime")]
As far as i know, you can remove this parameter attribute alltogether.
This is only used when you only want to bind to certain Attributes of your view model.
However there was one mistake: you have to repopulate the Select List if your ModelState is not valid. Otherwise your
return View(shift);
does not have the data to render a new SelectList.
Another approach is that you put the data in your ViewModel and initialize it in the default constructor. Then you dont have to worry about the data or casting.

mvc begin form cant make routing override work

simnilar to the answer of this question
Html.BeginForm with html attributes asp.net mvc4
I have a viewmodel for a view that contains collections that are used to populate drop downs and lists. so i dont watn to return them, i just want to return the model object. Well actually i just want to return 4 fields in that model - but that's the next problem.
I've dodged that rpeviously by doing this appraoch but im having no luck unless i submit the entire viewmodel which on this form is ridiculous as 95% of info is discarded.
Anyway the problem i get here is that i cannot get the game event that is returned in the create post to be anything other than null. The gameEvent parameter on create is NULL.
Also kinda suprised i haven't been able to find a ton of info on this.
The controller:
public ActionResult Create()
{
...
var createEventViewModel = new CreateEventViewModel()
{
Places = places,
Characters = characters,
Event = new GameEvent()
};
return this.View(createEventViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Description,EventType,Duration")] GameEvent gameEvent)
{
...
}
The View:
#model Sisyphus.Web.Models.CreateEventViewModel
#{
ViewBag.Title = "Create Event";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create Event</h2>
<div class="row">
<div class="col-lg-8">
<section id="createEvent">
#using (Html.BeginForm("Create", "Event",
new
{
GameEvent = Model.Event
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Event.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Event.Description, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.Event.Description, 10, 30, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.Duration, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Duration, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Duration, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.EventType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(m => m.Event.EventType)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create Event" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
The Model:
public class GameEvent
{
public string Name { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
public EventType EventType { get; set; }
}
The viewmodel: (edited down have removed members that are irrelevant
public class CreateEventViewModel
{
public GameEvent Event { get; set; }
}
Edit:
Ok i just tried this
#using (Html.BeginForm("Create", "Event",
new RouteValueDictionary()
{
{"GameEvent", Model.Event}
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
Game event is now not null (All values in it are) - so not really any closer
Your inputs for postback are based on class CreateEventViewModel, for example
#Html.TextBoxFor(m => m.Event.Name, ...
#Html.TextAreaFor(m => m.Event.Description, ...
which would generate the following html
<input id="Event_Name" name="Event.Name" value=....
However the parameter of your post action method is typeof GameEvent, not CreateEventViewModel. If you inspect the Response.Form.Keys you will see Event.Name, Event.Description etc, but class GameEvent has properties Name, Description etc so the values cant be matched up by the ModelBinder
You need to change your post method to
public ActionResult Create(CreateEventViewModel model)
{
GameEvent event = model.GameEvent;
// do whatever with GameEvent
You should also remove new {GameEvent = Model.Event} from theHtml.BeginForm` method
Note I excluded the BindAttibute because I don't think its necessary in this case - you appear to want all the properties of GameEvent, and unless you create inputs for properties of Places and Characters, they will be null anyway, and since you are not accessing the other properties there is no mass assignment vulnerability.
Other alternative are to create the inputs manually so that the properties are correctly mapped, either direct html
<input name="Name" value=#Model.Event.Name />
<input name="Description" value=#Model.Event.Desciption />
or using helpers
var Description = Model.Event.Description;
#Html.TextBoxFor(m => Description)

Categories

Resources