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" />
}
Related
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.
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"
I want to give my TextAreaFor a default value from my database (a certain comment which they can edit). I use the #Value and I can see it in the html code (inspect element), but in the textarea itself it isn't visible.
My code:
#Html.TextAreaFor(a => a.description, new { Value = ViewBag.Description }
ViewBag.Description = adver.description;
textarea works differently then other input fields in HTML and because of this the value attribute doesn't actually do anything that you'd expect.
<textarea value="you won't see this">you will see this</textarea>
Versus a text field:
<input type="text" value="you will see this" />
Because of this you need to assign the text that you want to be shown in the textarea to the variable before creating the textarea.
Edit
Here's a more complete example for you:
public ActionResult MyAction()
{
var myDefaultDescription = ""; //Replace with whatever you initialize ViewBag.Description with
return View(new AdverModel{ description = myDefaultDescription });
}
This will cause the model to be initialized in the controller. The #Html.TextAreaFor() only uses fields and values from the model object that's passed into the view. The Model variable is read only in the view so you must initialize it in the controller and pass it to the view during the return from the controller.
This will cause that field to auto populate with the default value.
I am trying to submit when a dropdownlist changes by doing this
#using (Html.BeginForm("testAction", "FishingTrip"))
{
#Html.DropDownListFor(x => x.Day, Model.Days, new { onchange="this.form.submit();" })
}
This works fine but I am having problems (in other words don't know how) to get the option value on the server, can anybody help me with this ?
cheers
sushiBite
You could simply have your POST controller action take it as parameter and leave the default model binder to the binding:
[HttpPost]
public ActionResult TestAction(string day)
{
// The day parameter will contain the selected value
...
}
Or directly use the view model you used in the view:
[HttpPost]
public ActionResult TestAction(MyViewModel model)
{
// The model.Day parameter will contain the selected value
...
}
Just use the Day property of the model parameter, like any other property / editor.
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.