Object reference not set to an instance of an object MVC - c#

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)
{
....
}

Related

How to post objects in ViewModel?

I've got this viewmodel class with an integer Id and an object type.
public class MyViewModel
{
[Required]
public int MyId { get; set; }
public MyObject MyObject { get; set; }
}
MyObject Model is:
public class MyObject
{
[Key]
public int ObjId { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}
This controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyId, MyObject.Number, MyObject.Name")]MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
//do something!
}
The view is:
#model MyProject.Models.MyViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.MyId , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("MyId", null, "Select", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.MyId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MyObject.Number, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.MyObject.Number, new { #class = "form-control", type = "number", min = "1", max = "3", step = "1" })
#Html.ValidationMessageFor(model => model.MyObject.Number, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MyObject.Name , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.MyObject.Name , new { #class = "form-control", type = "number", min = "1", max = "3", step = "1" })
#Html.ValidationMessageFor(model => model.MyObject.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" formaction="#Url.Action("Create")" />
</div>
</div>
}
When I post data to controller, it recognize the MyId value but don't fill MyObject parameter. Any suggests to how to post an object with ViewModel?
Assuming that you have #model MyViewModel at the top of the view, there's no need to complicate it.
Make your submit on the view
<input type="submit" value="Create" class="btn btn-default" />
and change the signature of the post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
//do something!
}
The view model should then be bound.
Try below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyId,MyObject, MyObject.Number, MyObject.Name")]MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
}
You aren't including MyObject in Bind(include)

ASP.NET MVC ViewModel Property is null

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; }
}

Objects referencing each other in .NET MVC

I have the following two classes:
public class Game
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public Console Console { get; set; }
}
public class Console
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Company { get; set; }
}
After this I added a GameController with scaffolding. Now I am working on the Create view of the Game Object and I am experiencing the problem that the MVC HTML.helpers are rendering input fields for all of the properties of a Console object instead of the just the title (which I guess is logical, given the fact that the property is an actual Console object).
I still want the user to choose a console to be taken along for the Game Object being created, so I tried solving it in the following way:
<div class="form-group">
#Html.LabelFor(model => model.Genre, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Genre, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Genre, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Console, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Console.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Console.Title, "", 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>
I thought I would simply mention the title property of the model, but I'm still receiving input fields for all the Console properties in my Game's create view.
I understand this is simply the way MVC works. But how should I deal with something like this? I also tried things like:
Console: #Html.DropDownList("console", "All")
Of course the problem here is that the HTML form does not understand that this field is meant to be taken along as the console field for the game object being created. What is the correct solution for this problem?
EDIT:
I did the following:
Add a gameCreation viewModel:
public class GameCreation
{
public Game game { get; set; }
public SelectList Consoles { get; set; }
}
Then I built my ViewModel and passed it to view via controller:
public ActionResult Create()
{
var gc = new GameCreation();
gc.Consoles = cb.BuildList(db);
return View(gc);
}
Note that cb.BuildList(db) returns a SelectList item. Then in the view I tried:
<div class="form-group">
#Html.LabelFor(model => model.Console.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("console", gc.Consoles)
#Html.ValidationMessageFor(model => model.Console.Title, "", new { #class = "text-danger" })
</div>
</div>
This does not work, as gc is not known. I also tried passing the data via the ViewBag but this item is also not known here. Also I then received the error:
HTMLHelper has no applicable method named 'dropdownlist'. How can I access the data of the viewmodel which I passed?

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