MVC3 Passing Model to Controller - Receiving Null values - c#

I just started working with MVC3 a few weeks ago and being young in the programming ladder I'm still learning quite a bit. I've recently been working with Models, using TextBoxFor and other helpers to populate properties for my "Character" Model.
Essentially what I'm trying to do is define a model and then pass it to my controller, however any property that I have defined as a static value in my Model is being passed as a null value on runtime.
Below are some snippets of the parts needed to understand whats going on..
Character.cs - Model
// Instances of manipulated objects.
otReal db = new otReal();
public player newPlayer = new player();
public byte[] conditions
{
get
{
return newPlayer.conditions;
}
set
{
byte[] array1 = null;
array1 = new byte[16 * 12 * 3];
newPlayer.conditions = array1;
}
}
CharacterController.cs
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(Character c)
{
// Add a new character to the database via LINQ to Entities.
otReal.AddToplayers(c.newPlayer);
otReal.players.AddObject(c.newPlayer);
otReal.SaveChanges();
return View();
}
The only helpers I have in my View are the ones that the user actually needs to interact with. If I go into my controller and set the values there they will get set to the correct value and it will insert. Any help would be greatly appreciated!
Index.cshtml - View
#using (Ajax.BeginForm("Submit", new AjaxOptions { OnComplete = "done" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>New Character Information</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name, "Character Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TownList)
</div>
<div class="editor-field">
#* #Html.DropDownList("TownList", ViewData["TownList"] as SelectList)*#
#Html.DropDownListFor(model => model.TownList, ViewData["TownList"] as SelectList)
#Html.ValidationMessageFor(model => model.TownList)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Sex)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Sex, ViewData["sex"] as SelectList)
#Html.ValidationMessageFor(model => model.Sex)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Vocation)
</div>
<div class="editor-field">
#Html.DropDownList("VocList", ViewData["VocList"] as SelectList)
#Html.ValidationMessageFor(model => model.Vocation)
</div>
<p>
<input id="button" type="submit" value="Create" />
</p>
<div style="font-family: Tahoma; font-size: large" id="completeDiv" style="display: none;">
</div>
<span></span>
</fieldset>
}
Basically what I'm trying to do here is create my model that has a bunch of base values that every 'Character' will have in the database table. This way when a player creates his/her character he/she will be able to enter a Character Name, Sex, Class(Vocation), Starting Town and all the other values will be populated behind the scenes and passed to the controller for creation.

Assuming you're trying to set values on 'newPlayer', then you can replace this
public player newPlayer = new player();
with this
public player newPlayer { get; set; }
The default model binder will create everything from the form on post-back--there's no need to instantiate it in the model.

Created a constructor for my model and inside the constructor I set the default values via the newly instantiated player model. Temp fix until I read into ataddeini's solution. Thanks everyone!

Related

Cannot add object to context.DbSet. SaveChanges not working

I have this GET method in controller:
public ActionResult Reserve(int id)
{
ViewBag.staffID = new SelectList(context.Staffs, "staffID", "fName");
ViewBag.roomID = id;
return View();
}
the corresponding view:
#model _00002165.Models.Reservation
#{
ViewBag.Title = "Reserve";
}
<h2>Reserve</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<div class="editor-label">
<label>Room Number</label>
</div>
<div class="editor-field">
<input type="text" value="#ViewBag.roomID" readonly name="roomID"/>
</div>
<div class="editor-label">
#Html.LabelFor(model => model.fromDate)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.fromDate)
#Html.ValidationMessageFor(model => model.fromDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.toDate)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.toDate)
#Html.ValidationMessageFor(model => model.toDate)
</div>
<div class="editor-label">
<label>Staff:</label>
</div>
<div class="editor-field">
#Html.DropDownList("staffID", "Select Staff")
#Html.ValidationMessageFor(model => model.staffID)
</div>
<button type="submit">Reserve</button>
}
and I want to save the data from these inputs with these POST method:
[HttpPost]
public ActionResult Reserve(Reservation res)
{
if (ModelState.IsValid)
{
var customer = context.Customers.First(x => x.username == User.Identity.Name);
res.customerID = customer.customerID;
context.Reservation.Add(res);
context.Entry(res).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
}
This is giving me the following error:
Store update, insert, or delete statement affected an unexpected number of rows (0).
People suggest I add #Html.HiddenFor(model => model.reservationID) to my view.
But model.reservationID is empty.
How can I fix this?
Please help
You shouldn't be passing your data transfer objects to/from your views. Create a view model with a ToModel method that will return the DTO that you want. Adding to the context, you don't need to change the state.
Use this
context.Reservation.Add(res);
context.SaveChanges();
removing
context.Entry(res).State = EntityState.Modified;
If you are trying to update, pull the record from the database, make the changes and call SaveChanges
context.Entry(res).State = EntityState.Modified;
This line is not necessary - this would be if you are manually marking something as modified. Since you're adding a brand new entity, the correct value would be Added, and that should be there by default.
It seems that is it's trying to do an update instead of an insert, which results in that error.

MVC submit isn't returning all data

I'm writing an MVC app which ends up accessing a SQL database. On my edit page, I previously had every item available to be edited that is in the model. Recently I was asked to no longer allow the user to edit the primary keys. I did a quick change to change the primary key fields (in this example, there are 2 of them) from an EditorFor to a DisplayFor. The new view is this:
#model App.Data.Item
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Item</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
<strong>ID:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID)</p>
</div>
</div>
<div class="form-group">
<strong>ID2:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID2)</p>
</div>
</div>
<div class="form-group">
<strong>Description:</strong>
<div class="col-md-10">
<p>#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)</p>
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-primary">Submit <i class="fa fa-caret-right"></i></button>
</div>
</div>
</div>
}
It used to work with the full editing. Now the data is displayed properly, as expected. However, when submit is pressed, Null values are passed back to the controller for the values that are displayed.
These are the edit functions in my controller. ItemService.Edit() just saves the data to the server. It works correctly.
[Authorize]
public ActionResult Edit(string id)
{
if (id == null)
{
//return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
string[] vals = id.Split('|');
ItemAttribute itemAttribute = itemAttributeService.Find(int.Parse(vals[0]), vals[1]);
if (itemAttribute == null)
{
return HttpNotFound();
}
return View(itemAttribute);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Edit([Bind(Include = "ID,ID2,Description")]
Item item)
{
if (item.Description == null)
{
ModelState.AddModelError("Description", "Description cannot be null.");
}
if (ModelState.IsValid)
{
itemService.Edit(item);
return RedirectToAction("../Home/Index/");
}
return View(item);
}
Lastly, my data model:
public class Item
{
public int ID { get; set; }
public string ID2 { get; set; }
public string Description { get; set; }
}
Why is the data no longer being passed back to the second function, and how do I get it to pass correctly so that I can save it to the database?
You need to have an input element generated for the items that you want returned. Currently, you are only displaying two of your model elements and have no associated input with them. As a result, they will not be POSTed back to the server.
To get them to post to the server and not "editable" from a textbox, add a Html.HiddenFor() helper for each of the items that you need returned.
<div class="form-group">
<strong>ID:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID)</p>
<p>#Html.HiddenFor(model => model.ID)</p>
</div>
</div>
<div class="form-group">
<strong>ID2:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID2)</p>
<p>#Html.HiddenFor(model => model.ID)</p>
</div>
</div>
However, keep in mind that anyone can edit the HTML using Firebug or Chrome console tools and submit any value that they want for any field, even if you did not want to change it. Be sure that when you are persisting the changes to the database, you are NOT including these fields as part of the update.
Try this, just before this line of code:
#Html.DisplayFor(model => model.ID)
put in this for debugging:
#Html.LabelFor(model => model.ID)
Tell us what you see...
If you see the label then check your controller, in particular the parameter it takes on the post. It should take and Item of type ITEM per your model.
Before the controller receives the data MVC has to try to populate the data... It converts name/value pairs to model types with values secretly. If you don't see any data after you are in the controller it's usually because the names were not found!

Issue with EditorTemplates

ASP.Net MVC 4.0 based Scenario:
I need to render a set of Widgets[aka User Control with sets of custom fields] on a screen.
The ViewModel for this Widget is something like this -
Public Class Widget
{
public string Header {get;set;}
//Note the data type here. It causes issues with rendering Widget itself.
public object ActualContent {get;set;}
public string Footer {get;set;}
}
At, run-time, say I want to render 2 widgets. Then all I need to do is create the instances as shown below :
Widget w1 = new Widget()
{
Header ="PatientDetails",
ActualContent = new Patient ()
{
FirstName = "ABC",
LastName = "XYZ"
}
Footer = "PatientDetails
};
Widget w2 = new Widget()
{
Header ="Address-Header",
ActualContent = new Address ()
{
ZIPCode = "123456",
Extn = "1234"
}
Footer = "Address-Footer"
};
The cshtmls are :
Main Page i.e Widget.cshtml
#using Widgets.Models
#model List<Widget>
...
<div class="widget">
<div class="header">
#Model.Header
</div>
<div class="body" >
#Html.EditorFor(model => Model.ActualContent)
</div>
<div class="footer">
#Model.Footer
</div>
</div>
...
Patient.cshtml in ~\Views\Shared\EditorTemplates
#model Widgets.Models.Patient
<div class="editor-label">
#Html.LabelFor(model => Model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.FirstName)
#Html.ValidationMessageFor(model => Model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => Model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.LastName)
#Html.ValidationMessageFor(model => Model.LastName)
</div>
Address.cshtml has a similar implementation.
Note, the property ActualContent, is of type object. It can be assigned an instance of any UserControl [Patient,Address,etc] at runtime.
Also, each of the UserControls has a corresponding strongly typed cshtml view defined inside the EditorTemplates.
Issue :
Now the issue is when I try to load the Widgets, I can see both the widgets getting rendered but only displaying the Header & Footer values.
The portion correponding to the ActualContent property, as shown above is not rendered at all, despite the fact that, while debigging, I can see that
corresponding .cshtml for Patients & Address are getting accessed!
I feel there might be something that I may have missed out on.
It would be nice if someone can get this working for me. Thanks in advance.
Sandesh L.
Try to access your Editor template like this,
#Html.EditorFor(m => m.ActualContent, "_YourEditorTemplate")

How to change dynamically the value of a label in MVC 3?

I'm developing a project in MVC 3 (CRUD)... I want to create a reservation for a tennis court...
Page:
So, when I type a "Start time" ("heure de début" in French) I want to increase the "FinishTime" in red ("heure de fin" in French) dynamically... If it's a simple match increase by 1:00 and if not by 2:00...
I'm beginner in MvC3 so I have no idea how to do that... Of course, I'm not request that you make my work but the right method to do that and if it's possible an example...
View:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Reservation</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Date)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Date)
#Html.ValidationMessageFor(model => model.Date)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.StartTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StartTime)
#Html.ValidationMessageFor(model => model.StartTime)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Simple)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Simple)
#Html.ValidationMessageFor(model => model.Simple)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.FinishTime)
</div>
<div class="editor-fieldFinishTime">
#Html.DisplayFor(model => model.FinishTime)
#Html.ValidationMessageFor(model => model.FinishTime)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Customer.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Customer.Name)
#Html.ValidationMessageFor(model => model.Customer.Name)
</div>
<div class="editor-label">
#Html.Label("Terrain N°")
</div>
<div class="editor-field">
#Html.DropDownListFor(c=>c.TennisCourtID,ViewBag.TennisCourtID as SelectList)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I've forget to precise that all clubs can have different schedule... For example:
Club 1:
Simple: 1:00
Double: 2:00
Club2:
Simple: 1:30
Double: 2:30
So in my database's table TennisClub I have an attribute SimpleGameTime and DoubleGameTime... Sorry :(
Bind to the change event of the Input for Start time (check its ID in rendered HTML):
$("input#TheId").bind("onchange", onStartTimeChanged);
Define a function to parse a time, see this post here on SO.
In the onStartTimeChanged function get the check box state (update with correct jQuery selector for the control):
var checked = $('input[type=checkbox]').is(':checked');
Then simply add the parsed date with the proper offset and write that back to the End Time control using toLocaleTimeString().
Note: I assumed you'll use JavaScript to perform calculations on client-side. If you prefer to do everything on server-side you have to add an AJAX-enabled method in your controller with the start time and the flag as parameters. In your onStartTimeChanged function you have to call it and asynchronously update the end time when the function return. If there's a lot of logic (or it's not trivial) I prefer the server-side solution with AJAX.
For example (I didn't check the code so use it cum grano salis) to call a server-side method with POST (you have to pass parameters, as alternative you may use getJson with GET only but you have to work with routing):
function ComputeEndTime(matchStartTime, isSimpleMatch) {
var url = '<%= Url.Action("ControllerName", "ComputeEndTime") %>';
$.post(url, { startTime: matchStartTime, isSimple: isSimpleMatch },
function(data) {
return data.endTime;
}
);
}
And your server side function:
[AcceptPost]
public ActionResult ComputeEndTime(string startTime, bool isSimple)
{
// Perform your calculations then convert to string
string calculatedEndTime = "";
return Json(new { endTime = calculatedEndTime });
}

Fail to submit a list of object to the model binding using ICollection

i have added extra three input fields to my view to enable the system admin to submit four objects at the same time instead of one object at a time; the view looks as the following:-
#model Elearning.Models.Answer
#{
ViewBag.Title = "Create";
}
<div id = "partialWrapper">
#using (Ajax.BeginForm("Create", "Answer", new AjaxOptions
{
HttpMethod = "Post",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "incrementanswer",
OnSuccess = "removePartial",
LoadingElementId = "progress2"
}))
{
<div id = "returnedquestion">
#Html.ValidationSummary(true)
<fieldset>
<legend>Answer here</legend>
<ol>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[0].Description")
#Html.ValidationMessageFor(model => model.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div>
</li>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[1].Description")
#Html.ValidationMessageFor(model => model.Description)
</div> <div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div> </li>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[2].Description")
#Html.ValidationMessageFor(model => model.Description)
</div> <div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div> </li>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[3].Description")
#Html.ValidationMessageFor(model => model.Description)
</div> <div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div> </li>
<ol>
</fieldset>
<input type= "hidden" name = "questionid" value = #ViewBag.questionid>
<input type= "hidden" name = "assessmentid" value = #ViewBag.assessmentid>
<input type="submit" value="Add answer" />
</div>
}
</div>
and the following Post Ation Method:-
[HttpPost]
public ActionResult Create(int questionid, ICollection<Answer> answer)
{
if (ModelState.IsValid)
{
foreach (var a in answer){
repository.AddAnswer(a);
repository.Save();
}
return PartialView("_details2",answer);
}
return View("_details2",answer);}
and last thing the _details2 partial view which contains the newly added objects:-
#model IEnumerable<Elearning.Models.Answer>
#{
ViewBag.Title = "Details";
}
#foreach (var m in Model)
{
<tr id = #m.AnswersID>
<td>
#Html.DisplayFor(modelItem => m.Description)
</td>
<td>
#*#Html.DisplayFor(modelItem => Model.Answer_Description.description)*#
#ViewBag.Answerdesription
</td>
<td>
#Ajax.ActionLink("Delete", "Delete", "Answer",
new { id = m.AnswersID },
new AjaxOptions
{
Confirm = "Are You sure You want to delete this Answer ?",
HttpMethod = "Post",
UpdateTargetId = #m.AnswersID.ToString(),
OnSuccess = "removePartial2"
})
</td>
</tr>
}
but the above is not working nethier the objects will be added nor the partial view will be returned , so how i can solve this issue???
BR
You bind your view to a single Elearning.Models.Answer object, how are you expecting to get a collection of Answers as a parameter in your Action? The default model binder will try to bind your view fields to the parameter in the Action but it won't be able to as it's a collection.
What you could try to do is to bind your View to a List<Elearning.Models.Answer> and feed it 4 empty Answer objects, then you can create a strongly typed Partial view that expects one Elearning.Models.Answer, add the Partial in a foreach and, when posting the form, expect that the default model binder does it work and fill your action method with a brand new List of Answer objects.
As an alternative, you can create a View Model object that contains the fields in your View, including those 4 description fields. You add them as Html.TextboxFor to bind each of them to a different property in the View Model. Then you can collect them in your action, provided you change it to public ActionResult Create(int questionid, ViewModelAnswer answer)
Does it make sense?
Your model should contain a list and code like this:
#for (int i=0; i < Model.FavouriteMovies.Count; i++) {
#Html.LabelFor(model => model.YourList[i].Field)
#Html.EditorFor(model => model.YourList[i].Field)
#Html.ValidationMessageFor(model => model.YourList[i].Field)
}
which will print something like:
<label for="YourList_0__Field">Field Name</label>
The Field Name field is required.
And receive the model back in your controller:
public ActionResult MyAction(MyModel model)
{
// First element?
model.YourList[0].
}

Categories