I'm building an ASP.NET Web API 2.
I'm serializing the JSON data to my model, which uses DataAnnotations, more specifically the Range and RegularExpression.
Everything works great, however, when the ModelState is not valid, I would like to be able to return all the invalid values back to the client.
Example:
public class Book {
...(fields removed for brevity)
[Range(0, 100)]
public int? Pages { get; set; }
}
Currently when the client sends a message with an invalid range(-1 for example), the returned message is: "The field Pages must be between 0 and 100."
I would like to return something like this: "The field Pages must be between 0 and 100. Current Value is -1."
Your Model could look like this:
[Range(0,100)]
[MinValue(0, "The field Pages must be between 0 and 100")]
public int? Pages { get; set; }
Related
Can someone tell about passing input for WebApi for number data types i.e int, long etc.
public class Emp
{
public int Id { get; set; }
public long Volume { get; set; }
}
Input set 1:
{
"Id" : "1",
"Volume" : "200"
}
this is working without model validation error.
Input set 2:
{
"Id" : "1.2",
"Volume" : "200.5"
}
Model validation failing as not able to convert values.
Error : {"Error converting value \"200.5\" to type 'System.Int64'. Path 'Volume', line 2, position 14."}
Input set 3:
{
"Id" : 1.2,
"Volume" : 200.5
}
It not gives any model validation failure, but the values mapped to C# object is Id = 1, Volume = 200.
But here also, I want model validation error as I'm passing decimal input. It should allow only whole numbers.
So what is the right pattern to send api input?
What is the right way to get model validation error when passing decimal point values to int and long data type.
Using int & long will make those values rounded to nearest integer value
How to override this to give model validation errors?
You can use the JsonConverter attribute on properties to define a custom json converter for it.
public class Emp
{
[JsonConverter(typeof(MyCustomIntConverter))
public int Id { get; set; }
public long Volume { get; set; }
}
public class MyCustomIntConverter : JsonConverter<int>
{
//implement here
}
There is a way to archive what you want, but isn't a easy way.
#Saif answer is pointing to the right way.
You must change you model to avoid unwanted framework behaviors and get the raw values, for example, change the type to double or string.
Then you have to validade the arguments for yourself inside your method. If some argument is not right, you must create a model validation errro.
In mvc core, you can create a model validation error like this (in mvc should be something similar):
ModelState.AddModelError(nameof(emp.Volume), $"Error converting value {emp.Volume} to long");
I'm new at ASP.NET MVC web framework. My database is compound of a single model class("Movie"). I need to validate user's from entering existing data, for instance, a database row could be:
Title - "Indiana Jones and the lost Arc"
Price - $10.00
If another user tries to insert into the database the same data above, provide an error message and prevent from submitting the form collection.
First of all, I think that your question need a "program as answer" but I'll try to suggest you from where to start for working with validation. Suppose you have this model:
class Movie {
public Guid Id { get; set; }
[Required(ErrorMessage="Title is required.")]
[Remote("UniqueTitle", "Validation")]
public String Title { get; set; }
[Required(ErrorMessage="Price is required.")]
public float Price { get; set; }
}
You can decorate it for "simple validation" using Data Annotation. I've used a specific attribute, called Remote.
This attribute allow you to define a custom, server-side, logic to validate the model.
Now, you can create a validation controller where check that provided value is not already in use:
class ValidationController : Controller {
private IDbContext db = ...;
public ActionResult UniqueTitle(String title) {
var item = db.Movies.FirstOrDefault(m => m.Title.Equals(title));
return Json(item == null, JsonRequestBehavior.AllowGet);
}
}
Now you are ready to validate your model.
I hope this can help.
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 am still just a couple days into ASP.NET and WebAPI frameworks so I must be missing out something really simple.
I have a model that has a couple properties and ID (as a property, which has a private setter but that didn't help).
public long ID { get; private set; }
[Required(ErrorMessage = "Location coordinate X is required.")]
public double X { get; set; }
[Required(ErrorMessage = "Location coordinate Y is required.")]
public double Y { get; set; }
And then I have a controller method post:
public HttpResponseMessage Post(MyModel model)
When I start the project and go to auto-generated API documentation, I can see that samples include ID as an input field. I want API to ignore ID input field. I could just ignore it myself but I don't like such must-remember-not-to-use things in my code.
One option would be to create a separate model just for the input but it would mean I have to maintain two classes instead of one.
Is there any data annotation to ignore this property entirely?
Try with:
[ScaffoldColumn(false)]
The ID property will no longer be seen by the html helpers. However, the model binder might still try to move a value into the ID property if it sees a matching value in the request.
So you decorate it with Exclude to avoid property to be binded:
[Exclude]
public long ID { get; set; }
You can also , (inside your Post function) remove the property from state:
ModelState.Remove("Id"); // Key removal
if (ModelState.IsValid)
{
}
}
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.