I am facing a strange issue, sometimes i am getting the url from the sendgrid as
https://localhost:81/Activation?username=ats8#test.com&activationToken=EAAAAA
which works fine. but sometimes i am getting url which is encoded as follows,
"https://localhost:81/Activation?username=ats8%40test.com&activationToken=EAAAAA"
and my ViewModel is as follows,
public class Verification
{
[DataType(DataType.EmailAddress)]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[Compare("Password")]
public string ConfirmPassword { get; set; }
public string ActivationToken { get; set; }
}
and the Method goes as follows,
public ActionResult Activation(string username, string activationToken)
{
var model = new Verification
{
Username = username,
ActivationToken = activationToken
};
return View(model);
}
on the 2nd case, the activationToken comes as null. how can i detect activationToken even if the url is encoded?
I believe it is not the code you pasted in question which is causing issue.
The issue may be somewhere else - probably in the view.
I have tested this code with various combination of HTTP versions / Browsers / .Net / .Net core frameworks and it is working fine.
All I can do right now is to give you pointers on where you can look for an error:
First pointer to look in model binding
While working on this sample I realized, that somewhere in your solution probably model binding is not decoding the email "#" character properly.
Note that this is applicable only if you have written any custom logic to bind the values.
I see very less probability that this pointer would help you as the input parameters to action are primitive data types.
Second Pointer To look for what you are doing in view
What i suspect is you are getting username and activation token both appropriately in second URL's case. But when you send your email ID with "%40" instead of "#" character, somehow your view is not rendering properly. This is somehow causing your activationToken to be NULL.
You should first put break-point in action method to check both UserName and ActivationToken parameters are nonempty.
If they are non empty then add HttpUtility.UrlDecode where you are assigning username as shown in below code:
var model = new Verification
{
Username = HttpUtility.UrlDecode(username),
ActivationToken = activationCode
};
This would remove %40 from mail and replace it with "#" character.
This second pointer mostly should resolve your issue.
Related
I have a simple Input Model that has an Email property on it. I'm using the EmailAddress DataAnnotation to validate the input data.
[Required]
[Display(Name = "UserReview_CustomerEmailTitle", ResourceType = typeof(Resources.User.Settings))]
[EmailAddress(ErrorMessageResourceType = typeof(Resources.User.Settings), ErrorMessageResourceName = "UserReview_InvalidEmailMessage")]
public string CustomerEmail { get; set; }
It works perfectly (ie. data is valid) if there are no spaces before/after the input data.
When I have a space at the end of the input text, it fails the validation.
Is there any tricks I can do to TRIM the input data coming in OR tell the EmailAddress validator to ignore prepended/postpended spaces?
EDIT:
I'm open to creating a custom attribute... I can't inherit from the EmailAddress attribute because it's sealed (sigh) .. so maybe a regex attribute?
You could handle in javascript when user tabs out of the email field to run trim on the value and replace it. EmailAddress annotation plays nice with unobtrusive validation on the client side (assuming MVC here).
If you want to avoid client side, you might look into a custom model binder where you could manipulate the data used to create the model-to-be-validated. You could check out some tutorials here.
Last but not least, maybe you could borrow the regex used within the Email validator and make it accept whitespace via a new RegularExpression validator and handle the TRIM in your code explicitly.
If you are able to make changes to the model, you could add another property and validate it:
[Required]
public string CustomerEmail { get; set; }
[Required]
[Display(Name = "UserReview_CustomerEmailTitle", ResourceType = typeof(Resources.User.Settings))]
[EmailAddress(ErrorMessageResourceType = typeof(Resources.User.Settings), ErrorMessageResourceName = "UserReview_InvalidEmailMessage")]
public string CustomerEmailTrimmed => CustomerEmail?.Trim();
Had a similar problem on ASP.NET Core 2.2.
Try CustomerEmail.Trim(). It does exactly what you describe.
See https://msdn.microsoft.com/en-us/library/t97s7bs3(v=vs.110).aspx
This is, initially at least, more of an exploratory question to see what is and isn't possible and to see if what I would like to achieve is even possible.
At the moment I am using jQuery Unobtrusive validation in conjunction with MVCs standard server side validation to carry out validation before the data is submitted via a web form. Now, I want to have the form be a little more 'dynamic' so depending on the choice of a dropdown/radiobutton/checkbox etc. parts of the form will show or hide themselves, but this is present 2 issues to me:
jQuery Unobtrusive will still attempt to validate all form fields even if they are hidden or have their display set to none. I think I can get around this using some jQuery, a custom class tag on each element and then when the UI is updated, loop through the elements and remove/add the data-val. This isn't the major issue
This is the big one. Since I will have decorated the model with Data Annotations, when the data hits the server, if parts of it are empty, then obviously the if(ModelState.IsValid()) check is going to fail, but I need the annotations there to trigger the client side validation
I'm wondering, is there a way I can override the existing MVC server side validation logic so that if a certain checkbox or dropdown value has been selected, then some logic will simply be bypassed. I've written a very basic custom validation attribute before when I was wanting to limit access based on the connecting parties IP address.
I know I could make use of multiple ViewModels, but when the user first visits the site, the form won't know what type of submission they will be making until they choose the appropriate option, by that point a generic submission object will have already been passed to the View on the HTTP GET controller method and ideally, I'd like to avoid any reloading of the page once a submission has been made to simply load a secondary ViewModel. I'm also making use of a file uploader and file information and so on is all stored within the ViewModel so this would also need to be passed around which I could see getting messy.
public class Submission
{
[HiddenInput(DisplayValue = false)]
public Guid SubmissionID { get; set; }
[Required(ErrorMessage = "Please provide your name")]
public string Name { get; set; }
[EmailAddress(ErrorMessage = "Invalid email address")]
[Required(ErrorMessage = "Please enter an email address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please provide a contact number")]
[StringLength(13, ErrorMessage = "Phone number can be no more than 13 digits long")]
public string Phone { get; set; }
public string SubmissionPurpose { get; set; }
//Suspicious Transaction
//This should only be validated if, on the POST, the SubmissionPurpose matches the appropriate value
[Required(ErrorMessage = "Please provide a date")]
public string TransDateNoticed { get; set; }
public string TransAdditionalDetails { get; set; }
//Support Query
//This should only be validated if, on the POST, the SubmissionPurpose matches the appropriate value
[Required(ErrorMessage = "Please provide a date")]
public string SupDateNoticed { get; set; }
public string SupAdditionalDetails { get; set; }
//Additional Data
public string Message { get; set; }
public List<UploadedFile> Attachments { get; set; }
public IEnumerable<SelectListItem> SubmissionPurposes()
{
return new List<SelectListItem>
{
new SelectListItem() {Value = "", Text = "Please select an option"},
new SelectListItem() {Value = "Suspicious Transaction Reporting", Text = "Suspicious Transaction Reporting"},
new SelectListItem() {Value = "Request a Support Session", Text = "Request a Support Session"}
};
}
I'm pretty open to any and all suggestions, I'm thinking some sort of custom data annotation is the best way to go since that seems to be the only way I could have granular control over it, but then my question would be how to have an annotation like the idea I'm currently trying to work towards below
[CustomRequirement, SumissionPurpose = "Suspicious Transaction Reporting"]
I know in MVC 5.1 they introduced a new DropdownForEnum feature but from my looking so far I can't see anything new in 5.1 or 5.2 that might allow me to meet this need either.
I've used Foolproof Validation in the past. They provide several extra attributes and the clientside validation.
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
Custom validation attributes are the way to go. Please note that if the properties in the model are complex objects and their validations depend on the parent object properties you will not get the validation context of the parent object property. In that case you have to think of writing your own model validators implementing from ModelValidator class and adding it to ModelValidatorProvider factory class.
I have an ASP.NET MVC 4 app that seems to work fine. I write a custom ValidatorAttribute to make sure the value of one property is not smaller than another. Since there are two properties involved, I override IsValid(object, context).
I write unit tests using Validator.TryValidateObject and the Validate(object, context) member of the attribute, and they pass as expected. I include tests for the expected use with values that are valid and values that are invalid. I include tests where the attribute is applied to a property that is the right type, and get expected behavior (My design choice is to pass if either property type is wrong.)
I add the attribute to my model, hooking it in to the app. Something like:
public abstract class DataElement
{
...
[Required]
public string Version { get; set; }
[StringLength(8, ErrorMessage = "8 characters or less")]
[Required(ErrorMessage = "Required")]
[DisplayName("ID")]
public string DataElementNumber { get; set; }
...
}
public abstract class SimpleElement : DataElement
{
[Required]
[DisplayName("Minimum")]
public int MinimumLength { get; set; }
[Required]
[DisplayName("Maximum")]
[NotSmallerThan("MinimumLength")]
public int MaximumLength { get; set; }
}
public class CodeList: SimpleElement
{
public Collection<CodeValue> Values { get; set; }
}
I have a controller something like
[HttpGet]
public ActionResult Edit(string elementId, string version)
{
CodeList model = Store.GetCodeList(elementId, version);
return View(model);
}
[HttpPost]
public ActionResult Edit(CodeList model)
{
ActionResult result;
if (ModelState.IsValid)
{
Store.Upsert(model);
result = RedirectToAction("Index", "SomeOtherController");
}
else
{
result = View(model.DataElementNumber, model.Version);
}
return result;
}
Simple, I think. If the model is valid, commit to the data store. If it's not valid, re-display the form, with a validation message. In cases where I enter valid values in the form, the validator behaves as expected, that is, the application commits values to the data store and move on.
In the case where I enter a value for Minimum that is smaller than Maximum, the case I am guarding against, instead of seeing my view, again, I see an error screen, something like this for the case where DataElementNumber="XML-25" and Version="201301"
The view 'XML-25' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/CodeListBuilder/XML-25.aspx
~/Views/CodeListBuilder/XML-25.ascx
~/Views/Shared/XML-25.aspx
~/Views/Shared/XML-25.ascx
~/Views/CodeListBuilder/201301.master
~/Views/Shared/201301.master
~/Views/CodeListBuilder/XML-25.cshtml
~/Views/CodeListBuilder/XML-25.vbhtml
~/Views/Shared/XML-25.cshtml
~/Views/Shared/XML-25.vbhtml
~/Views/CodeListBuilder/201301.cshtml
~/Views/CodeListBuilder/201301.vbhtml
~/Views/Shared/201301.cshtml
~/Views/Shared/201301.vbhtml
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException:...
I can comment out the custom NotSmallerThanAttribute and the system behaves as I expect, apart form being able to enter number fo maximum that are smaller than minimum. I am not sure how to diagnose this. What kind of behavior in a validator can confuse the routing engine? How do I find it? TIA
Your problem has nothing to do with your validator.
With the result = View(model.DataElementNumber, model.Version); you are using the following overload of the View method:
protected internal ViewResult View(
string viewName,
string masterName
)
So the framework thinks that your model.DataElementNumber is your viewName and your model.Version your masterName that is why you get this strange view missing exception.
To fix this you just need to use the correct overload with passing in your model
result = View(model);
and MVC will take care of re-displaying your previously posted DataElementNumber and Version values.
I can't seems to find anything related to this issue on Google.
Please help !!
SCENARIO:
Mainly I have a WebAPI server with a controller method that expects a simple type as parameter.
That API looks like this:
public HttpResponseMessage Foo([FromBody] LoginModel form)
{
// ...some code
return this.Request.CreateResponse(HttpStatusCode.OK);
}
and the LoginType class looks like this:
public class LoginModel
{
[Required]
[EmailAddress(ErrorMessage = "Please have a Email address format")]
public string Email;
[Required]
[StringLength(20, MinimumLength = 6, ErrorMessage = "Password must be between 6 and 20 characters")]
public string Password;
}
Problem occurs when the Client tries to run the API method. I pass a json that looks like this
{ "Email" : "xxx#xxx.com" , "Password" : "oooooo" }
....I get the following exception
EXCEPTION MESSAGE:
"Field 'Email' on type 'XXXX.Models.Login' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.
The same happens when I ran the api call from Fiddler !!
NOTE:
If I remove the various Attributes like [Required], it works smoothly. The client call never gets to the method when the Attributes are in place.
Help is truly appreciated !!!
Like it says:
Validation attributes on fields are not supported. Consider using a public property for validation instead.
So use properties;
public string Email { get; set; }
I have the following:
public class Address
{
public string Email { get; set; }
}
public class CheckoutViewModel
{
public Address Address { get; set; }
[Compare("Address.Email", ErrorMessage = "The email addresses you entered do not match")]
public string ConfirmEmailAddress { get; set; }
}
With client-side JS, this works a treat and validates properly. However, when testing without Javascript enabled, The form posts back but the ModelState error reads:
Could not find a property named Address.Email.
Any ideas as to why this works on the client but not the server? What is the solution in this case?
Many thanks.
If you view the HTML source generated you should find that the input element for Email is called "Address.Email", and this is why the validation works on the client side.
However it looks like the attribute is not built to handle nested properties and so at the server level it is not working (as there is no property called "Address.Email"). As a result you will need to make sure both properties are at the same level (either both on the ViewModel or both on the Address class).
Your best option if probably to put the Email address property onto the view model and then populate the Address object later.