ASP.NET Web API allows us to create HTTP services. These services are used by interested clients. We might run into several situations which might be unexpected ones. They might be because of client's input data or an uneventful situation on the service side. In these cases, we need to provide client with the relevant error information. In the previous post, we touched upon this area, where we discussed various types of responses from Web API. It seems better to further investigate this area.
Let us first have a look at how Web API life cycle explains how it handles the error. This is an excerpt from the official poster. The poster can be downloaded form Microsoft's download center.
From the message life cycle, it is quite clear that Exceptions generated during the execution of controller's requested action is passed through Exception filter which generates an Error Response.
Providing Error Details to Clients
When an error is generated for a client's request, ASP.NET Web API returns the information to the requester. So an external client would see the following in response of his request.
The API supports pushing additional details to the client. The is configured in HttpConfiguration used by the service. Here we can select an appropriate value for IncludeErrorDetails property of the configuration object. This is specially useful in debugging and test environments where we need to know what went wrong when an error is received as a result of client's request. In production environments this would rarely be the case as we don't want to expose these details (including stack trace) to our clients. This is because this doesn't help him, in any way, understanding the problem better. This might also leak service implementation to the users jeopardizing the security implementation.
Exception Filters
It must be remembered that the response pipeline has a special handling for HttpResponseException. This type of exceptions don't flow through the exception filter. The same is true for error response created using Request.CreateErrorResponse() in ApiController. These two cases wouldn't be handled by any external error handling APIs and frameworks including ELMAH. This is because of manual creation of a customized response that the application wants to push to client. The generated response shouldn't be tampered by any framework stages.
ASP.NET Web API supports exception filters to respond to cases of exceptions. It supports a specialized IFilter for exceptions, IExceptionFilter. We need to provide our customized ExceptionFilter to handle these exceptions.
Here IFilter interface is a special interface. ASP.NET Web Api has filters in other areas as well. All of these filters are defined in System.Web.Http.Filters namespace. All of these specialized interfaces inherit from this base interface. Be ready to see this interface in a number of future posts.
Let's introduce a brand new customized Exception type. We are planning to throw this exception when a client provides student information to request further data. In the cases where there is no corresponding student for the specified StudentId, we can throw this exception.
We can now introduce a new ExceptionFilter to handle this exception type. Let's name it as StudentNotFoundExceptionFilterAttribute. Yes I am also one of those who like big meaningful names ;). We can just inherit from ExceptionFilterAttribute type provided by ASP.NET Web API and provide the definition of OnException method as follows:
ASP.NET Web API supports the application of these filters on the scopes of action, controller and global. If we need this filter to be used for certain actions, we can decorate the actions with the filter. For the cases, where all actions should use a specific filter for exception situation, we can apply the filter on controller level. A service might also have a number of controllers. If we need the same filter to be used by all the controllers, we might apply the filter on global level.
A global exception filter is configured in the HttpConfiguration. The configuration has a Filters collection. The collection supports filters implementing IFilter interface.
Let's update InstituteController adding the following action to the controller. Here we are throwing the StudentNotFoundException when client passes a StudentId of a non-existent user.
Here we are sending such a request. Just look at the response, here the exception filter is being correctly applied. We can verify the exact error message we specified for the response in such cases.
As recommended on MSDN, you might want to add the same ExceptionFilterAttribute in GlobalConfiguration. For ASP.NET Web API, this can be added in FilterConfig. This also compiles fine but as we run it, the application fails with InvalidOperationException. When we look at the details of the exception, it doesn't really make sense. This is because we know that the attribute inherits from ExceptionFilterAttribute, which does implement IExceptionFilter interface.
But the message is right. Although it implements IExceptionFilter interface, it doesn't implement the one expected by the filter collection here.
Overriding Exception Filters From Wider Scope
As we just discussed, we can apply exception filters on action, controller and global levels. When an action execution causes an exception to be thrown, all filters in the processing chain are execution in the sequence of action, controller and then global filters. The filters are on the same level are executed in the sequence that they are defined. If an exception has been handled on a lower level, we might not want to bubble up the exception to the filters defined at a higher level. ASP.NET Web API2 has provided an option to override the filters at wider scopes. This can be achieved by OverrideExceptionFiltersAttribute. The type is sealed, an implementations of IOverrideFilter interface.
As you can notice above, there are also other implementations of the same interface to be used with action, authentication and authorization filters. Let us concentrate on exception filters for now. Let us introduce a global exception filter to be used for all exceptions. But we don't want this to be used if a controller or action itself has defined handlers for an exception.
Now let us add the filter to HttpConfiguration for the whole Web API on a global level. Let us update controller's action providing explicit details of exception filters to use the attribute to override any handling of exceptions in the pipeline on a higher level.
Based on our theory described above, when an exception is thrown from this action, ServiceExceptionFilterAttribute should not be used, as it added on global level. So the HTTP response provided on StudentNotFoundExceptionFilterAttribute should be used. We can verify this by viewing the request in Fiddler, it should still generate the same response as above, no matter we have a handler on a global level, as the one on the global level should have been overridden and not used by the framework.
HttpResponseException and its Special Handling
As we discussed above, HttpResponseException is a specialized Exception type introduced with ASP.NET Web API. This exception has a spcialized handling by the framework. It doesn't cause exception filters to be used by the framework. The definition of type can be seen as the following:
Now let us see how we can use HttpResponseException. Here we are creating an HttpResponseMessage and building up an HttpResponseException instance. We are throwing this exception when we can't find a student identified by the StudentId provided by the client. The interesting part is that the action is decorated with an exception filter. In a general case of any other exception type, this exception filter would be used to build up a response in case of an exception. In this particular case, since HttpResponseException already holds an HttpResponseMessage, it doesn't seem a need to build up another response, which is exactly what is happening, and exception filter shouldn't be used.
Here CreateErrorResponse is one of a list of extension methods for HttpRequestMessage type introduced in System.Net.Http namespace in System.Web.Http assembly. For a web client, the details of the exception can be provided and displayed as follows:
This looks very limited information is provided to the client. Our client can be interested in seeing more details to the actual cause of the problem. In order to do that the API has provided a new type, called HttpError. The definition of the type is as follows:
Now let's update the response to include some details to the error info returned to the client. This is extremely useful for the requesting client to determine the actual cause of the problem.
The idea is to add a list of details (key / value pairs) to an HttpError instance, and then use this to build up an HttpResponseMesssage. The response is formatted based on the Accepts header of client's HTTP request.
There are following values of keys defined in HttpErrorKeys type in System.Web.Http. They are used for standard errors. As the type required for the keys for this Dictionary [HttpError], they are all of string type.
Errors generated due to application of constraints
The services implemented with ASP.NET Web API also return error responses based on the failing route constraints. Here is an example action to return the courses attended by a particular student. The example is using alpha constraint.
All ASP.NET Web API route constraints implement IHttpRouteConstraint interface. AlphaRouteConstraint is a special type of RegexRouteConstraint where the patter is already prefixed.
All the default route constraints are available in System.Web.Http.Routing.Constraints namespace in System.Web.Http assembly. Here is a list of all default constraints available in the namespace.
A failing constraint just means that invalid route. Since this is a REST based service resources are identified and requested based on URIs. This means that a resource has been requested with some URI for which there is no resource / action is available. Hence this is an invalid request resulting in 404 message as follows:
We can also implement our custom route constraints. Here is a sample route constraint, named Max100Constraint. This constraint ensures a fixed 100 as the max value. Since we already have a MaxRouteConstraint providing a check on maximum values, we can just use extend it providing a fixed maximum value in base class constructor. We need to provide the details of the new constructor to the HttpConfiguration. It uses an implementation of IInlineConstraintResolver to provide the details of these constraints. The API provides a default implementation of the interface. We can just add the new route constraint to the default implementation.
Now we can use the constraint as we are using any default route constraint. We need to make sure that the identifier used to identify the constraint for route definition must match the key used to register it with IInlineConstraintResolver.
Now for all courseIds > 100, the service would result a 404 - NOT FOUND response.
Model Binding Errors
In the above description, we determined how we can verify routes based on registered constraints. We also saw how values from routes are copied to action parameters. They are simply copied based on their location in the route description to the action parameter identified by their name. Now let's take it a little further, HTTP supports clients to post data. The data can be posted using query parameters or request's content. This data can be received by ASP.NET Web API Service. The assignment of the data to the action parameters. Assignment of this data to action parameters is called Model Binding.
ASP.NET Web API looks for the action parameters in the client request. The data can be picked up from two places, query URL and request content based on that it is a simple or complex type. By default, simple types are picked up form request URL and complex types are from the content. We can validate this model as received from clients. The validation can fail and result in errors. The API has included following types for providing details of the errors on the service side.
In order to determine the model binding errors, ApiController has a ModelState property. This is of type ModelStateDictionary. We can access this from action's code to determine the validation results for the data passed with request.
We have been using data for Student type to be passed to the service. Let's update the type and introduce Data Annotation attributes for validation. Here we are using Range and Required field validation for StudentId and StudentName properties respectively.
Now let's prepare a post request in Fiddler and pass invalid student data to the service [StudentId = -1 with missing StudentName] as follows:
Obviously, the request would fail the validation requirement specified for our model. Let's try to see how ApiController.ModelState property shows this validation failure. Using the IsValid property, we can verify that model is not valid. The other details of the failure can be used to build up a response to be returned to the client.
We can also use an IActionFilter to filter the request before action code is executed. This can keep the action's code clean as we will not put any code to check if model is valid. This is a special case for interface type IFilter. Like IExceptionFilter (described above), this can also be used on action, controller or global scope. As is apparent from the request pipeline, action filters are executed after model binding. So by the time, they are invoked by the framework, the validation results are already in.
If we look at the definition of ActionFilterAttribute, there are two virtual methods. They are invoked at particular stages of message pipeline in ASP.NET Web API service. Actually, action execution is sandwiched between these two methods. The particular method we are interesting in, is OnActionExecuting(). This method is invoked by the framework before an action is executed for which this filter is enabled.
We can use the HttpActionContext argument in OnActionExecuting() method to determine the results of model validation. It is also possible to short-circuit the processing and generating a response from here without ever invoking the intended action, which is quite useful for model validation failures to generate an appropriate error response. Here we are creating an error response based on model validation results.
We can add action filters to ASP.NET Web API in the same way as an exception filter. Here we are decorating an action with the action filter attribute defined above.
Now an invalid request would result in a response with error details as follows:
What would happen if we throw an exception from action filter? From the official message pipeline it is not very apparent if ExceptionFilter (s) would be used for an exception thrown during execution of Action filters before action execution. Actually, if an exception is thrown from Action filters, the framework still uses the configured exception filters.
Download
Saturday, December 21, 2013
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment