Programmatically create hiddenfor fields for model properties not explicitly used - c#

So this is a bit of a 'how can I be lazier?' or 'can I be super overprotective?' type of question. The scenario I'm trying to account for is this...
A model is updated with a new field, but for whatever reason the addition of the property in the model's update user interface is forgotten while the explicit setting of it is implemented in the db access controller. When the user clicks submit, the value of this property makes it to the controller as 'null' and summarily the database value is 'updated' to null.
Is there a way to get the properties on the model that are not explicitly put into the form, and add hiddenfors for these properties? (Worst case scenario, this value is not updated as opposed to losing data).
Edit: Potential Scenario
Initial object (used as the model on the form)
public MyObject
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
The form has
#Html.TextboxFor(model => model.Value1)
#Html.TextboxFor(model => model.Value2)
Later, someone comes along and adds
public string Value3 { get; set; }
to the MyObject, but forgets to add a
#Html.TextboxFor(model => model.Value3)
This results in Value3 being submitted as null when the form is submitted. What I'm trying to figure out is if it is possible to add something to the form similar to the following:
foreach (var nonExplicitlyUsedProperty in Model.Properties)
{
#Html.HiddenFor(model => model.nonExplicitlyUsedProperty)
}
In the above scenario, this would be the equivalent of adding
#Html.HiddenFor(model => model.Value3)
at the bottom of the form.

You have a couple of options:
Client-Side Validation
Server-Side Validation
Server-Side Sanitize
It sounds like your utilizing a Form Action which will automatically submit data to your Controller. You'll either need to validate the data from the Client before it is passed to the server.
Another approach would be to simply validate the parameter, to avoid a Null being passed or modify your SQL to ignore a Null value passed to it before it does the update.
The other approach would be to couple the functionality of the UI in a manner that is better represented for your Model and Controller. This way it is far more fluid and easy to understand.
Important:
You can use a Hidden Field to store data in when the model is initialized, but your introducing extra data and need to cater / regulate before you can proceed. Though the hack may solve your problem, it isn't ideal truly as it bypasses your problem rather then truly solving it.
#if(Model != null)
{
foreach(var content in Model)
{
<div>
<input type="hidden" id="hdName" name="name" value="#content.Name" />
<div>
}
}
By using this hidden field, you'll then have to ensure when the user does change the field, the hidden field is updated like so:
$('#txtName').blur(function () {
$('#hdName').val($(this).val());
});
As you can see how using Hidden Fields can truly become a nightmare, so I suggest you rethink your solution.

So here is the final answer for what I have been trying to do...
First build out the form (as normal).
Then generate hidden input fields for all of the properties on the model like so... using string format to give it slightly different name/id attributes from the explicitly defined input fields' name/id. (Since ASP.Net passes values from view to controller by name instead of by id, but jQuery standard seems to be to use ids instead of name, account for both situations)
#foreach (var property in Model.GetType().GetProperties())
{
<input type="hidden" name="#(String.Format("{0}_2", property.Name))" id="#(String.Format("{0}_2", property.Name))" value="#property.GetValue(Model, null)" />
}
Afterwards, use jQuery to iterate through the hidden inputs, remove the discriminator that was added to the "name" property to get the actual property name, then check if the input with id of that value (the original property name) has a value. If it DOES have a value, the hidden input is not needed and can be removed, otherwise, rename the hidden input name attribute (since that's how ASP.Net passes the value to the controller) to the original property name.
<script>
$(document).ready(function() {
$("input[type='hidden']").each(function() {
var actualPropertyName = $(this).attr("id").substr(0, ($(this).attr("id").length - 2));
var value = $("#" + actualPropertyName).val();
if (value != null) {
$(this).remove();
} else {
$(this).attr("name", actualPropertyName);
}
});
});
</script>

Related

boolean model propertie's values not bind in POST method

I have table record as follows
this how those two properties defined in model classes
public bool? Report_Users { get; set; }
public bool? Innovation_Discussion_User { get; set; }
this is Form syntax that used to populate Boolean values in checkboxes
#Html.CheckBox("Report User", Model.Report_Users.GetValueOrDefault(), new { #value = "false" }) Report User
#Html.CheckBox("Innovation Discussion User", Model.Innovation_Discussion_User.GetValueOrDefault(), new { #value = "false" }) Innovation Discussion User
This is check boxes view of that form
but once I submit , I can see in model those two properties values getting null in POST method
How can I bind this check-boxes values to Boolean property fields, properly ?
There a a number of issues with you implementation.
Your CheckBox("Report User", ...) method is generating a checkbox (and associated hidden input) with name="Report" and your model does not contain a property named Report. Because no value is posted back for Report_Users, its value will always be its default (null) in the POST method. In order to bind to bool? Report_Users then it would need to be CheckBox("Report_User", ...) (underscore not space).
Changing that however means the value of Report_User now always be false instead of null because you have added new { #value = "false" }. The CheckBox() method generates 2 inputs, <input type="checkbox".. value="True" /> and <input type="hidden".. value="False" /> in order to correctly bind to your property. You have now overridden it to generate <input type="checkbox".. value="false" /> so a value of false is posted even if the checkbox is checked.
But the real issue with your code is that your property is bool? (nullable) and CheckBox() is designed to bind to bool. typeof bool? has 3 states (true/false/null) whereas a checkbox has 2 states (on/off or true/false). Its not clear why your database property is nullable, but if you cannot change it, then (as always), use a view model with a bool property. In the GET method, when mapping your data model to the view model, you can use
myViewModel.ReportUser = myDataModel.Report_User.GetValueOrDefault();
and now you can build you view correctly using the strongly typed helper
#Html.CheckBoxFor(m => m.ReportUser)
#Html.LabelFor(m => m.ReportUser, "Report User") // use a label
// or add [Display(Name="Report User")] and use #Html.LabelFor(m => m.ReportUser)
What to learn from this:
If your model is not binding, always check the html your generating
(the name attributes of the form controls must match the name of
the property)
Always use the strongly typed xxxxFor() HtmlHelpers to generate
form controls. Not only do you get intellisense, it ensures that the
correct name attributes are generated, and any errors are reported
Never attempt to change the value attribute when using the
HtmlHelpers (or the name attribute). The helpers generate the
correct html based on the model property and changing them from the
default values will only lead to model binding failing

Changes in the form not sent to the controller

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.

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)

How do I keep field values in the ASP.NET MVC form if it has been invalidated?

I have a form that is going through some validation before sending an e-mail.
I have tried using this for validation, where the method ValidateInput sets the ModelState depending on the input:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
ValidateInput(collection);
if (ModelState.IsValid == false) return View(collection);
This clears all the entered fields if something is invalid. I want to keep all the entered data in the field. How can I do that?
Are you using Html helpers for input field in your form or regular html tags?
<%: Html.TextBoxFor(x => x.Message) %> - original value will be kept
<input type="text" name="Message" /> - original value will be lost after postback
Also you could benefit from using build-in validation (with data annotation attributes) instead of using your own validation method.
AFAIK you wouldn't need to call ValidateInput yourself in the Create method - the framework has already set ModelState for you, so just remove the first line.
If you can't change the ValidateInput method to not wipe your properties in this scenario then you'll really need two copies of the form data, one to pass to this method and one in reserve to pass to the view if validation fails.
You may be better off using a specific view model for the actions view rather than relying on the FormCollection object. That way you can use model binding to retrieve the data.
I think the code below should work but I haven't had a chance to test it so beware!
[HttpPost]
public ActionResult Create(MyCreateViewModel collection)
{
MyCreateViewModel myCVM = new MyCreateViewModel();
TryUpdateModel(myCVM);
ValidateInput(myCVM);
if (ModelState.IsValid == false) return View(collection);

Categories

Resources