ServiceStack 4 Cookbook
上QQ阅读APP看书,第一时间看更新

Creating a basic create/read/update/delete service

When you're building a REST service, it's important to make good use of the main tool available to you, HTTP. There are multiple books on the subject of what makes a good RESTful service, but here are some of the headlines:

  • URL routes represent resources; they should be nouns, not verbs
  • Services should act predictably to common verbs, for example, GET, POST, PUT, DELETE
  • Use appropriate HTTP status codes for responses

In this recipe, we will build a simple service that will Create, Read, Update, and Delete (CRUD) entries from a database. For a deeper dive into some of the other concepts used in this example, please see the relevant recipes:

  • The Sharing and accessing configuration and common functionality using Funq IoC recipe in Chapter 1, Configuration and Routing
  • The Using and accessing OrmLite recipe in Chapter 4, Object Relational Mapping (OrmLite)
  • The Creating static validation rules using fluent syntax recipe in Chapter 6, Filters and Validators
  • The Getting started with authentication, sessions, registration, and user repositories recipe in Chapter 7, Security and Authentication

Getting ready

For this recipe, we will need to have ServiceStack already set up within a Visual Studio project. If you have ServiceStackVS installed, simply use one of the C# templates. Alternatively, see the section Creating a ServiceStack solution with VisualStudio and NuGet in Appendix A, Getting Started to help you to set up a ServiceStack solution.

When structuring your service application, there are a few common parts you'll find regardless of the application. Structuring your application correctly can make a big difference when the time comes to make changes. These parts are:

  • Database model
  • Service model
  • Service interface
  • Application host

Database models for use with our Object-relational mapping (ORM) will reflect what our database looks like. It's common for these classes to be in the same project as our service model.

Service models are the classes that represent our request and response objects of our service interface; it's encouraged that these exist in a project of relatively few dependencies as they are the most likely to be shared along with the database model classes.

The service interface is our RESTful contract that will usually contain logic that orchestrates our other abstractions, such as database access, business rules, and other isolated systems. It's the glue of the other moving parts of our system.

The application host specifies all the concrete implementations of the abstractions used by our service interfaces. This is usually done by an inversion of control container (IoC), which injects our services with any registered components. More information on the use of ServiceStack's default IoC container, Funq, can be found in the Sharing and accessing configuration and common functionality using Funq IoC recipe in Chapter 1, Configuration and Routing.

How to do it...

First, we want to structure our database model to match what we are storing in our SQL database. To interact with the database, we'll be using ServiceStack's OrmLite library. OrmLite is a lightweight and fast object-relational mapping library that maps your database tables to simple Plain Old CLR Objects (POCOs). In the example, our Place table only has three columns and is represented by the following class.

Create classes to represent our persisted model. These will be used by OrmLite for data access:

public class Place
{
    [Index]
    [PrimaryKey]
    [AutoIncrement]
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

This POCO maps to a table in our database, its properties map to specific columns, and the OrmLite specific attributes indicate to the underlying database various behaviors, such as [AutoIncrement] to automatically increase the Id value when a new value is inserted.

Create separate classes to represent our request-and-response objects. These objects will have attributes for the URL routes, which control how ServiceStack listens to incoming requests. They should also be created in a separate ServiceModel project to help us reuse these classes if needed in the future:

[Route("/places/{Id}", Verbs = "GET")]
public class PlaceToVisit : IReturn<PlaceToVisitResponse>
{
  public long Id { get; set; }
}

[Route("/places",Verbs = "GET")]
public class AllPlacesToVisit : IReturn<AllPlacesToVisitResponse>
{

}

[Route("/places", Verbs = "POST")]
public class CreatePlaceToVisit : IReturn<CreatePlaceToVisitResponse>
{
  public string Name { get; set; }
  public string Description { get; set; }
}

[Route("/places/{Id}", Verbs = "PUT")]
public class UpdatePlaceToVisit : IReturn<UpdatePlaceToVisitResponse>
{
  public long Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
}

[Route("/places/{Id}", Verbs = "DELETE")]
public class DeletePlaceToVisit
{
  public long Id { get; set; }
}

public class PlaceToVisitResponse
{
  public Place Place { get; set; }
}

public class AllPlacesToVisitResponse
{
  public List<Place> Places { get; set; } 
}

public class CreatePlaceToVisitResponse
{
  public Place Place { get; set; }
}

public class UpdatePlaceToVisitResponse
{
  public Place Place { get; set; }
}

In the previous code sample, we explicitly use the IReturn<T> interface on our request objects. This actually isn't strictly needed if the naming conventions of your request-and-response objects follows the pattern of MyDto and MyDtoResponse. ServiceStack will infer from the use of a class in a service method, such as Get(MyDto request), that a class named MyDtoResponse is the corresponding response DTO.

Create PlaceService to expose our Place resource using appropriate HTTP verbs. These services should be created in a separate ServiceInterface project, as this helps with testing our services and enables reuse when different hosting configurations are required:

public class PlaceService : Service, 
    IGet<PlaceToVisit>,
    IGet<AllPlacesToVisit>,
    IPost<CreatePlaceToVisit>,
    IPut<UpdatePlaceToVisit>,
    IDelete<DeletePlaceToVisit>
{
    public object Get(PlaceToVisit request)
    {
        if (!Db.Exists<Place>(x => x.Id == request.Id))
        {
            throw HttpError.NotFound("Place not found");
        }
        return new PlaceToVisitResponse
        {
            Place = Db.SingleById<Place>(request.Id)
        };
    }

    public object Get(AllPlacesToVisit request)
    {
        return new AllPlacesToVisitResponse
        {
            Places = Db.Select<Place>().ToList()
        };
    }

    public object Post(CreatePlaceToVisit request)
    {
        var place = request.ConvertTo<Place>();
        Db.Insert(place);
        return new PlaceToVisitResponse
        {
            Place = place
        };
    }

    public object Put(UpdatePlaceToVisit request)
    {
        if (!Db.Exists<Place>(x => x.Id == request.Id))
        {
            throw HttpError.NotFound("Place not found");
        }

        var place = request.ConvertTo<Place>();
        Db.Update<Place>(place);
        return new PlaceToVisitResponse
        {
            Place = place
        };
    }

    public object Delete(DeletePlaceToVisit request)
    {
        if (!Db.Exists<Place>(x => x.Id == request.Id))
        {
            throw HttpError.NotFound("Place not found");
        }
        Db.DeleteById<Place>(request.Id);
        base.Response.StatusCode = (int)HttpStatusCode.NoContent;
        return null;
    }
}

Previously, in the PlacesService class, we leveraged some defaults from the ServiceStack Service base class. The Db property used to access our database is IDbConnectionFactory injected by our IoC container, Funq. The concrete implementation of IDbConnectionFactory is registered in the application host, as follows:

var dbFactory = new OrmLiteConnectionFactory(
    "~/App_Data/db.sqlite".MapHostAbsolutePath(), 
    SqliteDialect.Provider);
container.Register<IDbConnectionFactory>(dbFactory);

Using the Db property resolves and opens a database connection automatically. If you need more control over the database connection, you can simply declare a public property within the Service class with both public get and set. ServiceStack will inject the registered IDbConnectionFactory into this property before the request is processed.

If we try out our PlaceService class, we can see that we have a basic CRUD service that is ready to be expanded on with business-specific rules, authentication, caching, and so on.

The example with this recipe is a simple HTML page to try out our CRUD PlacesToVisit service, as follows:

How to do it...

How it works...

In this recipe, we went through an example of a simple CRUD service to show how this is done using ServiceStack and how we might structure the code to help maintain the separation of concerns for easier maintenance as the project changes in the future.

In the first step, we created our classes for use with OrmLite; these are kept separate from the classes created for our requests and response objects created in the second step. It is good practice to keep the request objects separate from our database model even though it is common that these structures look very similar. This separation is driven by the want to make our code easier to read and understand what objects are responsible for different aspects of your system.

One of the responsibilities of the request objects is to control the service routes which, for example, should have nothing to do with indexes on your database. Even though the property type and name values are the same and represent the same model, they have two distinct responsibilities. If your service interface has to change to meet a business or usability requirement, for example, there is a good chance you don't want those changes directly effecting your database model.

After we have our routes, requests, and response objects set up the way we want them, we can move on to writing our actual services.

The service itself, you'll notice, implements the IGet<T>, IPost<T>, IPut<T>, and IDelete<T> interfaces. These are not required as ServiceStack will bind requests to the appropriate methods by the naming convention and the DTO argument type. These interfaces, however, can help you write the service if you like to be more explicit about your writing your services.

We take advantage of a few different parts of ServiceStack to make the code within our service methods concise:

How it works...

The previous figure shows the different parts of the recipe project in relation to the database and clients and how it is structured. This pattern can be followed to create what are essentially structured monolithic applications that can be easily split up and hosted in different combinations. ServiceStack encourages this structure to not only try and strike a balance and create the flexibility of creating standalone micro services if required, but also to allow you to composite them in a single application by reusing the ServiceModel and ServiceInterface projects. This allows the developer to choose how they want to composite these different parts in a single application host or to break them up into separate application hosts.

In this recipe, we wrote a service with examples of the most basic parts that most of your data-collection-centric services will have. In my experience, the layering and separation of concerns that ServiceStack encourages make enhancements and future development a much more fiction-free task. So, although there is a lot of structure for relatively little code, this structure is important. Trying to maintain multiple thousands of lines of logic for a request in one method leads to many problems and code that very few people will want to pick up and maintain. The functionality ServiceStack provides is vast, but the clean development practices it encourages are what makes this framework so productive to work with.

There's more…

In the example, a simple use of validation and authentication is added to show how these might be used. Just like our IDbConnectionFactory, we need to register our authentication and validation-related objects with our IoC container.

Authentication requires a few additional steps; further description of these can be found in the Getting started with authentication, sessions, registration, and user repositories recipe in Chapter 7, Security and Authentication:

var userRep = new OrmLiteAuthRepository(container.Resolve<IDbConnectionFactory>());
container.Register<IUserAuthRepository>(userRep);
Plugins.Add(
    new AuthFeature(() => 
    new AuthUserSession(), 
    new IAuthProvider[] {
    new CredentialsAuthProvider(), 
}));

We also need to initialize the user authentication schema in our database. This can be done using the InitSchema method on the IUserAuthRepository object or the DropAndReCreateTables method. The DropAndReCreateTables method is used in this recipe to make it easy to run and test out; this should not be used in a production application, else you will lose all your information when your application starts.

For validation, we need to ensure that the validation plugin has been added to our host; this is done in our AppHost.Configure method, as follows:

Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(CreatePlaceValidator).Assembly);

Once this feature is added, we can write validation classes for our requests. ServiceStack uses Fluent Validation; we can associate our validators and request objects using the AbstractValidator<T> class. Rules are declared in the constructor of the validator. Once registered, these rules will run automatically for every request of the associated DTO:

public class CreatePlaceValidator : AbstractValidator<CreatePlaceToVisit>
{
    public CreatePlaceValidator()
    {
        RuleFor(r => r.Name).NotEmpty();
        RuleFor(r => r.Name.Length).GreaterThan(2);
    }
}

public class UpdatePlaceValidator : AbstractValidator<UpdatePlaceToVisit>
{
    public UpdatePlaceValidator()
    {
        RuleFor(r => r.Id).NotEmpty();
        RuleFor(r => r.Name).NotEmpty();
    }
}

public class DeletePlaceValidator : AbstractValidator<DeletePlaceToVisit>
{
    public DeletePlaceValidator()
    {
        RuleFor(r => r.Id).NotEmpty();
    }
}

See also

In this example, we also used a CredentialsAuthProvider method for simple username and password authentication. If your service or application needs authentication, this topic is important enough to deserve consideration for what is right for your application. See Chapter 7, Security and Authentication, for more in-depth details on how ServiceStack works with its various options for security and authentication.

Other chapters that also have more in-depth details on topics this recipe has touched on are listed below:

  • The Routing using data transfer object attributes recipe in Chapter 1, Configuration and Routing
  • The Structuring your project to avoid dependency issues recipe in Chapter 1, Configuration and Routing
  • The Sharing and accessing configuration and common functionality using Funq IoC recipe in Chapter 1, Configuration and Routing
  • The Using and accessing OrmLite recipe in Chapter 4, Object Relational Mapping (OrmLite)
  • The Creating static validation rules using fluent syntax recipe in Chapter 6, Filters and Validators
  • The Getting started with authentication, sessions, registration, and user repositories recipe in Chapter 7, Security and Authentication