I have an MVC 5 app, and I'm using data annotations to do a majority of validation. One of the properties in my class looks like this:
[Required(ErrorMessage = "Please enter a business name")]
[StringLength(80)]
public string BusinessName { get; set; }
The validation is working but it doesn't appear to be happening in the browser like I thought it should. On my page I have a Save button. If I leave the Business Name field blank and click Save, a post is done to a controller method that looks, partially, as follows:
[HttpPost]
public ActionResult Create(Advertiser advertiser, FormCollection collection, HttpPostedFileBase file)
{
// Before we do anything, let's check to make sure any validation that's already been done is clean.
if (!ModelState.IsValid)
{
return View(advertiser);
}
...
...
}
When this method is executed, the model state is already set to invalid. That's good because it is invalid because the Business Name field is empty. However, shouldn't this validation be happening in the client?
The field in my .cshtml file looks as follows (using Bootstrap):
<div class="form-group">
#Html.Label("Business Name", new { #class = "control-label col-md-3" })
<div class="col-md-9">
#Html.TextBoxFor(model => model.BusinessName, new { #class = "form-control", title = "", autofocus = true })
#Html.ValidationMessageFor(model => model.BusinessName)
</div>
</div>
My Web.Config is set correctly as follows:
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
I found my problem. In my BundleConfig.cs I have the following:
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.min.js",
"~/Scripts/jquery-ui-1.10.4.min.js",
"~/Scripts/jquery.base64.js"
));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.min.js",
"~/Scripts/jquery.validate.unobtrusive.min.js"
));
But, what I didn't realize is that the jqueryval bundle DOES NOT get loaded in the _Layout.cshtml file by default. So I needed to add it, as follows:
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
Once I did this, it is working as it should. Of course, this will cause it to get loaded for all pages. That may not be desirable. If not, load it separately in each page as necessary.
Related
I have
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
in my config and
Html.EnableClientValidation(true);
Html.EnableUnobtrusiveJavaScript(true);
in my view and
<script src="~/js/jquery.validate.min.js"></script>
<script src="~/js/jquery.validate.unobtrusive.min.js"></script>
are in the HTML source (as is jQuery) (no JS errors in the browser)
I have one form element with [Required] and the form element seems to be rendering OK with the associated data- attributes
<div class='form-group'>
<label class="col-md-2 control-label" for="Username">en-gb(Username)</label>
<div class='col-md-10'>
<div class='input-group'>
<div class='input-group-addon'><span class='fa fa-user'></span></div>
<input class="input-validation-error form-control" id="Username" name="Username" type="text" value="" />
</div>
<span class="field-validation-error help-block" data-valmsg-for="Username" data-valmsg-replace="true">en-gb(The en-gb(Username) field is required.)</span>
</div>
</div>
But no client side validation is occurring; the form is always submitted to the server.
What am I missing? What should I be checking?
Edit
TextBoxFor is in System.Web.Mvc.Html.InputHelpers and calls TextBoxHelper which in turn calls InputHelper in the same file. This calls
htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata) which looks like this:
public IDictionary<string, object> GetUnobtrusiveValidationAttributes(string name, ModelMetadata metadata)
{
Dictionary<string, object> results = new Dictionary<string, object>();
// The ordering of these 3 checks (and the early exits) is for performance reasons.
if (!ViewContext.UnobtrusiveJavaScriptEnabled)
{
return results;
}
FormContext formContext = ViewContext.GetFormContextForClientValidation();
if (formContext == null)
{
return results;
}
string fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
if (formContext.RenderedField(fullName))
{
return results;
}
formContext.RenderedField(fullName, true);
IEnumerable<ModelClientValidationRule> clientRules = ClientValidationRuleFactory(name, metadata);
UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules, results);
return results;
}
(Source: http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/5cb74eb3b2f3#src/System.Web.Mvc/HtmlHelper.cs)
The problem I have is that formContext.RenderedField(fullName) returns true and therefore no validation attributes are added to my input element.
In your views, are you rendering the following code?
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Assuming you're using the default project template, you'll need to include that within the view that you're rendering to enable client side validation... Alternatively, add Scripts.Render("~/bundles/jqueryval") to the bottom of your layout file to enable it across all your views.
Also do ensure that bundles/jqueryval is defined in the Bundles configuration file.
formContext.RenderedField(fullName) was a red herring, I really don't know what all that was about.
The problem is that I've subclassed the ValidationAttribute classes in order to provide globalisation with lookup from a database rather than from resx. This means that they do not appear in the ModelMetadata.
One solution is to implement the interface System.Web.Mvc.IClientValidatable on the overridden attributes. However, the base classes do not implement this interface, so they must be configuring client side validation by some other mechanism -- and it's this other mechanism that isn't smart enough to pick up subclasses of the out-of-the-box ValidationAttributes.
I have the following in a view I created, but the model.UserName is not recognized:
#model AcmeMVC.Models.SelectUserRolesViewModel
#{
ViewBag.Title = "User Roles";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Roles for user #Html.DisplayFor(model => model.UserName)</h2>
If I add: #using AcmeMVC.Models it will work.
But, I have this entry in the web.config in my Views folder:
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
.....
<add namespace="AcmeMVC" />
<add namespace="AcmeMVC.Models" />
.....
I thought this would make it so I didn't need the using statement, but I still do.
Does anyone have any clue on what I could be doing wrong here?
Modify this line
<h2>Roles for user #Html.DisplayFor(model => model.UserName)</h2>
So it is like this
<h2>Roles for user #Html.DisplayFor(m=>m.UserName)</h2>
The #model is directive to indicate you want to use strongly-typed model classes within your view. It is not a reference to an instance of the object. To access the model in your View you can use an expression (i.e. m=>m.Property)
As for as I know it seems like Microsoft are using jQuery validation attributes as default for form input attributes.
Is it possible to configure my application so if I add the Required attribute and render my form using #Html.EditorFor(x => Model) the form will be rendered using required attributes instead of data-val-required? Or am I forced to write my own EditorTemplates for all standard types?
If you want to replace the standard data-* validation attributes used by ASP.NET MVC you should start by disabling unobtrusive client side validation in your web.config:
<add key="ClientValidationEnabled" value="false" />
This will prevent the html helpers from emitting them on your input fields.
Then you could write custom editor templates for the standard types. For example for string that would be ~/Views/Shared/editorTemplates/String.cshtml:
#{
var attributes = new Dictionary<string, object>();
attributes["class"] = "text-box single-line";
if (ViewData.ModelMetadata.IsRequired)
{
attributes["required"] = "required";
}
}
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, attributes)
And that's pretty much it. Now everytime you do an Html.EditorFor(x => x.Foo) where Foo is a string property it will generate the following markup:
<input class="text-box single-line" id="Foo" name="Foo" required="required" type="text" value="" />
It's also worth mentioning that if you don't want to disable unobtrusive client side validation and the data-* attributes for your entire application but only for a single form you could do that:
#using (Html.BeginForm())
{
this.ViewContext.ClientValidationEnabled = false;
#Html.EditorFor(x => x.Foo)
}
I am developing an internal project managment dashboard using MVC 2. One of the requirements is to link to already existing documents on one of our local servers, in other words, browse to server, select file, click add and the view for that project will contain the link. Here is what I have (I've left some details out for brevity):
Model:
public class AddDocumentModel
{
public HttpPostedFileBase DocumentLink { get; set; }
}
View:
<%
using (Html.BeginForm(MVC.ProjectDetails.Actions.AddDoc(this.Model.ProjectID),
FormMethod.Post, new { enctype = "multipart/form-data" }))
{%>
<%=Html.TextBoxFor(a => a.DocumentLink,
new { type = "file", style = "width:100%;"})%>
<input type="submit" value="Add Document Link" />
<%} %>
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult AddDoc(AddDocumentModel docModel)
{
var model = _projectManagementService.AddDocumentLink(
docModel.DocumentLink.FileName);
}
So, as you can see I am using an html textbox for file upload but not actually uploading, just attempting to grab path and filename and use that as link. However due to security constraints this will only work in IE, as no other browser will let you get at the path. Also, if the user uses a mapped drive it won't work as the full path will not be used, so they have to navaigate directly to the server.
Can anyone think of another way to do this? I would like to be able to use the browse functionality offered by upload functionality, but not be tied by constraints.
At the moment the only (low tech) solution I can think of is for the user to explicitly paste the link into a text box. But would prefer something a lot more friendly.
Thanks in advance. First posted question too, so be kind :-)
If I were you I would allow them to either upload a new file or paste in the location of an existing file. There's no reason to try to reuse a file upload element to do what you're doing.
Form example (didn't feel like writing out the <%=Html %>
<form>
<div>
<input type="radio" name="AddDocumentType" value="New" />
<label for="NewDocument">Upload New Document</label>
<input type="file" id="NewDocument" name="NewDocument" />
</div>
<div>
<input type="radio" name="AddDocumentType" value="Link" />
<label for="LinkDocument">Link To Existing Document</label>
<input type="text" id="LinkDocument" name="LinkDocument" />
</div>
<input type="submit" value="Add Document Link" />
</form>
Model
public enum AddDocumentType
{
New,
Link
}
public class AddDocumentModel
{
public AddDocumentType AddDocumentType { get; set; }
public HttpPostedFileBase NewDocument { get; set; }
public string LinkDocument { get; set; }
}
I have a simple situation where I have a page that uploads Files, for some importing. At the moment, all I have is a file upload input on my page.
this is what my get controller looks like
public ActionResult FileUpload()
{
return View();
}
This is what my view looks like
#{
ViewBag.Title = "FileUpload";
}
<h2>FileUpload</h2>
<form action="/Home/FileUpload" method="post" enctype="multipart/form-data">
<input type="file" id="newFile" name="newFile" />
<input type="submit" id="submitButton" value="Submit" />
</form>
and this is what my post action looks like
[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase newFile)
{
if (newFile.ContentLength > 0)
{
//do stuff here
}
return View("Index");
}
You will of course notice there is no mention of a model here as I cannot find a way to create a model for this situation. I would like to have some very basic validation, along the lines of 'please choose a file before you upload', thats all.
Is there a way to achieve this?!
Thanks in advance
Will
Create model class with string property newFile and put a Required on it.
In controller accept not HttpPostedFile but your model class.
You should add the client side validation manually:
<input type="file" data-val="true" data-val-required="please select a file" name="file" />
#Html.ValidationMessage("file")