left-icon

ServiceStack Succinctly®
by Zoran Maksimovic

Previous
Chapter

of
A
A
A

CHAPTER 11

Extending ServiceStack

Extending ServiceStack


ServiceStack offers a good level of extensibility. There are two ways of extending ServiceStack: through filters and through plug-ins.

Filters

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.

Request and Response filter ordering

  1. Request and Response filter ordering

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.

Request Filters

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.

public class ServiceUsageStatsRequestFilter RequestFilterAttribute

{

    public string ServiceName { getset; }

    //This property will be resolved by the IoC container.

    public ICacheClient Cache { getset; }

        

    public override void Execute(IHttpRequest req, IHttpResponse res, 

                                                           object requestDto)

    {            

        string getKey = ServiceName + "_GET";

 

        if (req.HttpMethod == "GET")

        {

            var item = Cache.Get<long>(getKey);

            Cache.Set(getKey, (item + 1));                

        }            

    }

}

Response Filters

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 ServiceUsageStatsResponseFilterResponseFilterAttribute

{

    

    public string ServiceName { getset; }

    //This property will be resolved by the IoC container.

    public ICacheClient Cache { getset; }

   

    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);

        }

    }

}

Applying Filters

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 { getset; }

}

[ServiceUsageStatsResponseFilter(ServiceName = "ProductService")]

public class ProductResponse { }

Service for Displaying Statistics

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 { getset; }

}

public class StatsItem

{

    public string ServiceName { getset; }

    public long Value { getset; }   

}

public class StatsCounterService: ServiceStack.ServiceInterface.Service

{

    public ICacheClient Cache { getset; }

 

    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": },
   {   "ServiceName":"ProductService_SUCCESSFUL", "Value": 1 }

]

Plug-ins

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:

  • Metadata feature: Provides the autogenerated pages for service inspection.
  • CSV format: Provides automatic handling of the CSV content type.
  • HTML format: Provides automatic handling of the HTML content type.
  • Swagger integration: Swagger is a specification and a complete framework implementation for describing, producing, consuming, and visualizing RESTful web services. This will be covered in the Swagger Integration chapter.
  • Validation: Enables validation.
  • Authentication: Enables authentication.
  • Request logger: Enables inspection of the latest requests and responses.

Plug-in Creation

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:

  • Implements the Register method of the IPlugin interface.
  • Adds a new Route. As we would normally do for any other service, the new route is created to support the routing to the new service. This is done by mapping the RegisteredPluginsDTO class to the /ListPlugins URI for the GET method.
  • Registers a new service. The service will be available to the given /ListPlugins URI. The new service is implemented as RegisteredPluginsService.

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>.

Plug-in Registration

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());

}

Plug-in Usage

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.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.