Why is my MVC ViewModel member overridden by my ActionResult parameter? - c#

Is this a bug or a feature?
All code below has been simplified for the sake of brevity and easy replication and does not actually do anything useful other than highlight the behavior.
I have a class that includes an int named ID:
public class FooterLink
{
public int ID { get; set; }
}
In my controller, I have an Edit actionresult that takes a parameter called 'id':
public ActionResult Edit(int id)
{
return View(new FooterLink() { ID = 5 }); //notice that I am explicitly setting the ID value here.
}
in my index view I have a link to the edit action that specifies the 'id' parameter:
<%= Html.ActionLink("Edit", "Edit", new { id = 1 })%>
In my view I have a couple of text boxes:
<%= Html.TextBox("ID", Model.ID)%>
<%= Html.TextBox("Blah", Model.ID) %>
which renders the following in HTML:
<input id="ID" name="ID" type="text" value="1">
<input id="Blah" name="Blah" type="text" value="5">
Notice that the input with an id of "ID" is getting its value not from the Model like I am telling it to...but from the parameter that was supplied to my ActionResult.
This behavior is the same with Html.TextBoxFor, Html.Hidden, Html.HiddenFor, etc.
What gives?
EDIT: I meant to update this a long time ago but never got around to it. The reason that this is happening is because the ModelState will have an "id" entry with the value of the "id" parameter in the method and the MVC ViewEngine first checks the ModelState when filling in Html helpers instead of the Model. In order to work around this, you can simply add the following line in your controller just before the return:
ModelState.Remove("id");
Now the <%= Html.TextBox("ID", Model.ID)%> will get the value from the Model and not the ModelState. Problem solved.

That's how html helpers work. They will first look if there's ID parameter in the request url and use that value instead of the one specified as second argument in the model.

Related

how can I have multiple forms each generated from a list of items?

In an MVC application, I have a list of exam questions and I want to present a small number of them to the user on the same page but where each answer can be submitted separately.
So my page looks like this ....
The view code is ....
#model List<QuestionResponseVM>
#for (int i = 0; i < Model.Count(); i++)
{
using (Html.BeginForm("CheckQuestions", "Checks", FormMethod.Post, new {questResponses = Model[i] }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model[i].QuestionID)
<tr>
<td width="35%">
#Html.Raw(Model[i].QuestionText)
#Html.HiddenFor(model => model[i].QuestionText)
</td>
<td>
#Html.TextAreaFor(model => model[i].Response, new { #name = "DisplayTextEdit", #id = "DisplayTextEdit", #rows = 1, #cols = 80 })
</td>
<td width="30%">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
</td>
</tr>
}
}
My problem is I can only get data returned to the POST method for Question 1.
Here is the Controller Code ....
public class ChecksController : Controller
{
public ActionResult CheckQuestions()
{
return View(LoadQuestions());
}
// POST: Checks
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(List<QuestionResponseVM> questResponses)
{
List<QuestionResponseVM> testList = new List<QuestionResponseVM>();
if (ModelState.IsValid)
{
testList = LoadQuestions(questResponses[0].QuestionID, questResponses[0].Response);
}
return View(testList);
}
private List<QuestionResponseVM> LoadQuestions (int _QuestionID = -1, string _Response = "")
{
List<QuestionResponseVM> thisList = new List<QuestionResponseVM>();
thisList.Add(new QuestionResponseVM()
{
QuestionID = 1,
QuestionText = "Question 1",
Response = (_QuestionID == 1 ? _Response : "")
});
thisList.Add(new QuestionResponseVM()
{
QuestionID = 2,
QuestionText = "Question 2",
Response = (_QuestionID == 2 ? _Response : "")
});
thisList.Add(new QuestionResponseVM()
{
QuestionID = 3,
QuestionText = "Question 3",
Response = (_QuestionID == 3 ? _Response : "")
});
return thisList;
}
}
If the Controller POST method has a parameter of QuestionResponseVM questResponses which is what I was expecting (hoping for) then null is returned from the view no matter which "Save" button is clicked.
However, if I change the parameter to a list (i.e. List<QuestionResponseVM> questResponses) then the "Save" button for Question 1 returns a list with a single item and correct data. But, any other "Save" button (e.g. Question 2 or Question 3) returns a null list.
The behaviour for scenario 1. seems counter-intuitive to me since the "Begin Form" is set to return a single model item (instance of the model) i.e. "Model[i]".
And in scenario 2., I just don't understand why it works for the first form ("Save" button) but not for the others.
I don't believe I should need to use JScript or AJAX to do this.
But clearly, I am not "connecting some dots" here.
Can someone please explain my observed behaviour and maybe give me a push in the right direction to meet this requirement.?
I would greatly appreciate any help.
Before going through your questions, I don't get what new {questResponses = Model[i] })) is doing in your forms:
using (Html.BeginForm("CheckQuestions", "Checks", FormMethod.Post, new {questResponses = Model[i] }))
{
...
}
Model[i] is a complex object. All you got there was the name of the object:
Q1: If the Controller POST method has just a single parameter
Since you're using a for loop to generate each form and inputs within the form, the name of those inputs will be in the forms of [INDEX].NAME:
By default, the model binding will bind those inputs (QuestionId, QuestionText and Response) to a matching object. QuestionResponseViewModel indeed matches that. The problem is [INDEX]. prefix.
In order for the default model binding to work, the parameter name you declare in the POST method has to be called [INDEX], i.e., [0] for the first form, [1] for the second form and so on:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(QuestionResponseVM [0])
{
...
}
But you know we can't declare anything like that in C#.
The Fix for Q1
Instead of using the regular for loop, you can use foreach to generate each form. In that way, you get rid of the need for naming a parameter that's changing for each form.
Another "GOTYOU" here is that the parameter in the controller has to match the variable you declared in the for loop for each QuestionResponseViewModel:
#foreach (var qrVM in Model)
{
using(Html.BeginForm("..."))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(x => qrVM.QuestionId)
<tr>
<td>
#Html.DisplayFor(x => qrVM.QuestionId)
#Html.HiddenFor(x => qrVM.QuestionId)
</td>
...
</tr>
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(QuestionResponseVM qrVM)
{
// If you name the parameter something else, it won't bind!
...
}
If you think about it, that makes sense, because you know the form will post data with keys like qrVM.QuestionId, qrVM.QuestionText back to the server. The default model binding will search for a model that has those properties and is named qrVM.
Q2: Change parameter to a list
When the first form posts back to the server, the form data in the request body will look like:
[0].RequestionId: 1
[0].RequestionText: Question 1
[0].Response: xxx
MVC model binding is still smart enough and thinks you're posting the first item of the list you declared. Hence you will see List<QuestionResponseVM> questResponses capture the correct data for the first form.
Well what about the second and third form? If you submit the data on the second form for example, the form data in the request body will look like:
[1].RequestionId: 2
[1].RequestionText: Question 2
[1].Response: xxx
MVC model binding sees it as the 2nd item of the list, but where's the 1st item? And it got confused so it couldn't bind the data to the parameter. Hence you will see NULL from the parameter List<QuestionResponseVM> questResponses.
My 2 cents
You actually cannot put a form inside a table or between table rows like this. It's considered as invalid HTML structure. It's never a good idea to use tables as structures to display data on the page anyway. Instead, You can use Bootstrap's row and columns.
I don't know why or what made you think you shouldn't need AJAX. Your case is like the best scenario to go with an AJAX approach! For example, with AJAX, the user can save each question's response individually. The page doesn't have to be refreshed.
Your save button on Question 1 is submitting the form to the controller. You will need to either have one Save/Submit button at the end of a set of questions and utilize the FormCollection object or spend time setting up JQuery/Ajax for click events on each button and removing the form element. You could have a bit of both if the button at the bottom becomes a 'Next' and then submits to a controller to get the next set of related questions.

How can I pass multiple parameters to Actionlink() while working with entity framework on c#/html

In my project i am working with MVC design and using the database with Entity Framework.
I wanted to update some cells in my database, but I am not sure how to pass them to the controller to update.
On the Update View page, the user can change the content of the cells using
<td>
<div contenteditable>
#Html.DisplayFor(Model => Model.HeadLine)
</td>
But when I try to send the updated data to the controller I can only understand how to send an id for the Model(because I cannot pass the Model itself).
I am not sure what I am doing wrong - here I am trying to pass an ID so the controller would know what to change and the new name - I get a compilation error.
<p>
#Html.Action("Update This Post >>", "Update", new { param1 = Model.ID, param2 = Model.Author });
</p>
The controller signature is :
[HttpPost]
public ActionResult Update(int id, string author)
Try this :
<p>
#Html.ActionLink("Update This Post >>","YourController", "Update", new { id = Model.ID, author = Model.Author });
</p>
This eventually solved the problem -
https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/introduction/examining-the-edit-methods-and-edit-view
I am not sure whether you are trying to pass a reference type or value type variables in the action. you should not pass the reference type but the value types for the anonymous object to pass route parameters. Can you try the below code
#Html.ActionLink("Update This Post >>", "Update", new { id= Model.ID, author= Model.Author });

How to assign the value from an HTML Element as value of a variable defined in MVC Modal class in ASP.NET?

I am exploring CRUD Operations in MVC using ADO.Net in order to store the employee record and manipulate them as per necessity.
As I defined the table columns in Model, the MVC generates the view accordingly which generated HTML helper but not normal html elements. So now I have this html element:
<input type="text" id="datepicker">
This text box gets a string of datetime from the datepicker jQuery API. Now I have defined it in view model as
[Display(Name ="Travel Days")]
public string Date { get; set; }
Now I want to perform an operation to pass the value Date to the database. For this I have performed certain operations here:
public bool AddEmpInfo(EmpInfoModels Info)
{
using (connect = new SqlConnection(ConfigurationManager.ConnectionStrings["CrudMvc"].ToString()))
{
SqlCommand com = new SqlCommand("AddEmpInfo", connect);
com.CommandType = CommandType.StoredProcedure;
com.Parameters.AddWithValue("#Date", Info.Date);
connect.Open();
int i = com.ExecuteNonQuery();
if (i >0)
{
return true;
}
else
{
return false;
}
}
}
As I debug the process, the value submitted from the view class is not received with the Info.Date.
So my problem is to bind the value entered from a plain html element and send it as a parameter to the stored procedure to perform database operations.
Add name to bind with Model
<input type="text" id="datepicker" name="Date">
MVC/Web Api Model binding can happen in three ways:
From Query string
Consider you are passing values in query string - http://your_domain/controller_name/youraction?id=100 - the id will be bound to your action parameter named "id", for eg, public ActionResult YourAction(int id).
You can even bind an object using query string values, for eg, url - http://your_domain/controller_name/youraction?id=100&name=your_name&age=30 & action public ActionResult YourAction([FromUrl]Employee employee) ,provided Employee class has id, name and age properties
From Route values - self explanatory, from the values you have specified in routes. In case of attribute routing, if we take the first scenario above, the action would look like:
[Route("YourAction/{id}')]
public ActionResult YourAction(int id)
From FormData - Putting it in simpler terms, when a form is submitted, model binder finds the elements whose name matches the property names and binds the value. For eg, if you want to bind an Employee object (which has Id, Name and Age properties) on form submit to an action, say,
[HttpPost]
public ActionResult YourAction(Employee employee)
you should have 3 html elements with the corresponding names like, <input type="text" name="Name" /><input type="hidden" name="Id" /> <input type="number" name="Age" />
This is exactly what #Html helpers do, they will assign the "name" attribute of the element to match the Model property.
So in your case, to bind value to "Date" property, you should have a html element with name="Date"

Different values from model

I have this two lines in a Razor template:
#Html.Hidden("step", Model.Step)
<p>#Html.Label(Model.Step.ToString())</p>
And they produce two different values:
<input data-val="true"
data-val-number="The field Step must be a number."
data-val-required="The Step field is required."
id="step"
name="step"
type="hidden"
value="0">
<p>
<label for="">1
</label>
</p>
How is this possible?
Property Step is of a type Int32 and is incremented every POST action.
EDIT:
#model ***.WebUI.Models.OrderViewModel
#{
ViewBag.Title = "New order";
}
<h2>
New order</h2>
#using (Html.BeginForm())
{
#Html.Hidden("step", Model.Step)
<p>#Html.Label(Model.Step.ToString())</p>
<div>
//other inputs
</div>
}
You have not shown you POST method, but based on "Property Step is of a type Int32 and is incremented every POST action." I assume it looks something like
public ActionResult Edit(OrderViewModel model)
{
model.Step = model.Step + 1;
return View(model);
}
When you post a model, the models values are added to ModelState (along with any ModelState errors) so in the first post, the ModelState value of Step will be 0. Html helpers use ModelState values for binding (if one exists) so when you return the view #Html.Hidden("step", Model.Step) will bind to the value of 0 (not the models value of 1). The reason for this behavior is explained in this answer.
The correct approach is to follow the PRG pattern (redirect to the GET method, passing a parameter indicating the Step value and initialize a new OrderViewModel), however you can make this work by clearing ModelState so that the Html Helper will use the model value.
public ActionResult Edit(OrderViewModel model)
{
ModelState.Clear(); // clear all model state
model.Step = model.Step + 1;
return View();
}
However, use this with caution. ModelState.Clear() removes all errors as well.

MVC 4 - hand over changed value from View to Controller

If a view is ment to allow editing of only one property but all other properties are being displayed (DisplayFor, non-editable) as well, what is a good way to design the handing-over of changed value to the controller?
Right now I have hidden-input-fields for all properties that are displayed with DisplayFor and the controller gets the full object passed.
This is pretty much ineffecient and I know it would suffice to post only the ID of that object and the changed value.
The user can input the value to be changed like this:
#Html.EditorFor(model => model.Verkaufspreis)
#Html.ValidationMessageFor(model => model.Verkaufspreis)
I could pass the ID of the object like this
#Html.ActionLink("Edit", "Edit", new { id=Model.ID })
But how would I pass the value that was changed? Thank you for your input.
if you want to get a value and you do not want to return the model knowing the name of the value you can use FormCollection
[HttpPost]
public ActionResult (FormCollectio collection)
{
string Verkaufspreis1=collection["Verkaufspreis"].ToString();
}
MVC allows all kinds of binding, for instance you could go
[HttpPost]
public ActionResult (int ID, String Verkaufspreis)
//Have to be the same propery name as your model
{
//Get original object with the ID
//change the "Sell of Stock" field
}
This would dynamically pass the ID and Verkaufspreis as parameters.
This would allow you to only have the ID and the value needing to be changed, as you would be getting the rest from your database(or wherever) on postback, only updating the value that is necessary.
You could do the entire model as a parameter, although this would mean you would have alot of empty values if you're not passing them to the client.
Instead of putting a lot of hidden inputs in your form, you can do this.
Simply post the changed values and the id to the action method. Read the full entity from your data source and update the new values and save it back.
[HttpPost]
public ActionResult Update(CustomerViewModel model)
{
Customer customer=repositary.GetCustomerFromID(model.ID)
customer.DisplayName=model.DisplayName;
repositary.SaveCustomer(customer);
return RedirectToAction("ProfileUpdated");
}
In this case, you need to post only the ID and DisplayName properties from the form
#model CustomerViewModel
<h2>Update Customer details</h2>
#using(Html.Beginform())
{
Display Name : #Html.TextBoxFor(x=>x.DisplayName)
#Html.HiddenFor(x=>x.ID)
<input type="submit" value="Save" />
}

Categories