CHAPTER 11
ServiceStack offers a good level of extensibility. There are two ways of extending ServiceStack: through filters and through plug-ins.
Filters are a simple concept; they allow us to execute some code before or after a service executes. Filters can be seen as a cross-cutting concern and allow a great deal of reusability of code. For instance, monitoring, authorization, authentication, logging, transaction processing, and validation can be implemented by filters. This is a great way of keeping the service code as clean as possible without implementing these same concepts over and over again.
There are two types of filters: Request filters and Response filters. We have seen this when we mentioned the Request and Response pipeline. A particularity of a filter is that it can have a priority, which means that we can control the order in which filters are going to be executed.

Filters are applied as attributes to a Request and Response DTO or, alternatively, to the service itself. There are some predefined ServiceStack filters that are executed before the user-applied ones. This default behavior can be changed if the priority of a user filter is set to a negative number, in which case it gets executed beforehand.
In the example, we will see how to create a Request and a Response filter and how to apply it to the service. The Request filter will count how many times the GET method has been called, and the Response filter will record how many successful requests have been processed. This recorded information will be exposed through a service called StatsCounterService.
The Request filters are executed before the service gets called. There are two ways of declaring the new Request filter, either by implementing the IHasRequestFilter interface or by using a RequestFilterAttribute base class. Using the RequestFilterAttribute is simpler as it already implements the basic functionalities; we only need to override the Execute() method.
In the following example, we will create the ServiceUsageStatsRequestFilter class that counts the number of times the Get() method is called. To store the data, we will use the MemoryCacheClient (ICacheClient) that will be injected at run time, hence the property called Cache. The filter also exposes the ServiceName property that should be specified every time the attribute is assigned to a service.
The Response filters are executed after the service has been executed. As for the Request filter, there are two ways of declaring the new Response filter: either by implementing the IHasResponseFilter interface or by using a ResponseFilterAttribute-derived attribute.
In the following example, we will create the ServiceUsageStatsRequestFilter class that counts the number of times a response status has been in the range of 200—which would generally mean that the call has been successful.
As for the Request filter, the same logic applies when it comes to the Cache and ServiceName declaration.
public class ServiceUsageStatsResponseFilter: ResponseFilterAttribute {
public string ServiceName { get; set; } //This property will be resolved by the IoC container. public ICacheClient Cache { get; set; }
public override void Execute(IHttpRequest req, IHttpResponse res, object responseDto) { string successKey = ServiceName + "_SUCCESSFUL"; if (res.StatusCode >= 200 && res.StatusCode < 300) { var item = Cache.Get<long>(successKey); Cache.Set(successKey, item + 1); } } } |
In order to enable the filters, we have to add the attributes to the service itself by applying the attributes as follows.
[ServiceUsageStatsRequestFilter(ServiceName = "ProductService")] [ServiceUsageStatsResponseFilter(ServiceName = "ProductService")] public class ProductService : ServiceStack.ServiceInterface.Service { … } |
In this case, by default, filters will be called for every method call (GET, POST, PUT, etc.).
If we would like to limit the filter execution to only a certain method, we can use the ApplyTo switch.
[ServiceUsageStatsRequestFilter(ApplyTo = ApplyTo.Get | ApplyTo.Put, ServiceName = "ProductService", Priority = 1)] [ServiceUsageStatsResponseFilter(ServiceName = "ProductService")] public class ProductService : ServiceStack.ServiceInterface.Service { … } |
Another way of enabling a filter is by attaching it directly to the Request and Response DTO.
[ServiceUsageStatsRequestFilter(ServiceName = "ProductService")] public class GetProduct { public int Id { get; set; } } [ServiceUsageStatsResponseFilter(ServiceName = "ProductService")] public class ProductResponse { … } |
In order to give some meaning to the collected data, we can expose a service that will query the Cache where the data is stored. The service is simple. The following implementation is perhaps not elegant, but its purpose is to simply demonstrate the feature.
[Route("/Cache/{ServiceName}", "GET")] public class GetCacheContent { public string ServiceName { get; set; } } public class StatsItem { public string ServiceName { get; set; } public long Value { get; set; } } public class StatsCounterService: ServiceStack.ServiceInterface.Service { public ICacheClient Cache { get; set; }
public object Get(GetCacheContent request) { var data = Cache.GetAll<long>(new[] { request.ServiceName + "_GET", request.ServiceName + "_SUCCESSFUL", });
List<StatsItem> items = new List<StatsItem>(); data.Keys.ToList().ForEach(x => items.Add(new StatsItem() { ServiceName = x, Value = data[x] })); return items; } } |
When calling the service GET http://localhost:50712/Cache/ProductService.json, the following response will be returned.
[ { "ServiceName":"ProductService_GET", "Value": 1 }, ] |
As filters, plug-ins are used to extend ServiceStack. Unlike filters, they are meant to live within the request and response pipeline. Plug-ins can enhance the functionality of ServiceStack, for example, by enabling new content type handlers, adding new services, and automatically registering certain filters to be executed for each request.
The following is a list of predefined plug-ins already available in ServiceStack that can be enabled or disabled:
In order to create a plug-in, ServiceStack exposes an IPlugin interface that a plug-in has to implement. The IPlugin interface is defined as follows.
public interface IPlugin { void Register(IAppHost appHost); } |
As an example, let’s implement a simple plug-in that enhances ServiceStack by offering an interface to query the system at run time and return a list of currently loaded plug-ins.
The first step is to create the plug-in itself as follows.
public class RegisteredPluginsFeature : IPlugin { public void Register(IAppHost appHost) { appHost.Routes.Add(typeof(RegisteredPluginsDTO), "/ListPlugins", "GET"); appHost.RegisterService(typeof(RegisteredPluginsService), "/ListPlugins"); } } |
We can see that the plug-in mainly does the following:
The following code example is the implementation of the service.
public class RegisteredPluginsDTO { //Used only as a route to the service. } public class RegisteredPlugin { public string Name { get; set; } } public class RegisteredPluginsService : ServiceStack.ServiceInterface.Service { public object Get(RegisteredPluginsDTO request) { var plugins = base.GetAppHost().Plugins.ToList(); var list = plugins.Select(x => new RegisteredPlugin() { Name = x.GetType().Name }); return list.ToList(); } } |
As you can see, the implementation is simple. In the GET method, we iterate through the list of the currently registered plug-ins and return it as List<RegisteredPlugin>.
In order to be available to the system, the plug-in has to be registered. The registration is done in the application host (as we have seen in previous chapters) by using the Plugins collection.
public override void Configure(Container container) { Plugins.Add(new RegisteredPluginsFeature()); } |
When running the application and pointing to the predefined plug-in URI /ListPlugins (in our case, http://localhost:50712/ListPlugins?format=xml), we can see the list of plug-ins registered in the system.
<ArrayOfRegisteredPlugin xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ServiceStack.Succinctly.Host.Plugins"> <RegisteredPlugin> <Name>HtmlFormat</Name> </RegisteredPlugin> <RegisteredPlugin> <Name>CsvFormat</Name> </RegisteredPlugin> <RegisteredPlugin> <Name>MarkdownFormat</Name> </RegisteredPlugin> <RegisteredPlugin> <Name>PredefinedRoutesFeature</Name> </RegisteredPlugin> <RegisteredPlugin> <Name>MetadataFeature</Name> </RegisteredPlugin> <RegisteredPlugin> <Name>RequestInfoFeature</Name> </RegisteredPlugin> <RegisteredPlugin> <Name>RegisteredPluginsFeature</Name> </RegisteredPlugin> </ArrayOfRegisteredPlugin> |
As you can see, the new URI can be queried as any other service we have seen in the previous examples, and it can return a result in XML, JSON, or any other format supported by the framework.