{"id":4172,"date":"2015-09-10T08:37:37","date_gmt":"2015-09-10T08:37:37","guid":{"rendered":"https:\/\/ushipblogsubd.wpengine.com\/?p=4172"},"modified":"2025-09-03T16:06:42","modified_gmt":"2025-09-03T16:06:42","slug":"implementing-api-errors-web-api-fluentvalidation","status":"publish","type":"post","link":"https:\/\/ushipblogsubd.wpengine.com\/shipping-code\/implementing-api-errors-web-api-fluentvalidation\/","title":{"rendered":"Implementing API Errors with Web API and FluentValidation"},"content":{"rendered":"<p>In a <a href=\"https:\/\/ushipblogsubd.wpengine.com\/shippingcode\/actionable-restful-api-errors\/\">previous article<\/a>, we talked about how APIs should return RESTful error responses so that API clients can act on them. There are plenty of articles like <a href=\"http:\/\/leojh.com\/2013\/09\/19\/integrating-fluentvalidation-with.html\" target=\"_blank\" rel=\"noopener\">this one<\/a> that talk about how to actually integrate the <a href=\"https:\/\/github.com\/JeremySkinner\/FluentValidation\/\" target=\"_blank\" rel=\"noopener\">FluentValidation<\/a> framework into the Web API pipeline, so we won\u2019t go into the plumbing details. Below is a simple implementation of the RESTful API response model using these tools.<\/p>\n<h2>Validation Options<\/h2>\n<p>There are numerous options one has when choosing how to validate input using Web API. A classic option commonly used in Web API tutorials is using attributes for model validation. We started off going this route, but ran into a couple of issues:<\/p>\n<ul>\n<li>Some of our validation rules depended on two adjacent properties. We found ourselves constantly overriding the default attribute validation behavior to account for this.<\/li>\n<li>In many cases, our input models had shared nested models, but we needed different validation for the nested properties. Adding an attribute to the shared nested model did not allow it to be reused with different validation rules.<\/li>\n<\/ul>\n<p>FluentValidation is a very flexible validation framework and is perfect for our needs.<\/p>\n<h2>WithState<\/h2>\n<p>FluentValidation provides an extension method when building validation rules called <a href=\"https:\/\/github.com\/JeremySkinner\/FluentValidation\/blob\/87aa512569e58184e3df72f50825566186617606\/src\/FluentValidation\/DefaultValidatorOptions.cs#L314\" target=\"_blank\" rel=\"noopener\">WithState<\/a>. This method allows you to add any context you wish to the current rule. Whatever object you add to this context will be available to you when the rule fails. Let\u2019s see it in action.<\/p>\n<p>First, define an object that will hold the data we will need when validation fails:<br \/>\n[cc lang=&#8221;csharp&#8221;]<br \/>\npublic class ErrorState<br \/>\n{<br \/>\npublic ErrorCode ErrorCode { get; set; }<br \/>\npublic string DocumentationPath { get; set; }<br \/>\npublic string DeveloperMessageTemplate { get; set; }<br \/>\npublic string UserMessage { get; set; }<br \/>\n}<\/p>\n<p>public enum ErrorCode<br \/>\n{<br \/>\nNone = 0,<br \/>\nRequired = 10271992,<br \/>\nTooShort = 11051992<br \/>\n}<br \/>\n[\/cc]<\/p>\n<p>Next, define the validator that takes advantage of the WithState method and uses the aforementioned ErrorState object to encapsulate the type of validation failure:<br \/>\n[cc lang=&#8221;csharp&#8221; escaped=&#8221;true&#8221;]<br \/>\npublic class UserInputModelValidator : AbstractValidator&lt;UserInputModel&gt;<br \/>\n{<br \/>\npublic UserInputModelValidator()<br \/>\n{<br \/>\nRuleFor(x =&gt; x.Username)<br \/>\n.Must(x =&gt; x.Length &gt;= 4)<br \/>\n.When(x =&gt; x.Username != null)<br \/>\n.WithState(x =&gt; new ErrorState<br \/>\n{<br \/>\nErrorCode = ErrorCode.TooShort,<br \/>\nDeveloperMessageTemplate = &#8220;{0} must be at least 4 characters&#8221;,<br \/>\nDocumentationPath = &#8220;\/Usernames&#8221;,<br \/>\nUserMessage = &#8220;Please enter a username with at least 4 characters&#8221;<br \/>\n});<\/p>\n<p>RuleFor(x =&gt; x.Address.ZipCode)<br \/>\n.Must(x =&gt; x != null)<br \/>\n.When(x =&gt; x.Address != null)<br \/>\n.WithState(x =&gt; new ErrorState<br \/>\n{<br \/>\nErrorCode = ErrorCode.Required,<br \/>\nDeveloperMessageTemplate = &#8220;{0} is required&#8221;,<br \/>\nDocumentationPath = &#8220;\/Addresses&#8221;,<br \/>\nUserMessage = &#8220;Please enter a Zip Code&#8221;<br \/>\n});<br \/>\n}<br \/>\n}<br \/>\n[\/cc]<\/p>\n<h2>Returning a RESTful Response<\/h2>\n<p>Before we can return the validation\u2019s result to the client, we must first map it over to our RESTful API error structure.<\/p>\n<p>The following objects are code representations of the JSON that we will send back to the client:<br \/>\n[cc lang=&#8221;csharp&#8221;]<br \/>\npublic class ErrorsModel<br \/>\n{<br \/>\npublic IEnumerable Errors { get; set; }<br \/>\n}<\/p>\n<p>public class ErrorModel<br \/>\n{<br \/>\npublic ErrorCode ErrorCode { get; set; }<br \/>\npublic string Field { get; set; }<br \/>\npublic string DeveloperMessage { get; set; }<br \/>\npublic string Documentation { get; set; }<br \/>\npublic string UserMessage { get; set; }<br \/>\n}<br \/>\n[\/cc]<br \/>\nNotice how ErrorModel is very similar to ErrorState. ErrorsModel is our contract to the outside world. No matter how our validation implementation changes, this class must stay the same. Conversely, ErrorState is free to change as you improve your validation layer. You can add convenience methods, new enums, etc. without worrying about changing the JSON response to the client.<\/p>\n<p>Once we run our validator, it is time for Web API to handle converting the result to something a client can consume. Below is code that can be placed in an ActionFilter:<br \/>\n[cc lang=&#8221;csharp&#8221; escaped=&#8221;true&#8221;]<br \/>\nprivate void ThrowFormattedApiResponse(ValidationResult validationResult)<br \/>\n{<br \/>\nvar errorsModel = new ErrorsModel();<\/p>\n<p>var formattedErrors = validationResult.Errors.Select(x =&gt;<br \/>\n{<br \/>\nvar errorModel = new ErrorModel();<br \/>\nvar errorState = x.CustomState as ErrorState;<br \/>\nif (errorState != null)<br \/>\n{<br \/>\nerrorModel.ErrorCode = errorState.ErrorCode;<br \/>\nerrorModel.Field = x.PropertyName;<br \/>\nerrorModel.Documentation = &#8220;https:\/\/developer.example.com\/docs&#8221; + errorState.DocumentationPath;<br \/>\nerrorModel.DeveloperMessage = string.Format(errorState.DeveloperMessageTemplate, x.PropertyName);<\/p>\n<p>\/\/ Can be replaced by translating a localization key instead<br \/>\n\/\/ of just mapping over a hardcoded message<br \/>\nerrorModel.UserMessage = errorState.UserMessage;<br \/>\n}<br \/>\nreturn errorModel;<br \/>\n});<br \/>\nerrorsModel.Errors = formattedErrors;<\/p>\n<p>var responseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest)<br \/>\n{<br \/>\nContent = new StringContent(JsonConvert.SerializeObject(errorsModel, Formatting.Indented))<br \/>\n};<br \/>\nthrow new HttpResponseException(responseMessage);<br \/>\n}<br \/>\n[\/cc]<br \/>\nOur filter is doing the following:<\/p>\n<ul>\n<li>Mapping our ErrorState object inline to the ErrorModel contract that we will be serializing<\/li>\n<li>Creating an HTTP response object with the serialized data, which is then sent to the client by wrapping the response in and throwing an HttpResponseException<\/li>\n<\/ul>\n<p>Now the client is ready to handle the nicely-structured API response.<\/p>\n<h2>Conclusion<\/h2>\n<p>FluentValidation is a powerful validation framework whose WithState method is a great entry point for custom logic including generating RESTful error responses. If you would like to see a full implementation, you can clone this <a href=\"https:\/\/github.com\/ivanmartinvalle\/actionable-restful-api-errors\" target=\"_blank\" rel=\"noopener\">GitHub repo<\/a>. Keep in mind that this is a rough example and will most likely need to be modified to meet your exact needs and current infrastructure.<\/p>\n<p>[amp-cta id=&#8217;8486&#8242;]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a previous article, we talked about how APIs should return RESTful error responses so that API clients can act on them. There are plenty of articles like this one that talk about how to actually integrate the FluentValidation framework into the Web API pipeline, so we won\u2019t go into the plumbing details. Below is&#8230;<a class=\"read-more\" href=\"https:\/\/ushipblogsubd.wpengine.com\/shipping-code\/implementing-api-errors-web-api-fluentvalidation\/\"> Read More<\/a><\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[295,2],"tags":[25,31,28,297,32],"class_list":["post-4172","post","type-post","status-publish","format-standard","hentry","category-shipping-code","category-company-news","tag-api","tag-fluentvalidation","tag-rest","tag-shipping-code","tag-web-api"],"acf":{"blog_post_content":[{"acf_fc_layout":"blog_post_entry_footer","blog_post_entry_footer_cta":[{"blog_post_entry_footer_cta_url":"https:\/\/www.uship.com\/","blog_post_entry_footer_cta_text":"Ready to Ship Something?","blog_post_entry_footer_onclick":""}]}]},"_links":{"self":[{"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/posts\/4172","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/comments?post=4172"}],"version-history":[{"count":0,"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/posts\/4172\/revisions"}],"wp:attachment":[{"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/media?parent=4172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/categories?post=4172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ushipblogsubd.wpengine.com\/wp-json\/wp\/v2\/tags?post=4172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}