I am using RazorGenerator to embed a view into a shared DLL. I have a HiddenFor that is generating an invalid name attribute and a missing id attribute.
I have a view model:
public class InsuredPartiesViewModel
{
public List<OrganisationViewModel> OrganisationWithBranches { get; set; }
...
public class OrganisationViewModel : IInsuredPartyViewModel
{
public bool CanQueryIVTRForBranches { get; set; }
...
In the Razor view I am using a #helper to iterate through:
#helper RenderHiddenFieldsForInsuredParty(int insuredPartyIndex, bool branchesAreVisible)
{
#Html.HiddenFor(x => Model.OrganisationWithBranches[insuredPartyIndex].CanQueryIVTRForBranches)
...
The output is:
<input data-val="true" data-val-required="The HasQueryIVTRForBranches field is required." name="<>4__this.Model.OrganisationWithBranches[0].HasQueryIVTRForBranches" type="hidden" value="False" />
The expected output is (I can see this from an older code branch):
<input data-val="true" data-val-required="The HasQueryIVTRForBranches field is required." name="<>4__this.Model.OrganisationWithBranches[0].HasQueryIVTRForBranches" type="hidden" value="False" />
The id attribute is missing and the name attribute of <>4__this.Model.OrganisationWithBranches[0].HasQueryIVTRForBranches looks like Razor's parser is getting stuck.
Have you seen this before? How do I debug the Razor output?
Identical behaviour in VS2013 and VS2015, ASP.NET MVC 5. The data is correct going into the View.
Update
The helper method NameFor also produces the error result.
#Html.NameFor(x => Model.OrganisationWithBranches[insuredPartyIndex].CanQueryIVTRForBranches)
Related
I've seen thousands of examples using the razor BeginForm and I know that it auto binds the controller input to the submitted info... anyway I want to know if the same auto bind exists in a normal
<form method="get" action="#Url.Action("Index","Home")">
<input type="text" name="foo">
</form>
and if it doesn't ... how can I bind it... and please don't tell me to use html helpers...
yes you can bind model with normal html but the value of the name attribute has to be same as name of the property of the model e.g
public class viewmodel
{
public string FirstName {get; set;}
public string LastName {get; set;}
}
so your html will be
#model viewmodel
<form method="get" action="#Url.Action("Index","Home")">
<input type="text" name="FirstName" value="#model.FirstName"> //name should be of same name as property name
<input type="text" name="LastName" value="#model.LastName">
<input type='submit' value='Submit'/>
</form>
and finally your action will be
public ActionResult Index(viewmodel model)
{
return View(model);
}
When using any of the Input Extension Helper Methods, like #Html.TextboxFor, any Validation Attributes from your model are automatically generated by the Razor engine (via ClientValidationEnabled/UnobtrusiveJavaScriptEnabled).
For example, take the following case which works fine
Model:
[Required]
public string QuestionOne { get; set; }
View:
#Html.TextBoxFor(model => model.QuestionOne)
#Html.ValidationMessageFor(model => model.QuestionOne)
Generated Markup:
<input type="text" id="QuestionOne" name="QuestionOne" value=""
data-val="true" data-val-required="The QuestionOne field is required." >
<span class="field-validation-valid" data-valmsg-for="QuestionOne" data-valmsg-replace="true"></span>
In this case the attributes data-val="true" & data-val-required="The QuestionOne field is required." are picked up by Unobtrusive validation and the form element is successfully validated.
However, for extensibility reasons, I want to be able to generate the <input> element myself instead of using TextBoxFor. So my view would now look like this:
<input type="textbox"
id="#Html.IdFor(m => m.QuestionTwo)"
name="#Html.NameFor(m => m.QuestionTwo)"
value="#Model.QuestionTwo"
data-val="true" data-val-required="Selection is Required" />
#Html.ValidationMessageFor(model => model.QuestionTwo)
In this case, I'm faking the validation attribute output by just re-writing data-val="true" (etc) by hand, but this would have to be expanded to cover every single case.
Here's a running Demo in .NET Fiddle
Q: Can I build /return a list of data-val-* attributes for a given element?
You can use the GetUnobtrusiveValidationAttributes() method of HtmlHelper to get the validation attributes associated with a specific property.
For example in the view
#{ var attributes = Html.GetUnobtrusiveValidationAttributes("QuestionTwo"); }
<input
type="textbox"
#foreach(var attr in attributes)
{
#:#attr.Key="#attr.Value"
}
id="#Html.IdFor(m => m.QuestionTwo)"
....
/>
Note the #:#attr.Key="#attr.Value" line will give a warning (Missing attribute name) but will run correctly
Alternatively, you could use javaScript/jQuery to add the attributes
<script type="text/javascript">
var attributes = #Html.Raw(Json.Encode(attributes));
var input = $('#QuestionTwo');
for(var i in attributes) {
input.attr(i, attributes[i]);
}
</script>
I have forked the DotNetFiddle here to show the working code for both options.
While the above code shows how it can be done, you should not be doing that. The HtmlHelper methods execute a lot of code your ignoring to ensure correct 2-way model binding, for example, the value attribute is determined by first checking for a value in ModelState, then in the ViewDataDictionary, and only if the previous values do not exist, does it use the value of the property (the second part of TextBoxFor displaying initial value, not the value updated from code explains the behavior).
Except for the incorrect value attribute, the code you have shown for the <input> is the same as will be generated by simply using #Html.TextBoxFor(m => m.Question2). I assume your real case is different, but if you cannot make use of TextBoxFor() and using an overload that accepts htmlAttributes to generate the html you need, then the correct approach is to create your own HtmlHelper method (and making use of existing methods in the HtmlHelper class and System.Web.Mvc.Html namespace)
I am trying MVC's datatype attributes, and just created a simple scenario like the following:
The View:
#model MVC4.Models.Model
#{
ViewBag.Title = "DataTypeAttribute";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<h2>DataTypeAttribute</h2>
#using (Html.BeginForm("SubmitData", "Home"))
{
<div>
#Html.ValidationSummary()
</div>
#Html.EditorFor(m => m.Email)
<br />
<br />
#Html.EditorFor(m => m.PostalCode)
<br />
<br />
#Html.EditorFor(m => m.TextOnly)
<br />
<br />
<button type="submit">Submit</button>
}
The Model:
public class Model
{
[DataType(DataType.EmailAddress)]
[Required]
//[EmailAddress]
public string Email { get; set; }
[DataType(DataType.PostalCode)]
public string PostalCode { get; set; }
public string TextOnly { get; set; }
}
"SubmitData" is just a controller that, returns View(..., model) if ModelState.IsValid is false.
Although posts like this do a good job in tackling the differences between Html.TextBoxFor and Html.EditorFor, I could not find an answer as to why validation for the datatype EmailAddress will not work when using TextBoxFor. I did find people mentioning TextBoxFor does not take metadata into account, while EditorFor does.
But does this make sense ? So TextBoxFor does not offer support for client validations ?!
I wonder what is the reason for the difference between the two ?
TextBoxFor() does work with validations.
[DataType(DataType.EmailAddress)] is not a validation attribute. Its an attribute that determines the type of input to display by setting the type attribute in the rendered html. For example <input type="text" ..>, <input type="date" ..>, <input type="email" ..> in order to render the browsers implementation of a HTML4 datepicker, email input etc.. It works only for EditorFor() because TextBoxFor() as its name suggest generates and input with type="text"
If you want validation for an email address, then you use the [EmailAddress] attribute on your property.
[Required]
[EmailAddress]
public string Email { get; set; }
Edit (further to the comments)
One of the features of HTML5 is the ability to validate user data without relying on scripts. One such form of browser validation is using the type attribute. The use of [DataType(DataType.EmailAddress)] on a property that is rendered with #Html.EditorFor() adds type="email" to the input element. From the MDN documentation
email: The element represents one email address. Line breaks are automatically stripped from the input value. An invalid email address can be set, but the input field will only satisfy its constraints if the email address satisfies the ABNF production 1*( atext / "." ) "#" ldh-str 1*( "." ldh-str ) where atext is defined in RFC 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section 3.5.
If your are currently seeing a validation error message associated with the property, and you have not added the [EmailAddress] attribute, then it means that jquery.validate.js is not loaded and you are seeing the browsers error message associated with type="email".
When jquery.validate.js is loaded (correctly), the novalidate="novalidate" attribute is added to the form element, which specifies that form is not to be validated (using the HTML5 validation) when submitted. The relevant code from jquery.validate.js is (approx line 35)
// Add novalidate tag if HTML5.
this.attr('novalidate', 'novalidate');
This is added to prevent possible confusion between error messages displayed by browser validation and jquery unobtrusive validation.
As to why DataTypeAttribute attribute inherits ValidationAttribute when it does not actually do validation, from Brad Wilson himself in this answer
The reason it derives from ValidationAttribute is so that you could create a new custom data type class, which was both a DataType and a Validation, all wrapped up into one. It's an unfortunate side-effect of .NET not allowing multiple inheritance.
I have implemented an image upload child action form for an application. I have a strongly typed partial view.
public class ImageViewModel{
public long ImageId{get;set;}
public long OwnerId{get;set;}
public string ImageName{get;set;}
public string ImageDescription{get;set;}
public IEnumerable<HttpPostedFileBase> Files { get; set; }
}
Razor code looks something like this:
<form action="UploadImage" method="post" enctype="multipart/form-data">
#Html.ValidationSummary()
#Html.HiddenFor(m => m.OwnerId)
#Html.HiddenFor(m => m.ImageId)
#HtmlEditorFor(m=>m.ImageName)
<input type="file" name="Files" id="file0" />
<input type="submit" value="Upload" />
</form>
Here is the problem. When form is posted back, the model has uploaded file and ImageName value in it. But values that were bound using HiddenFor are missing.
[HttpPost]
public ActionResult UploadImage(ImageViewModel model)
{ ...}
I have checked HTML source. Hidden fields are rendered corrected with Id and names matching to property named of model. On post back I checked the raw request. Both hidden fields are carried in Form collection. But model binding is not setting the values of these fields in properties.
Is there something that I am missing about these hidden fields?
Thanks
I have some problems with ASP.NET MVC’s default model binder. The View contains HTML like this:
<input name="SubDTO[0].Id" value="1" type="checkbox">
<input name="SubDTO[1].Id" value="2" type="checkbox">
This is my simplified ‘model’:
public class SubDTO
{
public virtual string Id { get; set; }
}
public class DTO
{
public List<SubDTO> SubDTOs { get; set; }
public DTO()
{
SubDTOs = new List< SubDTO>();
}
}
All this works fine if the user selects at least the first checkbox (SubDTO[0].Id). The controller ‘receives’ a nicely initialised/bound DTO. However, if the first check box is not selected but only, for example, SubDTO[1].Id the object SubDTOs is null. Can someone please explain this ‘strange’ behaviour and how to overcome it? Thanks.
Best wishes,
Christian
PS:
The controller looks like this:
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public RedirectToRouteResult Create(DTO DTO)
{
...
}
PPS:
My problem is that if I select checkbox SubDTO[0].Id, SubDTO[1].Id, SubDTO[2].Id SubDTOs is initialised. But if I just select checkbox SubDTO[1].Id, SubDTO[2].Id (NOT the first one!!!) SubDTOs remains null. I inspected the posted values (using firebug) and they are posted!!! This must be a bug in the default model binder or might be missing something.
This behavior is "by design" in html. If a check-box is checked its value is sent to the server, if it is not checked nothing is sent. That's why you get null in your action and you'll not find value in the posted form either. The way to workaround this is to add a hidden field with the same name and some value AFTER the check-box like this:
<input name="SubDTO[0].Id" value="true" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="true" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
In this way if you check the check-box both values will be sent but the model binder will take only the first. If the check-box is not checked only the hidden field value will be sent and you\ll get it in the action instead of null.
I think this post on Scott Hanselman's blog will explain why. The relevant line is:
The index must be zero-based and unbroken. In the above example, because there was no people[2], we stop after Abraham Lincoln and don’t continue to Thomas Jefferson.
So, in your case because the first element is not returned (as explained by others as the default behaviour for checkboxes) the entire collection is not being initialized.
Change the markup as follows:
<input name="SubDTOs" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTOs" value="<%= SubDTO[1].Id %>" type="checkbox">
What's being returned by your original markup is an unrelated set of parameters, i.e. like calling RedirectToRouteResult Create(SubDTO[0].id, SubDTO[1].id, ..., SubDTO[n].id) which is clearly not what you want, you want an array returned into your DTO object so by giving all the checkboxes the same name the return value to your function will be an array of ids.
EDIT
Try this:
<input name="SubDTO[0].Id" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="<%= SubDTO[1].Id %>" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
You have to return something to make sure there is an element for each index, I suspect that any gap will cause a problem so I'd suggest using a 'null' ID, for example 0 or -1 and then process that out later in your code. Another answer would be a custom model binder.
There is always the alternate option of adding a property to your class that takes an array of strings and creates the SubDTO array from that.
public List<string> SubDTOIds
{
get { return SubDTO.Select(s=>s.Id).ToList(); }
set
{
SubDTOs = new List< SubDTO>();
foreach (string id in value)
{
SubDTOs.Add(new SubDTO { Id = id });
}
}
}
or something like that