So I have a multi-step process that allows a user to retrieve a forgotten password. The steps are like so, with a different action for each step in my controller:
Enter username and email
Enter password question answer
Email a recovery link to the user if all goes well
I tried using one model for everything:
public class AccountForgotPassword
{
[Required()]
[DisplayName("Username")]
public string UserName { get; set; }
[Required()]
public string Email { get; set; }
[Required()]
public string PasswordAnswer { get; set; }
}
But when I check ModelState.IsValid on the first action, since the user can't input their password answer question yet, it will always be false and makes for some interesting code to check that the model state is in fact valid but is only missing the password answer since I don't know who the user is yet.
To get around this, I decided to forgo a typed model and just use string parameters in my actions. Only problem now is that I can no longer use the easy validation wire-ups you get with model binding.
With that said, does anyone know an easy way to manually wire-up the jQuery validation to individual inputs so it will check the required rule? Also, will this wiring allow me to use the default error messages the validator generates, or will I have to supply my own upon wiring them up? Would it be this easy in my view:
#{
ViewBag.Title = "Forgot Password";
}
<h2>
Forgot Password</h2>
#using (Html.BeginForm())
{
<p>
#Html.Label("UserName", "Username")
#Html.TextBox("UserName")
#Html.ValidationMessage("UserName")
</p>
<p>
#Html.Label("Email", "Email")
#Html.TextBox("Email")
#Html.ValidationMessage("Email")
</p>
<p>
<input type="submit" value="Send Form" />
</p>
}
<script type="text/javascript">
$.validator.unobtrusive.addRule(..something here...);
</script>
Also if there is a better way to do this, please let me know.
UPDATE
For anyone else that finds this, I did figure out how to add the rules manually. Should have just read the validation docs first. Assuming the view html above, the script would be:
<script type="text/javascript">
$(function () {
$('#UserName').rules('add', {
required: true,
messages: {
required: 'The username field is required.'
}
});
$('#Email').rules('add', {
required: true,
messages: {
required: 'The email field is required.'
}
});
$.validator.unobtrusive.parse('form');
});
</script>
However, after thinking about it some more and taking AFinkelstein's answer into account, I think I will just go ahead and make 2 different view models and let the framework do the work for me.
If you validate just the username and password first, and then the Password answer second, I think the easiest solution is to have two separate View Models. Then you can still use the appropriate View Model and validation for each section.
Related
so I was going on creating my MVC 3 web app when it dawned on me that I might be putting entirely too much logic in my Controller, that it needs to be in the Model instead. The problem with this is, that in this particular instance I'm dealing with a file.
The SQL database table stores the path of the file, and the file itself is saved in a directory. So in the database, the file path is stored as an nvarchar, and in the model, the file is a string, everything's consistent to that point. The issue comes when it's time to upload the file, at that point I'm dealing with a System.IO.File.
So the question is, how do you provide System.IO.File logic inside the model for the file when in the back-end it is actually a string?
I had finished the functional version of the Controller and had some logic already in it, and was about to add more when I realized that I was working against the system. What I mean is that in order to have server-side validation, the logic needs to be in the Model in order for the input validation to behave and work according to proper MVC rules, obviously optionally using client-side validation in conjunction.
Currently...
Here is my View:
#model ProDevPortMVC3.Profile
#{
ViewBag.Title = "Profile Photo Upload";
}
<h2>Photo Upload</h2>
<img alt="Profile Image" src="#Html.DisplayFor(model => model.ProfilePhotoPath)" />
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("UploadPhoto", "Profile", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<br />
<input type="file" name="File1" />
#Html.ValidationMessageFor(model => model.ProfilePhotoPath)
<input type="submit" value="Upload" />
}
Here is my Controller (just the relevant action method):
[HttpPost]
public ActionResult UploadPhoto(int id, FormCollection form)
{
Profile profile = db.Profiles.Find(id);
var file = Request.Files[0];
if (file != null && file.ContentLength > 0)
{
try
{
string newFile = Path.GetFileName(file.FileName);
file.SaveAs(Server.MapPath("/Content/users/" + User.Identity.Name + "/" + newFile));
profile.ProfilePhotoPath = "/Content/users/" + User.Identity.Name + "/" + newFile;
UpdateModel(profile);
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
return View();
}
And here is my Model (just the part relevant to the file):
public string ProfilePhotoPath { get; set; }
So I guess, what are your guys' solutions in these particular situations?
Description
Assuming i understand your question. I have read your question a few times. ;) If i don't understand, please comment my answer in order to get a better answer (i will update)
I think that you want is.. How to Model Validation for your particular case.
You can add Model Validation errors using the ModelState.AddModelError("Key", "Message) method.
ModelState.AddModelError Adds a model error to the errors collection for the model-state dictionary.
Sample
ModelState.AddModelError("ProfilePhotoName", "YourMessage");
This will affect ModelState.IsValid
So you can do whatever you want (your logic) and can make your Model invalid.
More Information
MSDN - ModelStateDictionary.AddModelError Method
There are any number of answers to this question. I'll take a crack at it knowing the risk going in due to varying opinion. In my personal experience with MVC3 I like to use flatter, simpler Models. If there is validation that can be done easily in a few lines of code that doesn't require external dependencies then I'll do those in the Model. I don't feel like your System.IO logic is validation, per se. Validation that could go in the Model, in my mind, is whether the filename is zero length or not. The logic to save is something you can put in your controller. Better yet, you could inject that logic using the Inversion of Controller pattern and specifically a Dependency Injection solution.
I've created a fairly straight forward page with a check box:
#using (Html.BeginForm("MyController", "MyAction", FormMethod.Get))
{
#Html.CheckBoxFor(x => x.MyCheckBox)
<input type="submit" value="Go!" />
}
The URL is populated with the MyCheckBox value twice!? As such:
MyAction?MyCheckBox=true&MyCheckBox=false
It only duplicates the value if the check box is true. If set to false it will only appear once in the query string.
The code above is simplified as I have a couple of drop downs and a textbox on the form which work fine. I don't think there's anything unusual about the code which I've left out from this question.
Has anyone had a similar issue with query string parameters being duplicated?
This behaviour is by design of the checkbox control. The standard HTML checkbox control passes no value if it is not checked. This is unintuitive. Instead, the ASP.Net checkbox control has 2 elements, the standard control which is visible and also a hidden control with a value of 'False'.
Therefore, if the checkbox is not checked, there will be one value passed: False.
If it is checked, there will be two values, True and False. You therefore need to use the following code to check for validity in your code:
bool checkboxChecked = Request.QueryString["MyCheckBox"].Contains("True");
Accepted answer is correct however in my case in a recent development the MVC behaviour is misleading.
The MVC Html.CheckBox(...) and Html.CheckBoxFor(...) generate an extra input of 'type=hidden' with the same ID as the checkbox control, leading to the duplicate URL parameters. I got around this problem by simply including the mark up desired as follows:
#if(checkTrue){
<input type="checkbox" id="MyCheckBox" name="MyCheckbox" checked="checked">
}else{
<input type="checkbox" id="MyCheckBox" name="MyCheckbox">
}
Would be better wrapped upin a helper to use in place of the MVC code so the value check is encapsulated.
As part of my application, the controller maintains sets of query parameters using both form injection and link injection using helpers in order to preserve state (of paging/filtering controls for example) when clicked to navigate within the same controller scope. As a result of this feature, the check box element is always set back to false if the standard MVC helpers are used. It's a good thing I noticed and did not waste much time on this bug.
In my model, I had a collection of checkboxes like so:
public class PrerequisitesViewModel
{
public List<StudentPrerequisiteStatusViewModel> PrerequisiteStatuses { get; set; }
}
public class StudentPrerequisiteStatusViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In order to get everything to bind correctly, I had to actually convert the values from the querystring and parse them manually with the following code:
// fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
for (int i = 0; i < model.PrerequisiteStatuses.Count(); i++)
{
model.PrerequisiteStatuses[i].IsSelected = bool.Parse((Request.QueryString[$"PrerequisiteStatuses[{i}].IsSelected"] ?? "false").Split(',')[0]);
}
Alas, it works, but I can't believe this is necessary in MVC! Hopefully, someone else knows of a better solution.
I solve this issue with use #Html.HiddenFor
<input id="checkboxId" type="checkbox" value="true" onchange="changeCheckboxValue()">
#Html.HiddenFor(m => m.MyCheckBox, new { #id = "hiddenId" } )
<script>
function changeCheckboxValue() {
document.getElementById("checkboxId").value = document.getElementById("hiddenId").checked;
}
</script>
I want to pass two values from view to controller . i.e., #Model.idText and value from textbox. here is my code:
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Model.idText
<input type="text" name="textValue"/>
<input type="submit" name="btnSubmit"/>
}
But problem is if i use "Url.ActionLink() i can get #Model.idText . By post action i can get textbox value using FormCollection . But i need to get both of this value either post or ActionLink
using ajax you can achieve this :
don't use form & declare your attributes like this in tags:
#Model.idText
<input type="text" id="textValue"/>
<input type="submit" id="btnSubmit"/>
jquery:
$(function (e) {
// Insert
$("#btnSubmit").click(function () {
$.ajax({
url: "some url path",
type: 'POST',
data: { textField: $('#textValue').val(), idField: '#Model.idText' },
success: function (result) {
//some code if success
},
error: function () {
//some code if failed
}
});
return false;
});
});
Hope this will be helpful.
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Html.Hidden("idText", Model.idText)
#Html.TextBox("textValue")
<input type="submit" value="Submit"/>
}
In your controller
public ActionResult SaveData(String idText, String textValue)
{
return null;
}
I'm not sure which part you are struggling with - submitting multiple values to your controller, or getting model binding to work so that values that you have submitted appear as parameters to your action. If you give more details on what you want to achieve I'll amend my answer accordingly.
You could use a hidden field in your form - e.g.
#Html.Hidden("idText", Model.idText)
Create a rule in global.asax and than compile your your with params using
#Html.ActionLink("My text", Action, Controller, new { id = Model.IdText, text =Model.TextValue})
Be sure to encode the textvalue, because it may contains invalid chars
Essentially, you want to engage the ModelBinder to do this for you. To do that, you need to write your action in your controller with parameters that match the data you want to pass to it. So, to start with, Iridio's suggestion is correct, although not the full story. Your view should look like:
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Html.ActionLink("My text", MyOtherAction, MaybeMyOtherController, new { id = Model.IdText}) // along the lines of dommer's suggestion...
<input type="text" name="textValue"/>
<input type="submit" name="btnSubmit"/>
#Html.Hidden("idText", Model.idText)
}
Note that I have added the #Html.Hidden helper to add a hidden input field for that value into your field. That way, the model binder will be able to find this datum. Note that the Html.Hidden helper is placed WITHIN your form, so that this data will posted to the server when the submit button is clicked.
Also note that I have added dommer's suggestion for the action link and replaced your code. From your question it is hard to see if this is how you are thinking of passing the data to the controller, or if this is simply another bit of functionality in your code. You could do this either way: have a form, or just have the actionlink. What doesn't make sense is to do it both ways, unless the action link is intended to go somewhere else...??! Always good to help us help you by being explicit in your question and samples. Where I think dommer's answer is wrong is that you haven't stated that TextValue is passed to the view as part of the Model. It would seem that what you want is that TextValue is entered by the user into the view, as opposed to being passed in with the model. Unlike idText that IS passed in with the Model.
Anyway, now, you need to set up the other end, ie, give your action the necessary
[HttpPost]
public ActionResult SaveData(int idText, string textValue) // assuming idText is an int
{
// whatever you have to do, whatever you have to return...
}
#dommer doesn't seem to have read your code. However, his suggestion for using the Html.ActionLink helper to create the link in your code is a good one. You should use that, not the code you have.
Recapping:
As you are using a form, you are going to use that form to POST the user's input to the server. To get the idText value that is passed into the View with the Model, you need to use the Html.Hidden htmlhelper. This must go within the form, so that it is also POSTed to the server.
To wire the form post to your action method, you need to give your action parameters that the ModelBinder can match to the values POSTed by the form. You do this by using the datatype of each parameter and a matching name.
You could also have a complex type, eg, public class MyTextClass, that has two public properties:
public class MyTextClass
{
public int idText{get;set}
public string TextValue{get;set;}
}
And then in your controller action you could have:
public ActionResult SaveData(MyTextClass myText)
{
// do whatever
}
The model binder will now be able to match up the posted values to the public properties of myText and all will be well in Denmark.
HTH.
PS: You also need to read a decent book on MVC. It seems you are flying a bit blind.
Another nit pick would be to question the name of your action, SaveData. That sounds more like a repository method. When naming your actions, think like a user: she has simply filled in a form, she has no concept of saving data. So the action should be Create, or Edit, or InformationRequest, or something more illustrative. Save Data says NOTHING about what data is being saved. it could be credit card details, or the users name and telephone...
i'm trying to put DropDownList validation to work.
in model:
[Required(ErrorMessage = "this field is required")]
public int ObjectTypeID { get; set; }
in view:
<div class="editor-field">
#Html.DropDownList("ObjectTypeID", string.Empty)
#Html.ValidationMessageFor(model => model.ObjectTypeID)
</div>
if the user leaves the selection empty i expect client side validation to alarm. but this does not happen.
what can be done?
The behavior of system types is that they must have a value when initalized. An integer has a value of "0". Change your model to accept a nullable int:
public int? ObjectTypeID { get; set; }
Just wondering, but why not use DropDownListFor?
For client side validation to work I think you need to turn on ClientValidationEnabled & UnobtrusiveJavaScriptEnabled in the web.config for your project, I believe you also need to reference the jquery.validate.unobtrusive.min.js script on your page?
1) You are not loading your dropdownlist
2) Use DropDownListFor in order to match validation with ddl
I'm laying out a view that compares two password strings. The two properties in one of my models are pretty straightforward:
[Required]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New Password")]
public string NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Confirm Password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
Here's my view code:
<table class="fieldset center" width="400">
<tbody>
<tr>
<th width="150">
#Html.LabelFor(m => m.NewPassword)
</th>
<td>
#Html.PasswordFor(m => m.NewPassword, new { #class = "itext3" })
<br /><br />#Html.ValidationMessageFor(m => m.NewPassword)
</td>
</tr>
<tr>
<th width="150">
#Html.LabelFor(m => m.ConfirmPassword)
</th>
<td>
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "itext3" })
<br /><br />#Html.ValidationMessageFor(m => m.ConfirmPassword)
</td>
</tr>
</tbody>
</table>
All of the attributes fire their client-side validation messages when tested, except for the CompareAttribute on ConfirmPassword which is not firing until I hit the server. However, in my controller the ModelState.IsValid = false.
I compared what I'm doing to the default MVC application which is working correctly. Any suggestions for troubleshooting and fixing this?
I'm using MVC 3 RTM.
There is a BUG in jquery.validate.unobtrusive.js (and jquery.validate.unobtrusive.min.js, the minified version). The client-side validation emitted as a result of the [Compare] attribute will ONLY work if the compare-to field is the FIRST field on the form. To fix this bug, locate this line in jquery.validate.unobtrusive.js:
element = $(options.form).find(":input[name=" + fullOtherName + "]")[0];
which results in this equivalent:
element = $(options.form).find(":input[name=MyExample.Control]")[0];
Unfortunately that's not the right syntax for find(), and causes it to reference the first input control on the form.
Change that line to:
element = $(options.form).find(":input[name='" + fullOtherName + "']")[0];
which results in this equivalent:
element = $(options.form).find(":input[name='MyExample.Control]'")[0];
Which is the correct syntax, and correctly matches the input control with the specified name.
Locate the same code block in jquery.validate.unobtrusive.min.js, which looks like this:
f=a(b.form).find(":input[name="+d+"]")[0];
and change it to:
f=a(b.form).find(":input[name='"+d+"']")[0];
Take a look at your script tags in your _Layout.cshtml. I'm guessing the issue is probably your jQuery references. Did you start the MVC 3 project form scratch or are you using an example project or something like that?
Here is what happend with me; I had similar issues...
I was using some example code that pointed to ajax.microsoft.com in the src attributes
So, for example one of the script tags looked like this: <script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
I wanted to get a better handle on what js was executing on the client side so I changed it to this: <script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
That is just an example, I think there were a couple more script tags that I changed as well
So, after changing to internally served jQuery files it worked. I went back and looked at my local .validate.js file... and it was version 1.6. That is how i realized the issue was due to the version of jQuery or it's compatibility with one of the other the js libs anyway.
Bottom line is that it looks like 1.7 doesn't fully function with the validate.unobtrusive.js lib that I had... there may be a newer version that does function with 1.7... like I said, I was tinkering with an example project so there are some unknowns there. I suppose it could also be an incompatibility with the MvcValidation.js lib between it and one of the other js libs too?
At any rate, I'd say the simplest way to state your issue is that you are most likely referencing a bad combination of js libs. I'd say the best failsafe way to get a good combination of js libs is to create a new Asp.Net MVC 3 Project in Visual Studio and see what versions it gives you by default/with the project template... that is assuming that you didn't start the project from scratch. If you DID start it from scratch then may be you changed your layout file to have bad js references or if none of that is true then I suppose it could be a problem with the VisualStudio project templates?... realistically I'd say that is doubtful though. All of that said- I'd say the most likely cause [that I'd wager on anyway] is that you just got in trouble like me trying to quickly use some example code =)
I've tested this with ASP.NET MVC 3 RTM and it worked fine for me:
Model:
public class MyViewModel
{
[Required]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New Password")]
public string NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Confirm Password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
#model SomeAppName.Models.MyViewModel
#{
ViewBag.Title = "Home Page";
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.NewPassword)
#Html.PasswordFor(m => m.NewPassword)
#Html.ValidationMessageFor(m => m.NewPassword)
<br/>
#Html.LabelFor(m => m.ConfirmPassword)
#Html.PasswordFor(m => m.ConfirmPassword)
#Html.ValidationMessageFor(m => m.ConfirmPassword)
<br/>
<input type="submit" value="OK" />
}
In this configuration client side validation works perfectly fine for all attributes including the [Compare].
I was never able to make the default way to work. Seems like in JQuery it fires only those validation methods that are registered with JQuery.validator.
Initially I was doing something like this...
jQuery.validator.unobtrusive.adapters.add("equaltopropertyvalidation", ["param"], function (rule)
{
alert("attaching custom validation adapter.");
rule.rules["equaltopropertyvalidation"] = rule.params.param;
rule.messages["equaltopropertyvalidation"] = rule.message;
return function (value, element, param)
{
alert("now validating!");
return value == this.currentForm[param].value;
};
});
But my second alert (now validating!) would never fire even though the first one would. I had seen this article but I was hesitating to implement it like this because it requires me to do something extra but I was lazy.
Well after spending lots of time to figure out what's wrong I implemented it in the way this article suggests and it works. Now it is like this and it works.
<script type="text/javascript">
jQuery.validator.addMethod("equaltopropertyvalidation", function (value, element, param)
{
return value == this.currentForm[param].value;
});
jQuery.validator.unobtrusive.adapters.add("equaltopropertyvalidation", ["param"], function (rule)
{
rule.rules["equaltopropertyvalidation"] = rule.params.param;
rule.messages["equaltopropertyvalidation"] = rule.message;
});
</script>
If you like detail then here it is: Look in jquery.validate.js and search for
check: function (element)
This is the function that is fired to check if the field is valid or not by firing all the applicable rules on the field. Now first it creates an array or rules to fire. It does that using the following code.
var rules = $(element).rules();
Then it iterates over the rules collection, creates a rule element for every item in the collection and then tries to find a corresponding method to fire for that rule. Once the method is found it is fired. It does that using the following code.
var result = $.validator.methods[method].call(this, element.value.replace(/\r/g, ""), element, rule.parameters);
Now the important thing is that if your custom method is not added to the $.validator.methods collection using the JQuery.validator.addMethod IT WILL NOT BE FOUND, even if it is sitting there in the JQuery.validator.unobtrusive.adapters collection.
There might be a way to make this work without using the JQuery.validator.addMethod (and if yes then someone please clarify) but this I believe is the reason the second approach works and the first doesn't.
I was using the following versions of Jquery files
JQuery: 1.6/1.7.1
Jquery.Validate: 1.9.0
JQuery.Unobtrusive.Validate: taken from Microsoft CDN on
2/14/2012.