CHAPTER 13

image

Input Validation

Regardless of the application type, input validation is a very important topic if the application accepts user inputs. As developers, we tend to assume that every user input is evil and they should be validated before being processed. Checking whether an input field is empty or not, validating the length of an input value, and comparing two inputs against each other according to a specific logic—these are just some of the common validation checks.

This chapter will explain how ASP.NET Web API simplifies this process by offering out-of-the-box solutions and how its built-in implementations can be extended to customize the validation in accord with business logic. You will also see how to handle responses for invalid requests in view of the resource owner and the consumer.

Overview

The ASP.NET Web API validation model is very similar to ASP.NET MVC. Data annotation validation attributes are supported right out of the box, and discussion of these will take up most of this chapter.

Data Annotation Attributes were introduced in .NET 3.5 SP1 as a way to add validation to classes used by ASP.NET Dynamic Data applications. Since .NET 4.0, Data Annotation Attributes has been part of .NET Framework itself, and the library provides not only validation attributes but also others that give metadata information about the properties and fields in which they are applied.

The model validation system in ASP.NET Web API is not limited to data annotation validation attributes. Although validation with data annotations will be what you want most of the time, you’ll have a chance to provide your own validation providers, too.

How Validation Works Through Parameter Binding

The built-in validation logic inside the ASP.NET Web API framework is triggered by the ExecuteAsync method of the ApiController. The ExecuteAsync method doesn’t perform the validation itself. Instead, it invokes the proper HttpParameterBinding instances, some of which have the ability to perform validation.

image Note  If you are using a custom base controller which is not derived from ApiController or overrides the default ApiController.ExecuteAsync method logic, you won’t get validation for free.

As you saw in Chapter 12, there are two main types of HttpParameterBinding implementations provided by the framework: FormatterParameterBinding and ModelBinderParameterBinding. Depending on the action parameter types, registered parameter binding rules, and applied ParameterBinding attributes, an HttpParameterBinding instance is chosen to process the action parameter binding. Only these two parameter HttpParameterBinding implementations provide the action parameter validation.

However, they don’t actually hold any information for the validation logic. They delegate this to other services. In the case of FormatterParameterBinding, the validation action is delegated to the registered IBodyModelValidator service. For ModelBinderParameterBinding, the provided System.Web.Http.Validation.ModelValidator implementations play the key role in performing the validation. The ModelValidator implementations are provided by the registered System.Web.Http.Validation.ModelValidatorProvider implementations, which are at the highest extensibility point for the validation system inside the framework.

There are three registered implementations of the ModelValidatorProvider:

  • DataAnnotationsModelValidatorProvider
  • DataMemberModelValidatorProvider
  • InvalidModelValidatorProvider

The DataAnnotationsModelValidatorProvider provides the model validators for Data Annotation validation attributes (they will, as noted earlier, be covered in depth in this chapter). This ModelValidatorProvider also acts as a validator for types implementing System.ComponentModel.DataAnnotations.IValidatableObject (also covered in this chapter).

The DataMemberModelValidatorProvider provides a required ModelValidator for members marked [DataMember(IsRequired=true)]. The last implementation, the InvalidModelValidatorProvider, provides validators that throw exceptions when the model is invalid according to a specific logic which we are about to explain.

The InvalidModelValidatorProvider is not a model provider that you want in most cases because, in our opinion, it behaves too aggressively. It is meant to provide the functionality to throw an error if you have a value-type property marked with RequiredAttribute and not with [DataMember(IsRequired=true)]. Consider the example of the controller implementation shown in Listing 13-1.

Listing 13-1.  CarsController and GetCar Action Method

[FromUri]
public class UriParameter {

    [Required]
    public int Id { get; set; }
}

public class CarsController : ApiController {

    // GET /api/cars/{id}
    public Car GetCar(UriParameter uriParameter) {
        
        //Lines omitted for brevity . . .
    }
}

Normally, when sending a GET request to /api/cars/10, one should hit the GetCar action method and process the request there. However, as Figure 13-1 shows, doing so will generate a complicated error message.

9781430247258_Fig13-01.jpg

Figure 13-1. A response message for the request against /api/cars/10

If you don’t require this kind of functionality, you can easily remove this validator provider (see Listing 13-2).

Listing 13-2.  Removing InvalidModelValidatorProvider from the ModelValidatorProvider List

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;

    //Lines omitted for brevity . . .

    config.Services.RemoveAll(
        typeof(ModelValidatorProvider),
        validator => validator is InvalidModelValidatorProvider);
}

Alternatively, you may want DataAnnotationsModelValidatorProvider solely registered. If such is the case, just remove all the validator providers except for DataAnnotationsModelValidatorProvider, as Listing 13-3 shows.

Listing 13-3.  Removing All Validator Providers Except for DataAnnotationsModelValidatorProvider

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    
    //Lines omitted for brevity . . .

    config.Services.RemoveAll(
        typeof(ModelValidatorProvider),
        validator => !(validator is DataAnnotationsModelValidatorProvider));
}

image Note  Keep in mind that the validation providers and several other validation components for ASP.NET Web API live under the namespaces System.Web.Http.Validation and System.Web.Http.Validation.Providers. The fact that some of the class names also exist for the ASP.NET MVC and ASP.NET Web Forms may lead to confusion and possible conflicts.

Throughout this chapter, samples will be run with this implementation in place unless stated otherwise.

As already mentioned, the validation happens at the parameter binding level through certain HttpParameterBinding implementations. At this point, you might ask, “How will I be informed about the validation errors?” The next section answers that very question.

Model State

Right after the authorization filters are invoked, the proper HttpParameterBinding instances are invoked. If a validation is performed through any of the HttpParameterBinding instances, validation errors that occur during the parameter binding are propagated back to the ApiController’s ExecuteAsync method through the ModelState property of the HttpActionContext.

If these HttpParameterBinding instances perform without error, the ModelState property of the ApiController is set, and the action filters are invoked. The action filters are the first place where you can see and act on the ModelState. There you can inspect the ModelState property and terminate the request with an action filter if there are validation errors. Listing 13-4 shows an example of a filter that handles ModelState validation errors, which we also saw in Chapter 11.

Listing 13-4.  InvalidModelStateFilterAttribute Class

[AttributeUsage(AttributeTargets.Class |
    AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class InvalidModelStateFilterAttribute : ActionFilterAttribute {

    public override void OnActionExecuting(
        HttpActionContext actionContext) {

        if (!actionContext.ModelState.IsValid) {

            actionContext.Response =
                actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

The CreateErrorResponse extension method (explained in Chapter 9) is used here to create an error response over the ModelState. As you saw in Chapter 11, the request is terminated if the Response property of the HttpActionContext is set inside the OnActionExecuting method of our action filter. Throughout this chapter, this filter will be used to demonstrate the validations’ behavior.

Data Annotation Validation Attributes

In most cases, validation will only be performed with data annotation validation attributes, which cover a huge part of the validation logic inside the ASP.NET Web API framework.

Data annotation validation attributes are based on the System.ComponentModel.DataAnnotations.ValidationAttribute abstract class, by means of which an attribute is recognized as a validation attribute. The ValidationAttribute class, which serves as the base class for all validation attributes, has the properties shown in Table 13-1.

Table 13-1. Public Properties of the ValidationAttribute Class

Name Type Description
ErrorMessage String Represents the error message to associate with a validation control if validation fails.
ErrorMessageResourceName String Represents the error message resource name to use to look up the ErrorMessageResourceType property value if validation fails.
ErrorMessageResourceType Type Represents the resource type to use for error-message lookup if validation fails.
ErrorMessageString String Represents the localized validation error message.
RequiresValidationContext Boolean Indicates whether the attribute requires validation context.

You can see that these are very basic properties, ones that every validation attribute will carry. Besides these common properties, every validation attribute can have its own properties.

.NET Framework provides several useful validation attributes out of the box. They cover most of the validation logic that our applications require. This section will explain them separately and show how to use them as well.

RequiredAttribute

The RequiredAttribute specifies that a field or property is required. As already explained, all validation attributes are derived from the ValidationAttribute abstract class. Besides the ValidationAttribute class’s properties, the RequiredAttribute class holds one more property, AllowEmptyStrings. This property indicates whether an empty string is allowed or not. The default value for AllowEmptyStrings is false, and in most cases this is what is desired.

To demonstrate the RequiredAttribute in action, Listing 13-5 uses a simple car gallery API application scenario with a Car class.

Listing 13-5.  Car Class

public class Car {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }
}

As you see, Make and Model properties are marked with RequiredAttribute. There is also a context class, which gets the car objects and adds them to the in-memory data store for demonstration purposes (see Listing 13-6).

Listing 13-6.  CarsContext Class

public class CarsContext {

    private static int _nextId = 9;

    //data store
    readonly static ConcurrentDictionary<int, Car> _carsDictionary =
        new ConcurrentDictionary<int, Car>(
        new HashSet<KeyValuePair<int, Car>> {
            new KeyValuePair<int, Car>(
                1,
                new Car {
                    Id = 1,
                    Make = "Make1",
                    Model = "Model1",
                    Year = 2010,
                    Price = 10732.2F
                }
            ),
            new KeyValuePair<int, Car>(
                  2,
                  new Car {
                      Id = 2,
                      Make = "Make2",
                      Model = "Model2",
                      Year = 2008,
                      Price = 27233.1F
                  }
              ),
              
            //Lines omitted for brevity . . .
    });

    public IEnumerable<Car> All {
        get {
            return _carsDictionary.Values;
        }
    }

    public IEnumerable<Car> Get(Func<Car, bool> predicate) {

        return _carsDictionary.Values.Where(predicate);
    }

    public Tuple<bool, Car> GetSingle(int id) {

        Car car;
        var doesExist = _carsDictionary.TryGetValue(id, out car);
        return new Tuple<bool, Car>(doesExist, car);
    }

    public Car GetSingle(Func<Car, bool> predicate) {

        return _carsDictionary.Values.FirstOrDefault(predicate);
    }

    public Car Add(Car car) {

        car.Id = _nextId;
        _carsDictionary.TryAdd(car.Id, car);
        _nextId++;

        return car;
    }

    public bool TryRemove(int id) {

        Car removedCar;
        return _carsDictionary.TryRemove(id, out removedCar);
    }

    public bool TryUpdate(Car car) {

        Car oldCar;
        if (_carsDictionary.TryGetValue(car.Id, out oldCar)) {

            return _carsDictionary.TryUpdate(car.Id, car, oldCar);
        }

        return false;
    }

}

Now, inside the controller, allow a GET request to retrieve all the cars, a POST request to add a new car, and a PUT request to edit an existing car entity. Listing 13-7 shows our controller implementation.

Listing 13-7.  CarsController Implementation

[InvalidModelStateFilter]
public class CarsController : ApiController {

    private readonly CarsContext _carsCtx =
        new CarsContext();
        
    // GET /api/cars
    public IEnumerable<Car> Get() {

        return _carsCtx.All;
    }

    // POST /api/cars
    public HttpResponseMessage PostCar(Car car) {
            
        var createdCar = _carsCtx.Add(car);
        var response = Request.CreateResponse(
            HttpStatusCode.Created, createdCar);

        response.Headers.Location = new Uri(
            Url.Link("DefaultHttpRoute",
                new { id = createdCar.Id }));

        return response;
    }

    // PUT /api/cars/{id}
    public Car PutCar(int id, Car car) {

        car.Id = id;

        if (!_carsCtx.TryUpdate(car)) {

            var response = Request.CreateResponse(
                HttpStatusCode.NotFound);

            throw new HttpResponseException(response);
        }

        return car;
    }
}

The InvalidModelStateFilter, shown in Listing 13-4, is registered at the controller level to handle the validation errors and return an informative response to the client. When a valid POST request is sent against /api/cars, a “201 Created” response comes back, as shown in Figure 13-2.

9781430247258_Fig13-02.jpg

Figure 13-2. A POST request against /api/cars and the “201 Created” response

However, if the request is invalid according to our validation logic, the InvalidModelStateFilterAttribute action filter will kick in and terminate the request by setting a new “400 Bad Request” response, along with the Errors collection of the ModelState object (see Figure 13-3).

9781430247258_Fig13-03.jpg

Figure 13-3. Invalid POST request against /api/cars and the “400 Bad Request” response

Figure 13-4 gives a formatted view of the message body that contains the validation errors.

9781430247258_Fig13-04.jpg

Figure 13-4. A “400 Bad Request” response body formatted in JSON view

As expected, the response message indicates that the Make property of the Car object is required. If you send the request by including the Make property but supplying an empty string value, you would still get the validation error because the RequiredAttribute doesn’t allow empty strings. For the reverse to apply, the Car class should be changed to what is shown in Listing 13-8.

Listing 13-8.  Car Class Reset to Allow Empty Strings for the Make Property

public class Car {

    public int Id { get; set; }

    [Required(AllowEmptyStrings = true)]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }
}

Notice that in the response body for the invalid request in Figure 13-4, there is one additional validation error message whose key is “car”. This validation error message comes from the formatter itself. By default, formatters check required fields. If a required field is not supplied, the formatter throws an error that carries an error message similar to the one shown in Figure 13-4. Also by default, if a formatter error occurs during the formatting stage, the error message is put inside the Errors collection in the ModelState.

Some of the ways to suppress this behavior depend on the type of formatter. One way to suppress the behavior on every formatter is to create a custom IRequiredMemberSelector implementation to replace the default one. Listing 13-9 shows the proper IRequiredMemberSelector implementation for this behavior.

Listing 13-9.  SuppressedRequiredMemberSelector Class

public class SuppressedRequiredMemberSelector
    : IRequiredMemberSelector {

    public bool IsRequiredMember(MemberInfo member) {

        return false;
    }
}

To replace the default IRequiredMemberSelector implementation on the formatters, iterate over all of them and replace the default on each formatter with our custom IRequiredMemberSelector implementation (see Listing 13-10).

Listing 13-10.  Replacing the Default IRequiredMemberSelector Implementation

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;

    // Lines omitted for brevity

    // Suppressing the IRequiredMemberSelector for all formatters
    foreach (var formatter in config.Formatters) {
        formatter.RequiredMemberSelector = new SuppressedRequiredMemberSelector();
    }
}

With this change in place, you won’t see the additional duplicate error message if an invalid POST request is sent to /api/cars (see Figure 13-5).

9781430247258_Fig13-05.jpg

Figure 13-5. Invalid POST request against /api/cars and formatted response body

RequiredAttribute and Value Types

The RequiredAttribute doesn’t validate the value types correctly. For example, when the RequiredAttribute is applied to a value-type field, you won’t get an exception, but you will see that the validation isn’t applied.

Let’s have a look at the Car class shown in Listing 13-11.

Listing 13-11.  Car Class with a Year Property Marked with RequiredAttribute

public class Car {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    [Required]
    public int Year { get; set; }

    public float Price { get; set; }
}

The one change made was to apply the RequiredAttribute to the Year property, an integer type (Int32 is a value type). With the changes applied in Listing 13-10, you won’t get a “400 Bad Request” if a POST request containing a car entity with the missing Year field is sent. This is obviously not the behavior expected here. Instead, the request will be processed, and a new Car object added to the list, because the formatter or the model binding will assign a default value for the value type—which is 0 for Int32. What all of this produces is the response shown in Figure 13-6.

9781430247258_Fig13-06.jpg

Figure 13-6. “201 Created” for an invalid POST request against /api/cars

One way to work around this issue is to use .NET Nullable types and apply RequiredAttribute to them. You can make a value type nullable either by adding the “?” sign as a suffix for the property name or using the Nullable<T> generic type. With this change, the Car class now looks like what is seen in Listing 13-12.

Listing 13-12.  Car Class with a Nullable<int> Type of Year Property

public class Car {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    [Required]
    public Nullable<int> Year { get; set; }

    public float Price { get; set; }
}

If applied to your existing code and depending on your situation, this change may require additional changes. For example, consider an object that is a type of Nullable<int>. Integer values can be directly assigned to this object, but the object cannot be assigned to an Int32 value type. To do that, the Value property of the Nullable<T> object needs to be used. The Value property will be a type of T. In the case here, no additional changes had to be made, and so if the request shown in Figure 13-6 was sent, the expected behavior would be what is seen in Figure 13-7.

9781430247258_Fig13-07.jpg

Figure 13-7. Invalid POST request against /api/cars and the “400 Bad Request” response

On the other hand, if the default IRequiredMemberSelector implementation on the formatters wasn’t replaced, you’d get the ModelState error from the formatter even if you used RequiredAttribute on value types (see Figure 13-8).

9781430247258_Fig13-08.jpg

Figure 13-8. Invalid POST request to /api/cars and the “400 Bad Request” response

If you also support XML format with your API, the RequiredAttribute will not be enough by itself for the value-type fields. As indicated in earlier sections, our samples here are running with the configuration that has only DataAnnotationsModelValidatorProvider registered as ModelValidatorProvider. If a POST request is now sent against /api/cars in XML format with a missing Year field, assuming the Car class is as shown in Listing 13-11, there won’t be any validation or runtime errors, and the object will be created with the default Int32 value for the Year field (see Figure 13-9).

9781430247258_Fig13-09.jpg

Figure 13-9. Invalid POST request to /api/cars and “201 Created” response

Let’s change our configuration for this sample, leave all the registered ModelValidatorProvider instances by default as they are, and send the same request again. A runtime error similar to the one shown in Figure 13-10 should be returned.

9781430247258_Fig13-10.jpg

Figure 13-10. POST request to /api/cars and a “500 Internal Server Error” response

As was briefly explained earlier, this error is thrown by the InvalidModelValidatorProvider. For the value-typed properties to be recognized as required, they need to be marked with [DataMember(IsRequired=true)] if the property is marked with RequiredAttribute. Also, the Car class needs to be marked with DataContractAttribute, as Listing 13-13 shows. These attributes live under the System.Runtime.Serialization namespace. You need to add the System.Runtime.Serialization reference to your project to get hold of them.

image Note  In the first RTW release of ASP.NET Web API, the InvalidModelValidatorProvider behaves overly aggressively and even throws runtime errors when the XmlMediaTypeFormatter is not registered. This issue—known but not yet solved at the time this book is being written (early 2013)—is also present in the FromUri attribute when using a class signature against the InvalidModelValidatorProvider. The issue is tracked on the official Codeplex page of the ASP.NET Web Stack project (http://aspnetwebstack.codeplex.com/workitem/270).  According to team members’ comments, the provider will be removed in the framework’s next version, but to date there has been no official confirmation of this decision.

Listing 13-13.  The Car Class with DataContract and DataMember Attributes

[DataContract]
public class Car {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    [Required]
    [DataMember(IsRequired = true)]
    public int Year { get; set; }

    public float Price { get; set; }
}

If we now send the same request, a “400 Bad Request” response should be returned (see Figure 13-11).

9781430247258_Fig13-11.jpg

Figure 13-11. Invalid POST request to /api/cars and a “400 Bad Request” response

StringLengthAttribute

The StringLengthAttribute provides a way to specify minimum and maximum lengths of characters allowed in a string field. This validation attribute is most useful when you know that the field inside the database has a certain capacity and that you can apply the StringLengthAttribute to those class fields and properties to validate the member early—before the value gets to the database.

The Car class in Listing 13-14 shows an example of a class whose properties are marked with the StringLengthAttribute.

Listing 13-14.  The Car Class That Contains Properties Marked with the StringLengthAttribute

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(maximumLength: 20)]
    public string Make { get; set; }

    [Required]
    [StringLength(maximumLength: 20, MinimumLength = 5)]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }
}

The StringLengthAttribute accepts two custom inputs: one through the constructor that specifies the member’s allowed maximum length value and the other through a property, named MinimumLength, that specifies the field’s allowed minimum length value. In our Car class, the Make property is marked with a StringLengthAttribute indicating that the maximum length value allowed is 20. On the other hand, the Model property is forced to have a value whose length is a minimum of 5.

Let’s use the sample set up, which is shown in the previous section, with the Car class implementation in Listing 13-14 and send an invalid POST request to /api/cars to create a new car entity. A “400 Bad Request” response will come back, as we have an action filter registered to handle the invalid ModelState (see Figure 13-12).

9781430247258_Fig13-12.jpg

Figure 13-12. Invalid POST request to /api/cars and a “400 Bad Request” response

As you see, our request message body contains a JSON object with Model and Make properties whose values are invalid. The response message body states those validation errors clearly.

Although the validation error messages are pretty clear, you might also want to customize them. Remember that all validation attributes contain properties to customize the error messages. In addition, depending on the validation attribute type, error message formats that combine dynamic input values and static error messages can be supplied. Listing 13-15 shows an example of custom error messages for the Make and Model properties of the Car class.

Listing 13-15.  The Car class, which has properties marked with StringLengthAttribute, contains custom error messages

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(
        maximumLength: 20,
        ErrorMessage = "For {0} field, the maximum allowed length is {1}."
    )]
    public string Make { get; set; }

    [Required]
    [StringLength(
        maximumLength: 20, MinimumLength = 5,
        ErrorMessage = "For {0} field, the maximum allowed length is {1} and the minimum allowed length is {2}."
    )]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }
}

The StringLengthAttribute accepts two different message formats depending on whether a MinimumLength was or wasn’t supplied. If it was, you would get three arguments from the FormatErrorMessage of the ValidationAttribute class, which you will see in the “Custom Validation” section of this chapter. The first one will be the name of the field, the second will be the maximum allowed characters value, and the third will be the minimum allowed characters value. If a value for the MinimumLength property isn’t supplied, you won’t get the third argument.

Using the custom validation messages set in Listing 13-15, you’ll now get different validation errors for invalid requests (see Figure 13-13).

9781430247258_Fig13-13.jpg

Figure 13-13. Invalid POST request to /api/cars and a “400 Bad Request” response with custom validation messages

Alternatively, you can put the error messages inside a resource file for any of several reasons (such as localization) and point the validation attribute to the message format inside that resource file. This example assumes that there is a resource file named ValidationErrors and that it has the same error message formats as in Listing 13-15 (see Figure 13-14).

9781430247258_Fig13-14.jpg

Figure 13-14. The ValidationErrors resource file view on Visual Studio

These string resources can now be used as validation message formats. As Table 13-1 briefly mentioned, the ErrorMessageResourceName and ErrorMessageResourceType properties of the ValidationAttribute class provide the ability to reference a resource inside a resource file (see Listing 13-16).

Listing 13-16.  The Car Class, Which Contains Properties Marked with a StringLengthAttribute, Contains Custom Error Messages from a Resource File

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(
        maximumLength: 20,
        ErrorMessageResourceName =
            "StringLengthAttribute_ValidationError",
        ErrorMessageResourceType =
            typeof(ValidationErrors)
    )]
    public string Make { get; set; }

    [Required]
    [StringLength(
        maximumLength: 20, MinimumLength = 5,
        ErrorMessageResourceName =
            "StringLengthAttribute_ValidationErrorIncludingMinimum",
        ErrorMessageResourceType =
            typeof(ValidationErrors)
    )]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }
}

The resource key has been set to the ErrorMessageResourceName property, and the type of the resource class through the ErrorMessageResourceType property has been specified. When an invalid request is now sent to the /api/cars URI, you will see the expected behavior (see Figure 13-15).

9781430247258_Fig13-15.jpg

Figure 13-15. Invalid POST request to /api/cars and a “400 Bad Request” response with custom validation messages stored inside a resource file

As the ErrorMessage, ErrorMessageResourceName, and ErrorMessageResourceType properties are actually properties of the ValidationAttribute base class, they are available to all validation attributes and serve the same purpose in all of them. You’ll recall that only the number of arguments supplied to create the complete error message will vary from one validation attribute type to another.

RangeAttribute

The RangeAttribute specifies the range constraints for the value of a field. There are three overloads of the RangeAttribute, and they commonly accept minimum and maximum values of the same type that are going to be compared.

Listing 13-17 shows an example of the RangeAttribute in use.

Listing 13-17.  The Car Class Containing Properties Marked with the RangeAttribute

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(maximumLength: 20)]
    public string Make { get; set; }

    [Required]
    [StringLength(maximumLength: 20)]
    public string Model { get; set; }

    public int Year { get; set; }

    [Range(minimum: 0F, maximum: 500000F)]
    public float Price { get; set; }
}

Here, the Price property is marked with the RangeAttribute, indicating that the value cannot be lower than 0 or higher than 500000. This is one of the overloads of the RangeAttribute, which accepts two Double-type parameters. Another one accepts Int32-type parameters for minimum and maximum values.

When a POST request against /api/cars containing an invalid value for the Price field according to our RangeAttribute is sent, a “400 Bad Request” response should come back, along with the message body displaying the validation error message (see Figure 13-16).

9781430247258_Fig13-16.jpg

Figure 13-16. Invalid POST request to /api/cars and a “400 Bad Request” response

The RangeAttribute is applicable not only to Double and Int32 types. Any type that implements a System.IComparable interface can be used along with the RangeAttribute.

Listing 13-18 adds to Car class a new property, named PurchasedOn, which is of type DateTime. This property is also marked with RangeAttribute.

Listing 13-18.  The Car Class Containing Properties Marked with RangeAttribute for a DateTime Field

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(maximumLength: 20)]
    public string Make { get; set; }

    [Required]
    [StringLength(maximumLength: 20)]
    public string Model { get; set; }

    public int Year { get; set; }

    [Range(minimum: 0F, maximum: 500000F)]
    public float Price { get; set; }

    [Range(type: typeof(DateTime),
        minimum: "2010-01-01", maximum: "9999-12-31")]
    public DateTime PurchasedOn { get; set; }
}

This overload of the RangeAttribute accepts three parameters. The first one is of the field type. The second and the third hold the minimum and maximum values. If the supplied value for the field is not within the specified range, a validation error message will be generated (see Figure 13-17).

9781430247258_Fig13-17.jpg

Figure 13-17. Invalid POST request to /api/cars and a “400 Bad Request” response

A DateTime type is used here to demonstrate this feature, but as was indicated before, you can use any type that implements the System.IComparable interface along with the RangeAttribute.

RegularExpressionAttribute

Sometimes a field value comes with specific and dynamic needs. In such cases, the built-in validation attributes are not going to work for those needs. One way to validate properties in those cases is to create a custom validation, which you will see in the “Custom Validation” section further along in this chapter. However, the .NET Framework provides a handy validation attribute for regular expressions.

The RegularExpressionAttribute provides a way to supply a regular expression so that a data field can validate according to that expression. Added to Listing 13-19 is a new property, ImageName, that indicates the image name for the Car entity. In accord with business logic, only file names that have a jpg, png, gif, or bmp extension are accepted. This property is marked with the RegularExpressionAttribute, by passing the regular expression for this logic.

Listing 13-19.  The Car Class Containing a Property Marked with RegularExpressionAttribute

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(20)]
    public string Make { get; set; }

    [Required]
    [StringLength(20)]
    public string Model { get; set; }

    public int Year { get; set; }

    [Range(0, 500000)]
    public float Price { get; set; }

    [RegularExpression("([^\s]+(\.(?i)(jpg|png|gif|bmp))$)")]
    public string ImageName { get; set; }
}

image Note  In a real-world scenario, this is not the best way to constrain the format of a file. This example has been chosen only as a sample-use case for the RegularExpressionAttribute.

Let’s now send a POST request against /api/cars and use the “Make123.ico” value for the ImageName field. A “400 Bad Request” response should come back along with the validation error message (see Figure 13-18).

9781430247258_Fig13-18.jpg

Figure 13-18. Invalid POST request to /api/cars and a “400 Bad Request” response

You can see that the regular expression is embedded in the error message. As was done in this chapter’s “StringLengthAttribute” section, so too here it is probably better to customize the error message by making it friendlier.

If a POST request that meets our conditions is sent, there shouldn’t be any validation issues, and a “201 Created” response should be returned (see Figure 13-19).

9781430247258_Fig13-19.jpg

Figure 13-19. Valid POST request to /api/cars and a “201 Created” response

EmailAddressAttribute

The most common use case for the RegularExpressionAttribute is for validating an e-mail address format. Although this is not a strong validation of an e-mail address (i.e., as verifying its existence), the e-mail address format should be validated as early as possible.

The EmailAddressAttribute is one of the new validation attributes introduced with .NET Framework 4.5. Under the hood, this attribute uses a regular expression to validate an e-mail address, but it is abstracted away from the developer. Listing 13-20 shows the Person class, which has a property named EmailAddress and is marked with the EmailAddressAttribute.

Listing 13-20.  The Person Class

public class Person {

    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Surname { get; set; }

    [EmailAddress]
    public string EmailAddress { get; set; }
}

As has been done so far, InvalidModelStateFilterAttribute has again been registered. There is also a PeopleController this time (see Listing 13-21).

Listing 13-21.  The PeopleController

[InvalidModelStateFilter]
public class PeopleController : ApiController {

    private readonly PeopleContext _peopleCtx = new PeopleContext();
        
    // GET /api/people
    public IEnumerable<Person> Get() {

        return _peopleCtx.All;
    }

    // POST /api/people
    public HttpResponseMessage PostPerson(Person person) {

        var createdPerson = _peopleCtx.Add(person);
        var response = Request.CreateResponse(HttpStatusCode.Created, createdPerson);
        response.Headers.Location = new Uri(
            Url.Link("DefaultHttpRoute", new { id = createdPerson.Id }));

        return response;
    }
}

The PeopleContext class here has the same implementation as the CarsContext class in Listing 13-6. When a GET request is sent to /api/people, the list of all existing people inside the collection is returned (see Figure 13-20).

9781430247258_Fig13-20.jpg

Figure 13-20. GET request to /api/people and a “200 OK” response along with the response body

Let’s try to add a new Person entity to the collection by supplying an invalid e-mail address (see Figure 13-21).

9781430247258_Fig13-21.jpg

Figure 13-21. Invalid POST request to /api/people and a “400 Bad Request” response

Notice that the EmailAddress field value inside the request body doesn’t contain a dot (.) before the com. This makes the value an invalid e-mail address. So a “400 Bad Request” came back along with the validation errors inside the response body. If a POST request is sent with a valid e-mail address, the entry should be created without any problem (see Figure 13-22).

9781430247258_Fig13-22.jpg

Figure 13-22. Valid POST request to /api/people and a “201 Created” response

MaxLengthAttribute and MinLengthAttribute

MaxLength and MinLength are individual validation attributes that have been introduced with .NET Framework 4.5. Respectively, they specify the maximum and the minimum length of an array or a data string that is allowed in a property. You will recall that there is already an attribute for validating the length of a string value. So for this function these attributes might have no appeal, but they really come in handy for constraining the length of an array field.

Listing 13-22 adds a string array property to the Car class we have been working with. That property represents the associated tags for the Car entity.

Listing 13-22.  The Car Class

public class Car {

    public int Id { get; set; }

    [Required]
    [StringLength(20)]
    public string Make { get; set; }

    [Required]
    [StringLength(20)]
    public string Model { get; set; }

    public int Year { get; set; }

    [Range(0, 500000)]
    public float Price { get; set; }

    [MinLength(1)]
    [MaxLength(4)]
    public string[] Tags { get; set; }
}

The Tags property has also been marked with two validation attributes: MinLength and MaxLength. These attributes specify that the minimum length allowed for this array is 1 and the maximum is 4.

When a GET request is sent to /api/cars, the cars list will come back along with the tags (see Figure 13-23).

9781430247258_Fig13-23.jpg

Figure 13-23. GET request to /api/cars and a “200 OK” response along with the response body

Let’s now send a POST request to add a new Car entry containing no tag (see Figure 13-24).

9781430247258_Fig13-24.jpg

Figure 13-24. Invalid POST request to /api/people and a “400 Bad Request” response

As expected, MinLengthAttribute raised a validation error message because the minimum required tag length was set to 1. Let’s now do the opposite: send a POST request to add a new Car entry containing five tags (see Figure 13-25).

9781430247258_Fig13-25.jpg

Figure 13-25. Invalid POST request to /api/people and a “400 Bad Request” response

This time, the MaxLengthAttribute complains because the maximum number of tags allowed is four (see Figure 13-26).

9781430247258_Fig13-26.jpg

Figure 13-26. Valid POST request to /api/cars and a “201 Created” response

Custom Validation

.NET Framework provides a handful of validation attributes that are useful for most common use cases. Although these attributes will cover most needs, there will still be some custom validation needs. The existing validation system makes implementing your own custom validation logic elegant and fairly easy.

There are two ways of providing custom validation logic with this system. One is to create custom validation attributes, and the other is to implement the IValidatableObject interface. This section will look at these two options separately and offer examples for each.

Creating Custom Validation Attributes

At the beginning of the “Data Annotation Validation Attributes” section in this chapter, brief mention was made that all validation attributes are derived from the System.ComponentModel.DataAnnotations.ValidationAttribute abstract class. This class will be used as the base class for the custom validation attributes.

When you are working with the ValidationAttribute class, there are two methods you must override to perform the validation. One is the IsValid method that returns a Boolean value. The other is the IsValid method that returns a ValidationResult instance and accepts two parameters, one for the value of the object and one for the ValidationContext instance.

Another method generally used is the FormatErrorMessage method. It accepts a string parameter for the name of the field being validated and returns a string for the error message. This method is later called to format the error message.

The ValidationAttribute class also has three other methods, but they are not virtual methods (in other words, they cannot be overridden) and are called during the validation stage. Listing 13-23 shows all the methods that the ValidationAttribute abstract class holds.

Listing 13-23.  Public Methods of the ValidationAttribute Class

public abstract class ValidationAttribute : Attribute {

    public virtual string FormatErrorMessage(string name);

    public ValidationResult GetValidationResult(
        object value, ValidationContext validationContext);
    
    public virtual bool IsValid(object value);
    
    protected virtual ValidationResult IsValid(
        object value, ValidationContext validationContext);

    public void Validate(object value, string name);

    public void Validate(
        object value, ValidationContext validationContext);
}

The ValidationAttribute properties carry the properties shown in Table 13-1. Only one of them, RequiresValidationContext, is virtual. RequiresValidationContext, a read-only, Boolean-type property, indicates whether the attribute requires a validation context, which gives a hint to validators that the object value alone is enough to validate or else that the ValidationContext is needed to perform the validation. The default value for this property is false.

Two other types—System.ComponentModel.DataAnnotations.ValidationContext and System.ComponentModel.DataAnnotations.ValidationResult—have been mentioned here. The ValidationContext class describes the context in which a validation check is performed and carries a lot of information necessary to perform the validation, including the type and the instance of the parent object. If you prefer to perform the validation by overriding the IsValid method that accepts a ValidationContext parameter, you will get access to all of this information. This method’s return type is ValidationResult. ValidationResult is basically the carrier for the error message, which holds the message itself and the associated field names for the validation error message.

Let’s create a custom validation attribute to get a better understanding of how it really works.

A Custom Validation Attribute Sample: GreaterThanAttribute

Some cases will require comparing two values, where one of them will be greater than the other. One canonical use case involves comparing the start date and end date for a particular instance. The end date always needs to be greater than the start date, and among the validation attributes that the .NET Framework offers, none performs such a validation. So this section will implement a general-purpose GreaterThanAttribute that works for all IComparable types.

Listing 13-24 shows the initial implementation of this GreaterThanAttribute.

Listing 13-24.  Initial Implementation of the GreaterThanAttribute

[AttributeUsage(AttributeTargets.Property)]
public class GreaterThanAttribute : ValidationAttribute {

    public string OtherProperty { get; private set; }

    public override bool RequiresValidationContext {

        get {
            return true;
        }
    }

    public GreaterThanAttribute(string otherProperty) :
        base(errorMessage: "The {0} field must be greater than the {1} field.") {

        if (string.IsNullOrEmpty(otherProperty)) {

            throw new ArgumentNullException("otherProperty");
        }

        OtherProperty = otherProperty;
    }

    public override string FormatErrorMessage(string name) {

        return string.Format(
            CultureInfo.CurrentCulture,
            base.ErrorMessageString,
            name,
            OtherProperty);
    }

    protected override ValidationResult IsValid(
        object value, ValidationContext validationContext) {
    
        // Implementation comes here
    }
}

There are several things to note about this initial implementation.

  • First of all, the GreaterThanAttribute is derived from the ValidationAttribute class. This is how our custom attribute will be recognized as a validation attribute.
  • The RequiresValidationContext property has been overridden to return true because our validation logic requires the ValidationContext.
  • A string property named OtherProperty has been added. It represents the name of the property that will be compared.
  • Our single constructor has been set up. That constructor accepts a value for the OtherProperty string property. Also, the base keyword is set up to call the constructor of our base class to pass the error message structure.
  • The FormatErrorMessage method to format the error message has been overridden. Notice that the ErrorMessageString property of the ValidationAttribute is used here because this property will ensure use of the right error message by inspecting any custom error message that is set or any resource key that is supplied.

Also overridden and left unimplemented is the IsValid method that returns ValidationResult. Listing 13-25 shows the implementation of the IsValid method.

Listing 13-25.  Implementation of the IsValid Method

protected override ValidationResult IsValid(
    object value, ValidationContext validationContext) {

    IComparable firstValue = value as IComparable;
    IComparable secondValue = GetSecondValue(
        validationContext.ObjectType,
        validationContext.ObjectInstance);

    if (firstValue == null || secondValue == null) {

        throw new InvalidCastException(
            "The property types must implement System.IComparable");
    }

    if (firstValue.CompareTo(secondValue) < 1) {

        return new ValidationResult(
            this.FormatErrorMessage(
                validationContext.DisplayName));
    }

    return ValidationResult.Success;
}

The logic underlying this method is fairly simple. First of all, grab the values to be compared. Before performing the comparison, ensure that both objects implement the IComparable interface. Last, compare them. If the value that the validation attribute is applied to is not greater than the other, construct a new ValidationResult by passing the error message. If there are no validation issues, use the static Success field of the ValidationResult class to indicate that there are no errors. It also works if a null is returned (actually, that is basically what ValidationResult.Success is), but doing it as just described will make the code more readable.

Inside the IsValid method, a private method named GetSecondValue is used to get the value of the second instance. Listing 13-26 shows the implementation of the GetSecondValue method.

Listing 13-26.  Implementation of the Private GetSecondValue Method

private IComparable GetSecondValue(Type type, object instance) {

    PropertyInfo propertyInfo =
        type.GetProperty(this.OtherProperty);

    if (propertyInfo == null) {

        throw new Exception(
            string.Format(
                "The property named {0} does not exist.",
                this.OtherProperty));
    }

    var value = propertyInfo.GetValue(instance, null);
    return value as IComparable;
}

To show the new custom method in action, two properties have been added to the Car class used so far. These properties will indicate the start and end dates of the sale for the particular Car, and the GreaterThanAttribute will be applied to the SalesEndsAt property (see Listing 13-27).

Listing 13-27.  The Car Class

public class Car {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }

    public DateTime SalesStartsAt { get; set; }

    [GreaterThan("SalesStartsAt")]
    public DateTime SalesEndsAt { get; set; }
}

If a POST request is sent to /api/cars with a request body containing a Car entity along with invalid SalesStartsAt and SalesEndsAt properties, a “400 Bad Request” error will be returned (see Figure 13-27).

9781430247258_Fig13-27.jpg

Figure 13-27. Invalid POST request to /api/people and a “400 Bad Request” response

If the request message body meets the set expectations, the request should go through, and a “201 Created” response will be returned (see Figure 13-28).

9781430247258_Fig13-28.jpg

Figure 13-28. Valid POST request to /api/cars and a “201 Created” response

IValidatableObject Custom Validation

Sometimes you need a custom validation attribute, and you feel that the validation attribute to be implemented should be tightly coupled to a specific entity and not be used anywhere else. In such a case, the right solution is to implement the System.ComponentModel.DataAnnotations.IValidatableObject interface instead of providing a new validation attribute.

The IValidatableObject interface has only one method, named Validate, and its return type is IEnumerable<ValidationResult> (see Listing 13-28).

Listing 13-28.  The IValidatableObject Interface

public interface IValidatableObject {

   IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
}

ASP.NET Web API has built-in support for this validation type, and the Validate method will be called at the validation stage of the object. The Validate method accepts a parameter that is a type of ValidationContext. As was seen earlier, this class carries all the necessary information for validating the object. Furthermore, inside the Validate method, the properties of the class can be directly accessed and validation performed according to them.

Let’s assume that you won’t accept any Car entities whose price is higher than 250000 and which was manufactured before 2010. This is a corner case issue, and if you try to solve this validation problem with a custom validation attribute, the attribute probably won’t be used in any other place. Listing 13-29 shows a sample where the IValidatableObject interface is leveraged.

Listing 13-29.  The Car Class Implementing the IValidatableObject

public class Car : IValidatableObject {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }

    public int Year { get; set; }

    public float Price { get; set; }

    public IEnumerable<ValidationResult> Validate(
        ValidationContext validationContext) {

        if (Year < 2010 && Price > 250000F) {

            yield return new ValidationResult(
                "The Price cannot be above 250000 if the Year value is lower than 2010.",
                new string[] { "Price" });
        }

        yield return ValidationResult.Success;
    }
}

In this implementation, the Validate method is no different from the IsValid method of the ValidateAttribute. As expected, if an invalid POST request is sent, the validation error should be as shown in Figure 13-29.

9781430247258_Fig13-29.jpg

Figure 13-29. Invalid POST request to /api/people and a “400 Bad Request” response

If the POST request is valid, there should be a “201 Created” response (see Figure 13-30).

9781430247258_Fig13-30.jpg

Figure 13-30. Valid POST request to /api/cars and a “201 Created” response

Summary

Input validation is a big deal for any application that requires user interaction with the back end. We developers tend to see every user input as evil and want to act on that input before processing it. ASP.NET Web API supports a great validation scenario out of the box. By embracing the .NET Data Annotation Validation, ASP.NET Web API already covers many validation cases, but it is also extremely easy to implement your own logic, as you have seen. On the other hand, validation components inside the framework are all pluggable, and you can extend the validation system of the framework by adding your own validation provider implementations or tweaking the existing ones.

Besides all of these great validation features and the system’s extensible architecture, ASP.NET Web API also offers a very unobtrusive way of aggregating validation error messages inside the ModelStateDictionary. As this chapter has shown, you can return friendly validation error messages just by implementing a simple action filter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.239.41