WebAPI Validation DataAnnotation creates runtime exception [HttpStatusCode 500] - c#

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; }

Related

Why my ViewModel field becomes empty when the url is HTML encoded?

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.

Web api - DataAnnotations: optional properties, require the rest if one is present

I am writing a web api application, where i need to deserialize json from a POST request into a DTO that looks like the following
public class UserDto
{
[Required(AllowEmptyStrings = false, ErrorMessage = "User.Email is required")]
[StringLength(128, ErrorMessage = "The email length cannot exceed 128 characters.")]
[EmailAddress]
public string Email { get; set; } //Always required
public string UserId { get; set; }
public string UserType { get; set; }
}
The rules is as following: Email is always required, UserId/UserType is optional, but they are linked, as in; if one of the two is present in the json the other property is required to be there.
Currently we have this handled in code in our service layer, but i would like to move this into the model, with DataAnnotations, like [Required]. This way its validated before it reaches the controller and i don't have to make try/catch blocks when invoking my service from the controllers
The json format we accept is as following:
{
"User": {
"Email": "example#email.com",
"UserId": "userId", //Optional, but required if 'UserType' is present
"UserType": "type" //Optional, but required if 'UserId' is present
}
}
Is there a way to achieve this with data annotation?
Ideally i would like to simply link the two properties together. I was thinking of creating an object for the two properties like UserData and have my json (de)serializer unwrap the object so we still accept the same format, and only our DTO have to change. (i know this is possible with fx. jackson in java). But it would be even cooler if it was possible with my current class-structure, with something like [RequireIf(link = anotherProperty)] annotation.

Why does an ASP.NET MVC 4 Custom validator cause a routing error?

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.

MVC3 Compare attribute and nested object properties

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.

DataAnnotation attributes buddy class strangeness - ASP.NET MVC

Given this POCO class that was automatically generated by an EntityFramework T4 template (has not and can not be manually edited in any way):
public partial class Customer
{
[Required]
[StringLength(20, ErrorMessage = "Customer Number - Please enter no more than 20 characters.")]
[DisplayName("Customer Number")]
public virtual string CustomerNumber { get;set; }
[Required]
[StringLength(10, ErrorMessage = "ACNumber - Please enter no more than 10 characters.")]
[DisplayName("ACNumber")]
public virtual string ACNumber{ get;set; }
}
Note that "ACNumber" is a badly named database field, so the autogenerator is unable to generate the correct display name and error message which should be "Account Number".
So we manually create this buddy class to add custom attributes that could not be automatically generated:
[MetadataType(typeof(CustomerAnnotations))]
public partial class Customer { }
public class CustomerAnnotations
{
[NumberCode] // This line does not work
public virtual string CustomerNumber { get;set; }
[StringLength(10, ErrorMessage = "Account Number - Please enter no more than 10 characters.")]
[DisplayName("Account Number")]
public virtual string ACNumber { get;set; }
}
Where [NumberCode] is a simple regex based attribute that allows only digits and hyphens:
[AttributeUsage(AttributeTargets.Property)]
public class NumberCodeAttribute: RegularExpressionAttribute
{
private const string REGX = #"^[0-9-]+$";
public NumberCodeAttribute() : base(REGX) { }
}
NOW, when I load the page, the DisplayName attribute works correctly - it shows the display name from the buddy class not the generated class.
The StringLength attribute does not work correctly - it shows the error message from the generated class ("ACNumber" instead of "Account Number").
BUT the [NumberCode] attribute in the buddy class does not even get applied to the AccountNumber property:
foreach (ValidationAttribute attrib in prop.Attributes.OfType<ValidationAttribute>())
{
// This collection correctly contains all the [Required], [StringLength] attributes
// BUT does not contain the [NumberCode] attribute
ApplyValidation(generator, attrib);
}
Why does the prop.Attributes.OfType<ValidationAttribute>() collection not contain the [NumberCode] attribute? NumberCode inherits RegularExpressionAttribute which inherits ValidationAttribute so it should be there.
If I manually move the [NumberCode] attribute to the autogenerated class, then it is included in the prop.Attributes.OfType<ValidationAttribute>() collection.
So what I don't understand is why this particular attribute does not work in when in the buddy class, when other attributes in the buddy class do work. And why this attribute works in the autogenerated class, but not in the buddy. Any ideas?
Also why does DisplayName get overriden by the buddy, when StringLength does not?
I noticed that your NumberCodeAttribute doesn't specify AllowMultiple=True in the AttributeUsage attribute. The default for that parameter (if it isn't specified) is false. Try adding that on and it should appear.
I recreated your code using VS2008 and MVC2 and it worked fine for me.

Categories

Resources