Getting Started - Services

Different ways for getting started with ServiceStack: Project templates, Walkthroughs, Docs and Videos, choose what's best for you

API Design

The primary difference between developing RPC vs ServiceStack's Message-based Services is that the Services entire contract is defined by its typed messages, specifically the Request DTO which defines both the System inputs and identifies the System output. Typically both are POCO DTOs however the response can be any serializable object.

As only the Any() wildcard method is defined, it will get executed whenever the GetContacts Service is invoked via any HTTP Verb, gRPC, MQ or SOAP Request.

The Request DTO is also all that’s required to invoke it via any Typed Generic Service Client in any supported language, e.g: client.Get(new GetContacts());

public class Contact 
{
   public int Id { get; set; }
   public string Name { get; set; }
}

[Route("/contacts")]
public class GetContacts : IReturn<List<Contact>> { }

public class ContactsService : Service
{
   public object Any(GetContacts request) => 
       Db.Select<Contact>();
}
Client.cs
var client = new JsonServiceClient(baseUrl);

List<Contact> response = client.Get(new GetContacts());

Routing

Without any configuration required, ServiceStack already includes pre-defined routes for all services in the format:

/api/[RequestDto]
For example, the pre-defined URL to call a JSON Hello Service is /api/Hello

Friendly custom routes are defined using the RouteAttribute applied to the Request DTO. Variable place holders bind by property name and can be used in the path. For example, the path /hello/world will bind "world" to the Name property of the Request DTO.

The QueryString, FormData and HTTP Request Body isn’t apart of the Route (i.e. only the /path/info is) but they can all be used in addition to every web service call to further populate the Request DTO. Routing can be limited to specific verbs, have flexible matching rules as well as matching specific service methods to be Content-Type specific.

[Route("/hello")]
[Route("/hello/{Name}")]
public class Hello : IGet, IReturn<HelloResponse>
{
   public string Name { get; set; }
}

public class HelloResponse
{
   public string Result { get; set; }
}

public class MyServices : Service
{
   public object Any(Hello request) =>
       new HelloResponse {
           Result = $"Hello, {request.Name}!"
       };
}

HTTP Verbs

ServiceStack Services lets you handle any HTTP Verb in the same way by using a matching method name in your Service class. Routing by default will match All HTTP verbs, but an additional parameter can be specified to limit routes to individual verbs. This lets you route the same path to different services.

Multiple verbs on a Route can be used with a single Any service method to handle situations where you want multiple, but specific, verbs to map to a single service method like handling both the creation and update of a resource.

public class ContactServices
{
   public object Get(GetContacts request)
   {
       var contacts = request.Id == null ? 
           Db.Select<Contact>() :
           Db.Select<Contact>(x => x.Id == request.Id.ToInt());
       return contacts;
   }
   
   public object Any(UpdateContact request)
   {
       var contact = Db.SingleById<Contact>(request.Id);
       contact.PopulateWith(request);
       Db.Update(contact);
       return contact;
   }
   
   public void Delete(DeleteContact request) =>
       Db.Delete<Contact>(x => x.Id == request.Id);
}
Contacts.cs
[Route("/contacts")]
[Route("/contacts/{Id}")]
public class GetContacts : IGet, IReturn<List<Contact>>
{ 
   public int? Id { get; set; }
}

[Route("/contacts/{Id}", "PATCH PUT")]
public class UpdateContact : IPut, IReturn<Contact>
{
   public int Id { get; set; }
}

[Route("/contacts/{Id}")]
public class DeleteContact : IDelete, IReturnVoid
{
   public int Id { get; set; }
}

Filters

ServiceStack has several ways to have custom filter methods on requests and responses. Global filters will fire every request/response allowing you to apply your own generic custom behavior across your application.

Typed request/response filters give you a strongly typed API for specific message types. These can be registered for MQ and HTTP independently. All filters can access the underlying Request, Response and related typed DTO. If you need access to resources from your IoC, you can also register Autowired Typed Filters which can have IoC dependencies injected like services.

Filter attributes are another powerful way to have generic filter that can be then applied to service methods as .NET attributes as needed. An example of this is the ServiceStack [Authenticate] and [RequiredPermission] attributes are filter attributes.

public class AppHost : AppHostBase
{
   public AppHost() : base("Web",typeof(MyServices).Assembly){}

   public override void Configure(Container container)
   {
       GlobalRequestFilters.Add((req, res, requestDto) => {
           var sessionId = req.GetCookieValue("user-session");
           if (sessionId == null)
               res.ReturnAuthRequired();
       });

       RegisterTypedRequestFilter<Resource>((req, res, dto) =>
       {
           var route = req.GetRoute();
           if (route?.Path == "/tenant/{Name}/resource")
               dto.SubResourceName = "CustomResource";
       });
   }
}

public class MyServices : Service
{
   [NoNumbersInPath]
   public object Any(Hello request)
   {
       return new HelloResponse {
           Result = $"Hello, {request.Name}!"
       };
   }
}

public class NoNumbersInPathAttribute : RequestFilterAttribute
{
   public override void Execute(IRequest req, IResponse res, 
       object requestDto)
   {
       if (Regex.IsMatch(req.PathInfo, "[0-9]"))
           throw HttpError.BadRequest("No numbers");
   }
}

Validation

As validation and error handling is an essential part of developing services, ServiceStack provides a rich array of error handling options that work intuitively out-of-the-box.

The ValidationFeature plugin automatically scans and auto-wires all validators in the AppHost.ServiceAssemblies that’s injected in the AppHost constructor. Like services registered in the IoC container, validators are also auto-wired, so if there’s a public property which can be resolved by the IoC container, the IoC container will inject it.

The validation rules for this request dto are made with FluentValidation. ServiceStack makes heavy use of rule sets to provide different validation rules for each HTTP method (GET, POST, PUT…). This enables developers to write comprehensive validation rules to suit complex business requirements.

public class AppHost() : AppHostBase("MyApp"), IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services => {
            services.AddSingleton<IAddressValidator>(c => new AddressValidator());
        });
}

// ServiceInterface
public class MyServices : Service
{
   public object Post(CreateUser request)
   {
       var id = Db.Insert(request.ConvertTo<User>());
       var user = Db.SingleById<User>(id);
       return user;
   }
}

public interface IAddressValidator
{
   bool ValidAddress(string address);
}

public class AddressValidator : IAddressValidator
{
   public bool ValidAddress(string address)
   {
       return address != null
          && address.Length >= 20
          && address.Length <= 250;
   }
}

public class UserValidator(IAddressValidator addressValidator) 
    : AbstractValidator<CreateUser>
{
   public UserValidator()
   {
       //Validation rules for all requests
       RuleFor(r => r.Name).NotEmpty();
       RuleFor(r => r.Age).GreaterThan(0);
       RuleFor(x => x.Address).Must(x => 
           addressValidator.ValidAddress(x));
   }
}

// ServiceModel
[Route("/users", "POST")]
public class CreateUser : IReturn<User>
{
   public string Name { get; set; }
   public string Company { get; set; }
   public int Age { get; set; }
   public int Count { get; set; }
   public string Address { get; set; }
}

public class User
{
   [AutoIncrement]
   public int Id { get; set; }
   public string Name { get; set; }
   public string Company { get; set; }
   public int Age { get; set; }
   public int Count { get; set; }
   public string Address { get; set; }
}

Formats

ServiceStack bundles support for 5 default formats. Those are HTML, JSON, XML, CSV and JSV. Additionally ServiceStack has support for SOAP, Message Pack, Protocol Buffers and Wire. Since ServiceStack is message based, the serialization of requests and responses are completely interchangeable.

ServiceStack Services supports a number of Content Negotiation options where you can define which format should be returned by adding a .{format} extension to your /route.{format}. For example, /hello/world.json and /hello/world?format=json will both respond to in the JSON format.

Adding these additional formatters like MessagePack can be done by including the relevant dependency from NuGet and adding support using Plugins.Add. For Example, adding the NuGet package ServiceStack.MsgPack and then in the AppHost, Plugins.Add(new MsgPackFormat());.

public class AppHost() : AppHostBase("MyApp"), IHostingStartup
{
   public void Configure(IWebHostBuilder builder) => builder
       .ConfigureServices(services => {
           services.AddSingleton<IAddressValidator>(c => new AddressValidator());
           // Add additional format support by using Plugins
           services.AddPlugin(new MsgPackFormat());
       });
}

public class MyServices : Service
{
   // HTML, JSON, CSV, XML and JSV enabled by default
   public object Any(Hello request) =>
       new HelloResponse {
           Result = $"Hello, {request.Name}"
       };
}