Using PostSharp to Add ServiceStack Route Attributes
Updated: The latest version of ServiceStack (v3.9.53) now includes the .ext routing option using the AllowRouteContentTypeExtensions configuration property. See The comments section for more information. I would use the framework instead of this custom approach. This example now really serves as a demonstration of using PostSharp to apply attributes in general.
An aspect oriented approach to adding data format to the route
A common approach in restful APIs is to allow the consumer to provide the content type in the route as a file extension. For example: /api/movies.json or /api/movies.xml?genre=Action.
Currently, ServiceStack supports different formats via the querystring “format” parameter or via the headers. You can easily override the HTTP content type by simply specifying urls like the following: /api/movies?format=json. One way to make ServiceStack work with the file extensions is to explicitly put the formats into the RouteAttribute paths and adding your own RequestFilter to check for a provided format. See the RequestFilter code at the bottom of the post.
For Example:
[Routes("/shows.json")] [Routes("/shows.xml")] [Routes("/shows.csv")] [Routes("/shows.jsv") [Routes("/shows.jsv") [Routes("/shows") However, this becomes cumbersome to manage. If you are using Aspect oriented programming and PostSharp there is an easier way. You can create an aspect to decorate your DTOs with the accepted content types.
PostSharp Attribute to Turn one Route attribute into many route attributes.
/// /// Turns one Route into many routes with different data formats /// /// For example: /// /// [RoutesWithDataFormats("/shows") => [Routes("/shows.json")] /// [Routes("/shows.xml")] /// [Routes("/shows.csv")] /// [Routes("/shows.jsv")] /// [Routes("/shows")] /// [MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)] [Serializable] public sealed class RoutesWithDataFormatsAttribute : TypeLevelAspect, IAspectProvider { private string _path; private string _verbs; public RoutesWithDataFormatsAttribute(string path, string verbs) { _path = path; _verbs = verbs; } public RoutesWithDataFormatsAttribute(string path) { _path = path; } // This method is called at build time and should just provide other aspects. public IEnumerable ProvideAspects(object targetElement) { var attributes = new List(); var formats = new[] {"json", "xml", "csv", "jsv"}; // Add data format routes first! foreach (var format in formats) { var dataFormatPath = string.Format("{0}.{1}", _path, format); attributes.Add(CreateAspect(targetElement, dataFormatPath, _verbs)); } // Then add plain route last attributes.Add(CreateAspect(targetElement, _path, _verbs)); return attributes; } private AspectInstance CreateAspect(object targetElement, string path, string verbs) { var constructor = string.IsNullOrWhiteSpace(verbs) ? new ObjectConstruction(typeof(RouteAttribute), path) : new ObjectConstruction(typeof(RouteAttribute), path, verbs); var introduceDataContractAspect = new CustomAttributeIntroductionAspect(constructor); return new AspectInstance(targetElement, introduceDataContractAspect); } } AppHost Request Filters to make it work
You will of course need to filter for these formats and override the content types. You can do this by adding a RequestFilter in your AppHost.
// Filters happen before every request! Even before auth check this.RequestFilters.Add((httpReq, httpResp, requestDto) => { // allow .xml and .json if (httpReq.PathInfo.EndsWith(".xml")) { httpReq.ResponseContentType = ContentType.Xml; } else if (httpReq.PathInfo.EndsWith(".json")) { httpReq.ResponseContentType = ContentType.Json; httpResp.ContentType = ContentType.Json; } // and ect for the other data types. You can refactor this into cleaner code });
Hey Joe,
Thanks for this post, it’s looks like a good approach for automating future repetitive tasks.
It inspired be to bake this feature in the framework in the latest release of ServiceStack (v3.9.53):
https://github.com/ServiceStack/ServiceStack/wiki/Routing#content-negotiation
Here are some tests showcasing the feature if you want to see it in action:
https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.Endpoints.Tests/RouteTests.cs
Demis Bellot (@demisbellot)
June 18, 2013 at 1:32 am
Thanks for including this into the framework. I believe this is where it belongs since it is so closely tied with the smart routing resolution.
I will likely update this blog post to use the source but keep the example available for ways to utilize PostSharp and attributes.
jokecamp
June 18, 2013 at 8:37 am
Yep agreed this approach of using AOP/PostSharp is useful to know about and should come in handy in the near future!
Demis Bellot (@demisbellot)
June 18, 2013 at 10:14 am