Changes in the form not sent to the controller - c#

I'm trying to send back the contents of the input field as follows.
#model Bike
#using (Html.BeginForm("BikeStore", "Home", FormMethod.Post))
{
<input type="text" value="#Model.Color" />
<input type="submit" value="Save"/>
#Html.ActionLink("Cancel", "Bikes", "Home")
}
The action and the model are declared as follows.
public ActionResult BikeStore(Bike bike)
{
...
return RedirectToAction("Bikes");
}
public partial class Bike
{
[Key] public Guid Id{get; set;}
[Required, StringLength(999)] public string Color { get; set; }
}
I'm hitting the breakpoint in the method BikeStore but bike passed in is empty, i.e. it's not null but all the strings are, the guids are 00..00 etc.
I've tried different variable types. I also tested FormMethod.Get and (not at the same time, of course) adding HttpPost attribute. No luck.

Asp.Net MVC binder system uses the name of the input elements to bind to the appropriate property or parameter. So, change this line:
<input type="text" value="#Model.Color" />
to:
#Html.TextBoxFor(m => m.Color)
This will generate following html(for example):
<input type="text" id="Color" name="Color" value="Black" />
Keep in mind that, you can use another helper which offers you to hard-type the name of the value:
#Html.TextBoxFor("Color")
Or, you can write plain html as you did and add name attribute, but let Asp.Net decide what must be the name of the element.
<input type = "text"
name = "#Html.NameFor(m => m.Color)"
value = "#Model.Color" />
If we want to summarize the result of the answer, then let's write Pros and Cons of each version:
Strongly typed version - These helpers can be used only with strongly typed views. The HTML generated by these helpers is not any different, but we use the strongly typed helper methods in our projects because they reduce the chances of causing an error by mistyping a property name.
Hard-typed version - The string argument is used to search the ViewData, ViewBag, and view model to find a corresponding data item that can beused as the basic for the input element. So, for example, if you call
#Html.TextBox("DataValue"), the MVC Framework tries to find some item of data that corresponds with the key DataValue. The following locations are checked: ViewBag.DataValue and Model.DataValue.
The first value that is found is used to set the value attribute of the generated HTML. (The last check, for #Model.DataValue, works only if the view model for the view contains a property or field called DataValue.)
If we specify a string like DataValue.First.Name, the search becomes more complicated. The MVC Framework will try different arrangements of the dot-separated elements, such as the following:
• ViewBag.DataValue.First.Name
• ViewBag.DataValue["First"].Name
• ViewBag.DataValue["First.Name"]
• ViewBag.DataValue["First"]["Name"]
Also keep in mind that, the first value that is found will be used, terminating
the search.

Related

.NET Core submit passes all the data to the controller except for the id

.NET Core 3.1 with EF Core, trying to update data loaded into a form, but for some reason it sets the Id = 0 on submit, and I have no idea why. Or, if I explicitly set the id value, the controller gets a null value for all the records. Importantly, it's a list of entities that I'm sending down to the db.
I can create multiple records just fine using the same form. But if I load the form with existing records, and want to make changes, I can't. The Id of each record gets set to zero, if I don't put the Id in for each record. If I do put the Id of each record, the form seems to submit correctly, as I'm looking at Chrome DevTools and see that the values are there, including the Id, but then the controller gets that data, and it's null...
ETA It looks like the 41st row is causing the issue when I do submit the form with the Id included for each row. What I don't know is if that means 41 rows is too many, or there is some issue with the data itself, which means the controller won't pick up any of it...but neither of these make sense, since the form loads the 62 rows just fine.
Here's a simplified version of it, with key pieces included so it makes sense.
My model:
#model List<EvaluationsModel>
My form:
#{
var createOrUpdate = "Update";
if (Model[0].IsCreate)
{
createOrUpdate = "Create";
}
}
<form id="evalForm" asp-action="#createOrUpdate">
#for (var i = 0; i < Model.Count; i++)
{
<textarea asp-for="#Model[i].Comments" rows="5" cols="75"></textarea>
<input asp-for="#Model[i].Id" type="hidden" />
}
<input type="submit" id="submitButton" value="#createOrUpdate" class="btn btn-primary" />
My controller method:
public IActionResult Update(List<EvaluationsModel> evalRows)
{
_dostuffWith(evalRows);
return Redirect(toGet);
}
Sometimes asp-for in the hidden field is not working properly for some reasons, and I usually use value too
<input asp-for="#Model[i].Id" value"#Model[i].Id" type="hidden" />
IMHO, you have to add Id to your model, in case if it is not update, but create
public ViewModel
{
public List<EvaluationsModel> EvaluationsModels {get;set;}
public int Id { get; set;}
{
and add hidden field Id to a view
#model ViewModel
....
<form id="evalForm" asp-action="#createOrUpdate">
<input type="hidden" asp-for="Id" value="#Model.Id" />
#for (var i = 0; i < Model.EvaluationsModels.Count; i++)
action
public IActionResult Update(ViewModel viewModel)
The original problem is that there were checkbox entries that were being doubled, and adding the Id value broke the submission of the form. I hadn't included the checkbox in the original code I submitted because I didn't think that was connected to the issue.
After lots of searching, this is the (final) answer I've come up with:
Upgrade to dotnet core 5.0 or greater.
In Configure Services in Startup.cs, add this line:
services.Configure(options => options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None);
The CheckBoxHiddenInputRenderMode has three options; "none" will prevent the 2nd set of checkboxes from materializing. The other two options will determine where the 2nd set gets generated: either at the end of the form (default), or inline.
I haven't found any documentation showing how to implement this behavior as of this writing.
The problem was twofold:
The tag helper for a boolean input (aka, a checkbox, and not included in my original question) in 3.1 and before, duplicates its entries, so for each <input asp-for="#Model[i].IsCorrect" /> item, I get two entries. (The reason MVC does this is to make sure that something gets submitted, because the HTML5 spec says that if a checkbox isn't checked, don't submit anything at all. MVC make sure that something instead of nothing gets submitted.)
I saw this, but it didn't look strange to me because of some other things I had going on. So the form was sending all the regular entries, plus two of the bool values (instead of just one, like all the others). Adding the id meant I was sending more than the model was expecting.
The controller was only receiving null after getting too many rows/entries, because I was sending the duplicated boolean/checkbox value, and then the Ids. There was no error reporting about this at all. Nothing saying that the form was oversubmitting, or that there were too many entries. This part makes me wonder if there is a bug in the dotnet core framework. I'm not sure if having it fail silently like this is intentional, or was an oversight, if there is a way to check.
But problem solved.
References: the commit for the new options.

How to use HiddenFor

I am wondering how to properly use the HTML helper method HiddenFor when I am dynamically pulling in data to populate my view.
Prior to trying to use HiddenFor, I was using the regular hidden element using the following code:
#Html.Hidden("Answers["+i+"].FormEntryId", entry.FormEntryId)
This produces the following HTML:
<input id="Answers_0__FormEntryId" name="Answers[0].FormEntryId" type="hidden" value="d318afa2-42ba-4205-9f8a-9d7e6ad59ea4">
As you can see, it is quite fragile in that it relies on a string literal. Myself and another developer then decided to try and using HiddenFor as follows:
#Html.HiddenFor(x => x.Answers[i].FormEntryId, entry.FormEntryId)
This produces the following HTML, notice the empty value field:
<input data-val="true" data-val-required="The FormEntryId field is required." id="Answers_0__FormEntryId" name="Answers[0].FormEntryId" type="hidden" value="">
With x being a stand-in for our ViewModel. However, once we switched to this approach, we hit a snag where our id value is not populated what-so-ever. Thus, I am wondering, what is the correct way to use HiddenFor, and if it is even possible to do so when dealing with a dynamic view. The view model looks as follows:
You are actually using wrong overload of HiddenFor helper method, you are already iterating on the Answers collection, you just need to use this overload which just takes expression as a single parameter like:
#for(int i=0; i<Model.Answers.Count; i++)
{
#Html.HiddenFor(x => x.Answers[i].FormEntryId)
}
This will itself take care of generating correct id and name property of input elements so that their values are posted back in Model/ViewModel object back to controller action.
This should work for you fine.
SideNote:
If you have strongly typed view with a Model/ViewModel, you should always be using the Stringly Typed Helper Methods like TextBoxFor,HiddenFor,DropDownListFor etc instead of normal helper methods, as those require a little more work, and strongly typed ones will take care of posting back the new values for input elements via Form post.
Hope it helps!

MVC Http Post Action With Variable Parameters

I am having a bit of an issue with an MVC action that I am currently implementing. Simply put, I have a table of users with roles assigned to them. The number of roles per user can range from 1-3 and I am not sure how to reflect this in a POST action of MVC when editing them. To get the roles into the view I use a list of class AccountMapping that contains the role name and ID:
public ActionResult EditUser(User user, EditUserModel model)
{
// Omitted model assignments
model.AccountMappings = _reportUsersRepository.GetAccountMapping(user.UserId).ToList();
return View(model);
}
In the View (using Razor):
#foreach (var item in #Model.AccountMappings)
{
<div>
<p>#item.Target_Type.Replace('_', ' ')</p>
<input name="#item.Role_Name" value="#item.Target_Id" type="number" placeholder="Role Id" required />
</div>
}
How would I go about structuring the POST action to take into account these inputs? I am using the same model for the POST action and I am aware that the AccountMapping list would contain no results on postback. I am not sure about using the FormsCollection method because you need to know the name of the keys in order to retrieve the values. Am I missing something obvious?
Many Thanks.
Expanding on my comment, If you use indexing instead of foreach, then it can correctly name and map the individual items in a collection back to the posted input collection. e.g.:
#for (int i = 0; i < Model.AccountMappings.Count(); i++)
{
<div>
#Html.EditorFor(x=>Model.AccountMappings[i])
</div>
}
If you look at the generated HTML you will see the inputs are named uniquely based on their index position.
These will be automatically mapped back into a receiving collection (assuming your postback takes the same object type as your view model that was passed to the view).

How to make Html.TextBox(string name) render sanitized input?

I am using ASP.NET MVC4 with .NET Framework 4.5. I have a controller action that accepts a model of one type with a property named 'Name' but renders a view using a model of another type. I am still able to use #Html.TextBox("Name") and #Html.ValidationMessage("Name").
I want the textbox to display the sanitized input, that is, the input without leading/trailing/extra spaces the user may have entered. The setter for my model sanitizes the value for me, and I am successfully obtaining the sanitized value using the getter within the controller action. It's just that upon submitting the form, the textbox still displays the unclean input.
Is there some mechanism I am missing? Is the #Html.TextBox(string name) helper looking at the raw request data and not the model? If so, how come the validation message is working?
Update
I have just tried defining a new view model that includes my textbox field so I could hopefully just use the #Html.TextBoxFor helper. Everything is still working as it was after a re-build, I am still not getting sanitized input appearing in the textbox. I still don't know a solution for this.
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult MyAction(MyViewModel model)
{
if (this.ModelState.IsValid)
{
using (var service = new MyService())
{
model.MyResults = service.DoSomething(model.MySanitizedProperty);
}
}
return this.View("MyView", model);
}
Then, in "MyView":
#Html.TextBoxFor(m => m.MySanitizedProperty)
<input type="submit" value="Go" />
#Html.ValidationMessageFor(m => m.MySanitizedProperty)
In the controller, invoking model.MySanitizedProperty returns the sanitized value while the textbox goes on to display the unsanitized data.
It sounds like a problem with the models; make sure you are properly accessing the value from the model you wish to populate it with, i.e., possibly discretely specifying the model "Name" is coming from.
Also, check to see that the setter has a chance to operate on the value - if the controller is activating before the setter function is used, then you'll only get the original input value.
Realize you have to go to the server for the setter to work, possibly you need a async postback or such, and the value reloaded.

Avoiding input element value posted back to server but allow MVC validation to occur

I have an input, type text, element which is being validated using MVC3 validation on the client and I’d like to not have the input value sent to the server when the post occurs.
I have two entities: A “File” entity and a “Company” entity which share a 1 to 1 relationship. The file entity has a CompanyId foreign key.
This is why if you look at the name and id attributes they appear as: File.Company.Code or File_Company_Code.
The reason I want to avoid sending the input value back to the server is when the request reaches the server I only want to bind the values to my entity of type “File”. As it is also receiving a value for “File.Company.Code” it is also attemting to bind the values to the File’s company object, which is what I want to avoid.
The input element is :
<input name="File.Company.Code" id="File_Company_Code" type="text" data-val-required="Se requiere un código de cliente." data-val="true" value=""/>
And the span element:
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for=" File.Company.Code "/>
I’ve tried:
-Changing the input name and span data-valmsg-for attributes using jquery. But I think that after doing this I may need to rebind the validators??
Any suggestions? (I hope to have explained myself clearly if not let me know.)
Thanks
UPDATE 1 **
Thanks to AFinkelstein sugestion which put me on the right track I updated my domain model such as:
public class FileModel {
public File File {
get {
return this.file;
}
}
*** ADDED this which helped me solve the problem ***
public Company Company {
get {
return this.file.Company;
}
}
}
In my view instead of doing :
#Html.TextboxFor(model => model.File.Company.Code)
#Html.ValidationMessageFor(model => model.File.Company.Code)
I now do:
#Html.TextboxFor(model => model.Company.Code)
#Html.ValidationMessageFor(model => model.Company.Code)
This way the generated name and Id attributes have the value: Company.Code and Company_Code, they dont have the preceding "File". When I receive the post on the server and bind the values to the File object:
FileModel fileModel = new FileModel();
try {
TryUpdateModel(fileModel.File, "File");
as it is not receiving a value for "File.Company.Code" it doesnt attempt to initialize the file's "Company" object, which was causing me other problems.
As it is also receiving a value for “File.Company.Code” it is also attemting to bind the values to the File’s company object, which is what I want to avoid.
I presume this means that File is a domain model within your project. I recommend using a view model in your view.
public class FileViewModel
{
//other stuff contained within the File class
[Required]
public string FileCompanyCode { get; set: }
}
You can use your view model to create or refetch your actual File after posting. Just don't set your actual file company object to the file company code property in the view model. This way it doesn't actually matter if your file company code is binded or not.
I had a similar issue where I wanted the client-side validation but not the field being posted as it was a list of objects and the posting structure didn't support a normal validator.
Still, I was inspired by this question and answer and found out a solution where you add another input field (with all the HTML5 tags that an HTML.HiddenFor would have generated to enable unobtrusive validation) and Html.Validator for the non-existing model property hence the MVC binder in the postback would ignore it.
I also added an Html.ValidatorFor for the real property so that the validation on postback would have somewhere to render as my other validation tags point to a different tag (theoritically)

Categories

Resources