left-icon

ServiceStack Succinctly®
by Zoran Maksimovic

Previous
Chapter

of
A
A
A

CHAPTER 5

Service Implementation


In this chapter, we will describe in detail how to build each of the services mentioned in Chapter 3. We will also describe how to implement all the relative components that enable full support for the functionalities we want to implement.

Our solution will contain three services and all three will implement all of the HTTP methods. The following table contains the list of verbs and the respective routes that will be implemented.

  1. Services with respective operations

Service

Description

OrderService

Contains methods that insert, delete, create, and update orders.

  • GET    /orders
  • GET    /orders/{id}
  • POST      /orders
  • PUT    /orders/{id}
  • DELETE  /orders/{id}

ProductService

Contains methods that insert, delete, create, and update products.

  • GET    /products
  • GET     /products/{id}
  • POST      /products
  • PUT    /products/{id}
  • DELETE  /products/{id}

OrderItemService

Contains methods that manipulate OrderItems associated with an order.

  • GET    /orders/{id}/items
  • GET    /orders/{id}/items/{id}

For all three services, we will implement the following components:

  • Service Model (Request and Response DTO) object definitions.
  • Route specification.
  • Mapper(s) implementation.
  • Validator implementation.
  • Service implementation.
  • Configuration (application host) wiring everything together.

Additional Information

Link and Status DTOs

All of the Response DTO objects will use the Status and Link classes. I’ve included the code here as a reference.

public class Status

{

    public int Id { getset; }

    public string Name { getset; }

}

public class Link

{

    public string Rel { getset; }

    public string Href { getset; }

    public string Title { getset; }

    public string Type { getset; }

}

The Link class will contain the hypermedia information related to the resource.

Test Project

To test the various services, create a new project called ServiceStack.Succinctly.Host.IntegrationTest that references the following assemblies:

  • From the NuGet package: ServiceStack.Common, which will install the ServiceStack.Common, ServiceStack.Text, and ServiceStack.Interfaces DLLs.
  • Microsoft.VisualStudio.QualityTools.UnitTestFramework, which is the standard Microsoft .NET assembly that contains testing attributes.
  • ServiceStack.Succinctly.ServiceInterface, which is the project that contains our DTO implementation.

Removing Namespaces

To remove superfluous namespaces from XML returned objects, add the following code to the Assembly.cs in the ServiceStack.Succinctly.ServiceInterface project.

[assemblyContractNamespace("", ClrNamespace="ServiceStack.Succinctly.ServiceInterface.OrderModel")]

[assemblyContractNamespace("", ClrNamespace="ServiceStack.Succinctly.ServiceInterface.OrderItemModel")]

[assemblyContractNamespace("", ClrNamespace="ServiceStack.Succinctly.ServiceInterface.ProductModel")]

[assemblyContractNamespace("", ClrNamespace="ServiceStack.Succinctly.ServiceInterface")]

Product Service

In our solution, the Product class can be seen as a reference data class as it is a property of the OrderItem class. Since one Product can be assigned to several OrderItems, there is a need to have a separate service that specifically manages a Product itself. ProductService is a classic create, read, update, and delete (CRUD) service.

Service Model

As a particularity of the service, I’ve chosen to create two separate DTOs for managing inserts and updates. I’ve done so to show that there is such a possibility and that we may fine-tune the actual requests. Indeed, the CreateProduct class specifically doesn’t have the Id because the client shouldn’t know about the Id when creating a product. However, you may choose not to implement it in such a way, but to have a more consistent object instead (as we are going to see in the OrderService implementation).

The following five classes will be used as Request DTOs of the service. The GetProducts class intentionally does not have properties; it is mainly used for routing a request to the proper service method as we will see later.

public class GetProducts { }

public class GetProduct 

{

    public int Id { getset; }

}

 

public class CreateProduct

{

    public string Name   { getset; }

    public Status Status { getset; }

}

public class UpdateProduct

{

    public int    Id     { getset; }

    public string Name   { getset; }

    public Status Status { getset; }

}

public class DeleteProduct

{

    public int Id { getset; }

}

Almost all of the Service methods will return the ProductResponse object. ProductResponse is a mirror of the Product and, as we will see, it can contain more or less information. In our case, it contains a list of Links and no information about the Product.Version, which is only used for optimistic concurrency control.

ProductsResponse instead will hold a list of ProductResponse. This is helpful as we can reuse this object and enrich it with further attributes that can be useful for paging, navigation, etc.

public class ProductResponse

{

    public int        Id     { getset; }

    public string     Name   { getset; }

    public Status     Status { getset; }

    public List<Link> Links  { getset; }

}

public class ProductsResponse

{

    public List<ProductResponse> Products { getset; }

}

Route Specification

In the application host (Global.asax.cs), we need to register the various routes related to the Product service. As we have seen in the previous chapters, this is done either in the application host’s constructor or in the Configure method. I’ve chosen to use the constructor because I want to use the Configure method only for the IoC-related items.

using ServiceStack.Succinctly.Host.Extensions;

public ServiceAppHost():                    base("Order Management"typeof (ServiceAppHost).Assembly)

{

    Routes

      .Add<GetProducts>  ("/products",      "GET",    "Returns Products")

      .Add<GetProduct>   ("/products/{Id}""GET",    "Returns a Product")

      .Add<CreateProduct>("/products",      "POST",   "Creates a Product")

      .Add<UpdateProduct>("/products/{Id}""PUT",    "Updates a Product")

      .Add<DeleteProduct>("/products/{Id}""DELETE""Deletes a Product");

}

The ServiceStack Routes.Add() method currently doesn’t expose the method signature that we have just seen. To achieve this, I’ve created an extension method. The Routes property implements the IServiceRoute interface and, once we know this, it’s very easy to extend.

public static class RoutesExtensions

{

    public static IServiceRoutes Add<T> (this IServiceRoutes routes, 

                             string restPath, string verbs, string summary)

    {

        return routes.Add(typeof (T), restPath, verbs, summary, "");

    }

 

    public static IServiceRoutes Add<T>(this IServiceRoutes routes, 

               string restPath, string verbs, string summary, string notes)

    {

        return routes.Add(typeof(T), restPath, verbs, summary, notes);

    }

}

Product Mapper Implementation

We need to map the data back and forth from the domain object model to the service object model (DTOs). The best way to do so is to create a specific class that enables the application to transform the Product domain object to ProductResponse and CreateProduct/UpdateProduct to the Product. Because this can be quite a heavy workload in the case of big classes, I advise you to use some specific libraries such as AutoMapper[21] as this would decrease the amount of necessary code. Explaining how AutoMapper works is outside the scope of this book but, as we will see, it’s pretty intuitive and easy to use.

Adding the AutoMapper library to the project is as easy as running the following NuGet command:

PM> Install-Package AutoMapper

The ProductMapper implements the IProductMapper interface, which will make it easier to be injected to the Service. Let’s create the following file in the ServiceStack.Succinctly.Host project under the ServiceStack.Succinctly.Host.Mappers namespace.

The following code example shows the definition of the IProductMapper interface.

using OrderManagement.Core.Domain;

using ServiceStack.Succinctly.ServiceInterface.ProductModel;

 

namespace ServiceStack.Succinctly.Host.Mappers

{

    public interface IProductMapper

    {

       Product ToProduct(CreateProduct request);

       Product ToProduct(UpdateProduct request);

       ProductResponse ToProductResponse(Product product);

       List<ProductResponse> ToProductResponseList(List<Product> products);

    }

}

The following code is the implementation of the ProductMapper.

using OrderManagement.Core.Domain;

using SrvObjType = ServiceStack.Succinctly.ServiceInterface;

using SrvObj = ServiceStack.Succinctly.ServiceInterface.ProductModel;

  

namespace ServiceStack.Succinctly.Host.Mappers

{

    public class ProductMapper : IProductMapper

    {

        static ProductMapper()

        {

            Mapper.CreateMap<SrvObjType.StatusStatus>();

            Mapper.CreateMap<Status, SrvObjType.Status>();

            Mapper.CreateMap<SrvObj.CreateProductProduct>();

            Mapper.CreateMap<SrvObj.UpdateProductProduct>();

            Mapper.CreateMap<Product, SrvObj.ProductResponse>();

        }

 

        public Product ToProduct(SrvObj.CreateProduct request)

        {

            return Mapper.Map<Product>(request);

        }

 

        public Product ToProduct(SrvObj.UpdateProduct request)

        {

            return Mapper.Map<Product>(request);

        }

 

        public SrvObj.ProductResponse ToProductResponse(Product product)

        {

           var productResponse = Mapper.Map<SrvObj.ProductResponse>(product);

 

           productResponse.Links = new List<SrvObjType.Link>

                {

                    new SrvObjType.Link

                        {

                            Title = "self",

                            Rel = "self",

                            Href = "products/{0}".Fmt(product.Id),

                        }

                };

           return productResponse;

        }

 

        //Transforms a list of products into a list of ProductResponses.

        public List<SrvObj.ProductResponse> ToProductResponseList(

                                                      List<Product> products)

        {

            var productResponseList = new List<SrvObj.ProductResponse>();

            products.ForEach(x => 

                         productResponseList.Add(ToProductResponse(x)));

            return productResponseList;

        }

    }

}

Note: All of our services will have a Mapper class by design because we want to separate the Request and Response DTOs (service model) from the application domain model.

Validation Implementation

As we saw in the previous chapter, we can create some custom validation logic. We will create some in this case because we want to make our implementation a bit stronger. For our current example, we will just make sure that when the Product is created or updated, we check that the Name property is set and is not longer than 50 characters. We will create the following two classes in the ServiceStack.Succinctly.Host.Validation namespace, and will register those two validators in the application host.

public class CreateProductValidator : AbstractValidator<CreateProduct>

{

    public CreateProductValidator()

    {

        string nameNotSpecifiedMsg = "Name has not been specified.";

        string maxLenghtMsg = "Name cannot be longer than 50 characters.";

        RuleFor(r => r.Name)

            .NotEmpty().WithMessage(nameNotSpecifiedMsg)

            .NotNull().WithMessage(nameNotSpecifiedMsg)

            .Length(1, 50).WithMessage(maxLenghtMsg);

    }

}

 

public class UpdateProductValidator : AbstractValidator<UpdateProduct>

{

    public UpdateProductValidator()

    {

        string nameNotSpecifiedMsg = "Name has not been specified.";

        string maxLenghtMsg = "Name cannot be longer than 50 characters.";

        RuleFor(r => r.Name)

            .NotEmpty().WithMessage(nameNotSpecifiedMsg)

            .NotNull().WithMessage(nameNotSpecifiedMsg)

            .Length(1, 50).WithMessage(maxLenghtMsg);

    }

}

Application Host Configuration

The following code shows the full implementation of the application host.

public class ServiceAppHost : AppHostBase

{

  public ServiceAppHost()

        : base("Order Management"typeof(ServiceAppHost).Assembly)

   {

    Routes

    .Add<GetProducts>  ("/products""GET""Returns a collection of Products")

    .Add<GetProduct>   ("/products/{Id}""GET""Returns a single Product")

    .Add<CreateProduct>("/products""POST""Create a product")

    .Add<UpdateProduct>("/products/{Id}""PUT""Update a product")

    .Add<DeleteProduct>("/products/{Id}""DELETE""Deletes a product")

    .Add<DeleteProduct>("/products""DELETE""Deletes all products");

                               

     Plugins.Add(new ValidationFeature());

   }

    public override void Configure(Container container)

    {

        container.Register<IProductRepository>(new ProductRepository());

        container.Register<IProductMapper>(new ProductMapper());

                

        container.RegisterValidator(typeof(CreateProductValidator));

        container.RegisterValidator(typeof(UpdateProductValidator));

    }

}

Service Implementation

ProductService implements two Get methods, one Post, one Put, and one Delete. Every ServiceStack service has to inherit from the ServiceStack.ServiceInterface.Service class.

As shown in the following code example, ProductService has two properties: ProductMapper and ProductRepository, which will hold the instances that will be injected at run time by the IoC container.

public class ProductService : ServiceStack.ServiceInterface.Service

{       

    public IProductMapper ProductMapper { getset; }

    public IProductRepository ProductRepository { getset; }

    public ProductResponse Get(GetProduct request){…}

    public List<ProductResponse> Get(GetProducts request){}

    public ProductResponse Post(CreateProduct request){…}

    public ProductResponse Put(UpdateProduct request) {…}

    public HttpResult Delete(DeleteProduct request){…}

}

Get Products by Id

When calling GET /product/1, the following method will be called. As you can see, the implementation is quite simple:

  • Get the data from the repository.
  • If nothing has been found, then return the 404 Not Found status code (the Response.StatusCode attribute can be used to do so).
  • If something is returned by the repository, then by using the ProductMapper, we transform the Product (transform the domain model to the ProductResponse service model).

public ProductResponse Get(GetProduct request)

{

    var product = ProductRepository.GetById(request.Id);

    if (product == null)

    {

        Response.StatusCode = (int)HttpStatusCode.NotFound;

        return default(ProductResponse);

    }

    //Transform to ProductsResponse and return.

    return ProductMapper.ToProductResponse(product);

}

In order to call the GET service method in the previous example, we can use any web client. The following example shows a unit test (using Microsoft Test Framework) that uses the built-in ServiceStack JsonServiceClient to perform a GET on the /products/{Id} URI.

[TestMethod]

public void GetProductByProductId_ReturnsValidProduct()

{

    //ARRANGE --- 

    int PRODUCT_ID = 1;

    var client = new JsonServiceClient("http://localhost:50712/");

 

    //ACT  ------ 

    var product = client.Get<ProductResponse>("/products/" + PRODUCT_ID);

 

    //ASSERT ---- 

    Assert.IsTrue(product!=null);

    Assert.IsTrue(product.Id == PRODUCT_ID);

}

If we were to use a browser to navigate to http://localhost:50712/products/1.xml, we would get the following response.

<ProductResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

  <Id>1</Id>

  <Links>

    <Link>

      <Href>products/1</Href>

      <Rel>self</Rel>

      <Title>self</Title>

      <Type i:nil="true"/>

    </Link>

  </Links>

  <Name>Pizza</Name>

  <Status>

    <Id>1</Id>

    <Name>Active</Name>

  </Status>

</ProductResponse>

Returning All Products

In this case, the service accepts a GetProducts message which, on its own, doesn’t have any property. But, as we are going to see later, being the collection that is returned, we can build the paging, sorting, filtering, etc. For the time being though, we simply pass a request and the service method will:

  • Get the data from the repository.
  • Transform the data from the domain model to the service model (DTO).

//Returns all the Products.

public ProductsResponse Get(GetProducts request)

{

//Get data from the database.

List<Product> products = ProductRepository.GetAll();

//Transform to ProductsResponse and return.

return new ProductsResponse()

    {

        Products = ProductMapper.ToProductResponseList(products)

    };

}

To call the /products in order to get the list of available products, the client would look like the following.

[TestMethod]

public void GetAllProducts_ReturnsValidProductList()

{

    //ARRANGE --- 

    var client = new JsonServiceClient("http://localhost:50712/");

 

    //ACT  ------ 

    var products = client.Get<ProductsResponse>("/products");

 

    //ASSERT ---- 

    Assert.IsTrue(products != null);

    Assert.IsTrue(products.Products.Count > 0);

}

And, as shown in the web browser:

List of products

  1. List of products

Creating a New Product

When creating new Products, there are several things to note:

  • To create objects, the POST method is used.
  • Once the Product is created and successfully sent to the repository (database), we will return to the client the full Product as we did with the GET /products/1.
  • To note that the object has been created, we will add the Location in the header and inform the client of the object’s new location.
  • Status code 201 Created will be returned to the client.

//Returns all the orders.

public ProductResponse Post(CreateProduct request)

{

   //Transform the request to Domain.Product.

   var domainProduct = ProductMapper.ToProduct(request);

 

   //Storing data to database.

   var newProduct = ProductRepository.Add(domainProduct);

 

   //Transform to ProductResponse.

   var response = ProductMapper.ToProductResponse(newProduct);

 

   //Manipulate the header and StatusCode.

   Response.AddHeader("Location", Request.AbsoluteUri + "/" + newProduct.Id);

   Response.StatusCode = (int)HttpStatusCode.Created;

 

   return response;

}

To insert a new product using the ServiceStack client, we will test that the Response is returning 201 Created HTTP status and that the Location header has been returned with the new URI.

[TestMethod]

public void CreateNewProduct_ReturnsObjectAnd201CreatedStatus()

{

    //ARRANGE --- 

    WebHeaderCollection headers = null;

    HttpStatusCode statusCode = 0;

    const string PRODUCT_NAME = "Cappuccino";

    const string SITE = "http://localhost:50712";

    const string PRODUCTS = "/products";

    const string URI = SITE + PRODUCTS;

    var client = new JsonServiceClient(SITE)

        {

            //Grabbing the header once the call is ended.

            LocalHttpWebResponseFilter =

                httpRes =>

                {

                    headers = httpRes.Headers;

                    statusCode = httpRes.StatusCode;

                }

        };

    var newProduct = new CreateProduct

        {

            Name = PRODUCT_NAME,

            Status = new Status {Id = 1}

        };

 

    //ACT  ------ 

    var product = client.Post<ProductResponse>(PRODUCTS, newProduct);

 

    //ASSERT ---- 

    Assert.IsTrue(headers["Location"] == URI + "/" + product.Id);

    Assert.IsTrue(statusCode == HttpStatusCode.Created);

    Assert.IsTrue(product.Name == PRODUCT_NAME);

}

After the POST method has been called, the following is the response from the service, header, and body. As you can see, the 201 Created verb and the Location headers are specified and returned to the client.

HTTP/1.1 201 Created

Cache-Control: private

Content-Type: application/xml

Location: http://localhost:50712/products/10

Server: Microsoft-IIS/8.0

X-Powered-By: ServiceStack/3.956 Win32NT/.NET

Date: Tue, 13 Aug 2013 22:15:42 GMT

Content-Length: 277

<ProductResponse>

  <Id>10</Id>

  <Links>

    <Link>

      <Href>products/10</Href>

      <Rel>self</Rel>

      <Title>self</Title>

      <Type i:nil="true" />

    </Link>

  </Links>

  <Name>Cappuccino</Name>

  <Status>

    <Id>1</Id>

    <Name>Active</Name>

  </Status>

</ProductResponse>

Updating a Product

To update a product, our service will implement the PUT method which will accept the UpdateProduct message.

Before doing any work, we check if the resource we are updating is available in the repository. If it is not, we then force the 404 Not Found status code which indicates that the resource is not available. In a successful scenario, either the 200 (OK) or 204 (No Content) response codes should be sent to indicate successful completion of the request. We will return 200 (OK) and the full body of the message if the call is successful.

public ProductResponse Put(UpdateProduct request)

{

    var domainObject = ProductRepository.GetById(request.Id);

    if (domainObject == null)

    {

        Response.StatusCode = (int)HttpStatusCode.NotFound;

        return null;

    }

            

    //Transform to Domain.Product.

    var domainProduct = ProductMapper.ToProduct(request);

 

    //Store data to database.

    var updatedProduct = ProductRepository.Update(domainProduct);

 

    //Transform to ProductResponse and return.

    return ProductMapper.ToProductResponse(updatedProduct);

}

To test the product update, where only the status of the resource would be updated to Inactive:

[TestMethod]

public void UpdateProduct_ReturnsUpdatedObject()

{

    //ARRANGE --- 

    HttpStatusCode statusCode = 0;

    const string PRODUCT_NAME = "White Wine";

    const string SITE = "http://localhost:50712";

    const string PRODUCT_LINK = "/products/2";

 

    var client = new JsonServiceClient(SITE)

        {

            //Grabbing the header once the call is ended.

            LocalHttpWebResponseFilter =

                httpRes =>

                {

                    statusCode = httpRes.StatusCode;

                }

        };

 

    var updateProduct = new UpdateProduct

        {

            Name = PRODUCT_NAME,

            Status = new Status {Id = 2} // Id = 2 means inactive.

        };

 

    //ACT  ------ 

    var product = client.Put<ProductResponse>(PRODUCT_LINK, updateProduct);

 

    //ASSERT ---- 

    Assert.IsTrue(statusCode == HttpStatusCode.OK);

    Assert.IsTrue(product.Name == PRODUCT_NAME);

    Assert.IsTrue(product.Status.Id == 2);

}

The difference between the original and the new (updated) resource is only the Status.Id.

Original Resource

Server Response

<ProductResponse>

  <Id>2</Id>

  <Links>

    <Link>

      <Href>products/2</Href>

      <Rel>self</Rel>

      <Title>self</Title>

      <Type i:nil="true"/>

    </Link>

  </Links>

  <Name>White Wine</Name>

  <Status>

    <Id>1</Id>

    <Name>Active</Name>

  </Status>

</ProductResponse>

HTTP/1.1 200 OK

Cache-Control: private

Content-Type: application/xml

Server: Microsoft-IIS/8.0

Date: Tue, 13 Aug 2013 22:19:55 GMT

Content-Length: 278

 

<ProductResponse>

  <Id>2</Id>

  <Links>

    <Link>

      <Href>products/2</Href>

      <Rel>self</Rel>

      <Title>self</Title>

      <Type i:nil="true"/>

    </Link>

  </Links>

  <Name>White Wine</Name>

  <Status>

    <Id>2</Id>

    <Name>Inactive</Name>

  </Status>

</ProductResponse>

Deleting a Product

In case the resource is not found, we can return the 404 Not Found status code. But you might choose not to return an error and instead return 200 OK. In this example, I’ve chosen to inform the client about the nonexistence of the resource. When returning 201 NoContent, we won’t return any body.

//Deletes a product.

public HttpResult Delete(DeleteProduct request)

{

    var domainObject = ProductRepository.GetById(request.Id);

    if (domainObject == null)

    {

        Response.StatusCode = (int)HttpStatusCode.NotFound;

    }

    else

    {

        ProductRepository.Delete(request.Id);

        Response.StatusCode = (int)HttpStatusCode.NoContent;

    }

    return null;

}

The following code tests the Delete method implemented in the service.

[TestMethod]

public void DeleteProduct_ReturnsNoContent()

{

    //ARRANGE --- 

    HttpStatusCode statusCode = 0;

    const string SITE = "http://localhost:50712";

 

    var client = new JsonServiceClient(SITE)

        {

            //Grabbing the header once the call is ended.

            LocalHttpWebResponseFilter =

                httpRes =>

                {

                    statusCode = httpRes.StatusCode;

                }

        };

 

    //ACT  ------ 

    client.Delete<HttpResult>("/products/5");

 

    //ASSERT ---- 

    Assert.IsTrue(statusCode == HttpStatusCode.NoContent);

}

Order Service

OrderService will implement the following methods:

  • GET    /orders
  • GET    /orders/{id}
  • POST   /orders
  • PUT    /orders/{id}
  • DELETE /orders/{id}

As we did in the ProductService, we will divide the implementation the same way.

Service Model

OrderService’s service model is a bit more complex since we have a graph of objects that should mimic in some ways the domain object model. In this example, I’ve chosen to use the Order name for the DTO class when creating and updating an Order rather than the CreateOrder and UpdateOrder as I did in the previous chapter. This is mainly for brevity. Keep in mind that you should take care of the naming convention and follow it through all the services as much as possible (as it would be much easier for clients to understand your service’s object model).

The following classes will be used as Request DTOs of the service.

public class GetOrders { }

 

public class GetOrder

{

    public int Id { getset; }

}

public class DeleteOrder

{

    public int Id { getset; }

}

//Class used for Create and Update.

public class Order

{       

    public int             Id           { getset; } 

    public bool            IsTakeAway   { getset; }

    public DateTime        CreationDate { getset; }

    public Status          Status       { getset; }

    public List<OrderItem> Items        { getset; }

}

 

public class OrderItem

{

    public int     Id       { getset; }

    public Product Product  { getset; }

    public int     Quantity { getset; }

}

 

public class Product

{

    public int    Id     { getset; }

    public string Name   { getset; }

    public Status Status { getset; }

}

Almost all of the Service methods will return the OrderResponse object. OrderResponse is a representation of the Order which is a class within the domain object. But, as we will see, it can contain some more information. In our case, it contains a list of Links.

public class OrdersResponse

{

    public List<OrderResponse> Orders { getset; }

}

 

public class OrderResponse 

{

    public int Id { getset; }

    public bool IsTakeAway { getset; }

    public DateTime CreationDate { getset; }

    public Status Status { getset; }

    public List<OrderItemResponse> Items { getset; }

    public List<Link> Links { getset; }

}

 

public class OrderItemResponse 

{

    public int Id { getset; }

    public ProductResponse Product { getset; }

    public int Quantity { getset; }

    public List<Link> Links { getset; }

}

 

public class ProductResponse

{

    public int Id { getset; }

    public string Name { getset; }

    public Status Status { getset; }    

    public List<Link> Links { getset; }

}

Route Specification

In the application host, we need to register the various Request DTOs to their relative URLs.

Routes

    .Add<GetOrder>   ("/orders/{Id}""GET",    "Returns an Order")

    .Add<GetOrders>  ("/orders",      "GET",    "Returns Orders"  )

    .Add<Order>      ("/orders",      "POST",   "Creates an Order")

    .Add<Order>      ("/orders/{Id}""PUT",    "Updates an Order")

    .Add<DeleteOrder>("/orders/{Id}""DELETE""Deletes an Order");

Order Mapper Implementation

In order to map the data back and forth from the domain object model to the service model (DTOs), as we did in the ProductService example, we will have a specific OrderMapper class for this.

The OrderMapper implements the IOrderMapper interface which will make it easier to be injected to the service.

using SrvObj = ServiceStack.Succinctly.ServiceInterface.OrderModel;

using Domain = OrderManagement.Core.Domain;

public interface IOrderMapper

{

   Domain.Order ToOrder(SrvObj.Order request);

   SrvObj.OrderResponse ToOrderResponse(Domain.Order order);

   List<SrvObj.OrderResponse> ToOrderResponseList(List<Domain.Order> orders);

   SrvObj.OrderItemResponse ToOrderItemResponse

                                   (int orderId, Domain.OrderItem orderItem);

   List<SrvObj.OrderItemResponse> ToOrderItemResponseList

                                 (int orderId, List<Domain.OrderItem> items);

}

The following example is the full implementation of the mapper.

public class OrderMapper : IOrderMapper

{

    static OrderMapper()

    {

        Mapper.CreateMap<Domain.StatusStatus>();

        Mapper.CreateMap<Status, Domain.Status>();

        Mapper.CreateMap<SrvObj.Order, Domain.Order>();

        Mapper.CreateMap<SrvObj.OrderItem, Domain.OrderItem>();

        Mapper.CreateMap<SrvObj.Product, Domain.Product>();

        Mapper.CreateMap<Domain.Order, SrvObj.OrderResponse>();

        Mapper.CreateMap<Domain.OrderItem, SrvObj.OrderItemResponse>();

        Mapper.CreateMap<Domain.Product, SrvObj.ProductResponse>();

    }

    public Domain.Order ToOrder(SrvObj.Order request)

    {

        return Mapper.Map<Domain.Order>(request);

    }

 

    public SrvObj.OrderResponse ToOrderResponse(Domain.Order order)

    {

        var orderResponse = Mapper.Map<SrvObj.OrderResponse>(order);

 

        var orderSelfLink = "orders/{0}".Fmt(order.Id);

        orderResponse.Links = new List<Link>();

        orderResponse.Links.Add(SelfLink(orderSelfLink));

        orderResponse.Items.ForEach(x =>

            {

                var productId = x.Product.Id;

                var productLInk = "products/{0}".Fmt(productId);

                var itemsLink = orderSelfLink + "/items/{0}".Fmt(x.Id);

                x.Product.Links = new List<Link>();

                x.Product.Links.Add(SelfLink(productLInk));

                x.Links = new List<Link>();

                x.Links.Add(SelfLink(itemsLink));

            });

        return orderResponse;

    }

 

    private Link SelfLink(string uri)

    {

        return new Link

            {

                Title = "self",

                Rel = "self",

                Href = uri

            };

    }

 

    private Link ParentLink(string uri)

    {

        return new Link

            {

                Title = "parent",

                Rel = "parent",

                Href = uri

            };

    }

 

   public List<SrvObj.OrderResponse> ToOrderResponseList(List<Domain.Order> orders)

    {

        var orderResponseList = new List<SrvObj.OrderResponse>();

        orders.ForEach(x => orderResponseList.Add(ToOrderResponse(x)));

        return orderResponseList;

    }

    public SrvObj.OrderItemResponse ToOrderItemResponse(int orderId, 

                                                        Domain.OrderItem item)

    {

        var orderItemReponse = Mapper.Map<SrvObj.OrderItemResponse>(item);

 

        var productId = orderItemReponse.Product.Id;

        var orderLink = "orders/{0}".Fmt(orderId);

        var itemsLink = "/items/{0}".Fmt(item.Id);

        var productLink = "products/{0}".Fmt(productId);

 

        orderItemReponse.Links.Add(SelfLink(orderLink + itemsLink));

        orderItemReponse.Links.Add(ParentLink(orderLink));

        orderItemReponse.Product.Links.Add(SelfLink(productLink));

 

        return orderItemReponse;

    }

 

    public List<SrvObj.OrderItemResponse

                   ToOrderItemResponseList(int orderId,                             

                                           List<Domain.OrderItem> items)

    {

      var orderItemResponseList = new List<SrvObj.OrderItemResponse>();

      items.ForEach(x => orderItemResponseList

                         .Add(ToOrderItemResponse(orderId, x)));

      return orderItemResponseList;

    }

}

Validation Implementation

The order validator is simple. It only checks if the order contains a not-empty OrderItems collection and it will simultaneously check that the CreationDate is not in the future.

public class OrderValidator : AbstractValidator<Order>

{

    public OrderValidator()

    {

        RuleFor(r => r.CreationDate)

            .LessThan(DateTime.Now.AddSeconds(10))

            .WithMessage("Creation Data shouldn't be in the future");

 

        RuleFor(r => r.Items)

            .NotEmpty()

            .WithMessage("Order Items should be specified");

    }

}

Application Host Configuration

The following code shows the full implementation of the application host. This comprehends the previously defined configuration for the ProductService.

public class ServiceAppHost : AppHostBase

{

    public ServiceAppHost()

        : base("Order Management"typeof (ServiceAppHost).Assembly)

    {

        Routes

         //Products

         .Add<GetProducts>("/products""GET""Returns Products")

         .Add<GetProduct>("/products/{Id}""GET""Returns a Product")

         .Add<CreateProduct>("/products""POST""Creates a Product")

         .Add<UpdateProduct>("/products/{Id}""PUT""Updates a Product")

         .Add<DeleteProduct>("/products/{Id}""DELETE""Deletes a Product")

         

         //Orders

         .Add<GetOrder>("/orders/{Id}""GET""Returns an Order")

         .Add<GetOrders>("/orders""GET""Returns Orders")

         .Add<Order>("/orders""POST""Creates an Order")

         .Add<Order>("/orders/{Id}""PUT""Updates an Order")

         .Add<DeleteOrder>("/orders/{Id}""DELETE""Deletes an Order");

 

        Plugins.Add(new ValidationFeature());

    }

 

    public override void Configure(Container container)

    {

        //Product dependencies

        container.Register<IProductRepository>(new ProductRepository());

        container.Register<IProductMapper>(new ProductMapper());

 

        //Orders dependencies

        container.Register<IOrderRepository>(new OrderRepository());

        container.Register<IOrderMapper>(new OrderMapper());

        container.Register<IStatusRepository>(new StatusRepository());

        //Validators

        container.RegisterValidator(typeof (CreateProductValidator));

        container.RegisterValidator(typeof (UpdateProductValidator));

        container.RegisterValidator(typeof (OrderValidator));

     }

}

The highlighted code is what is needed for the OrderService. As we may see, it is just a natural progression by following the same style of coding and service creation.

Service Implementation

OrderService implements two Get methods, one Post, one Put, and one Delete.

As shown in the following code example, OrderService has four public properties, namely OrderMapper, OrderRepository, ProductRepository, and StatusRepository, which will hold the instances that will be injected at run time by the IoC container.

public class OrderService : ServiceStack.ServiceInterface.Service

{

    public IOrderRepository   OrderRepository   { getset; }

    public IProductRepository ProductRepository { getset; }

    public IStatusRepository  StatusRepository  { getset; }

    public IOrderMapper       OrderMapper       { getset; }

    

    //Returns all the orders.

    public OrdersResponse Get(GetOrders request) { … }

    //Returns a single order.

    public OrderResponse Get(GetOrder request){ … }

    //Creates a new order.

    public OrderResponse Post(Order request) { … }

    //Updates an existing order /orders/{id}.

    public OrderResponse Put(Order request){ … }

    //Delete an order.

    public HttpResult Delete(DeleteOrder request){ … }

}

Getting Orders by OrderId

When calling GET /orders/1, the Get(GetProduct request) method will be called. As we have seen previously with the ProductService, the structure is very similar:

  1. Get the data from the repository.
  2. If nothing is found, return the 404 Not Found error. This is done by setting the Response.StatusCode value.
  3. If something is returned by the repository, use the OrderMapper to transform the Order (transform the domain model to the OrderResponse service model).

//Returns a single order.

public OrderResponse Get(GetOrder request)

{

    var domainObject = OrderRepository.GetById(request.Id);

    if (domainObject == null)

    {

        Response.StatusCode = (intHttpStatusCode.NotFound;

        return null;

    }

    else

    {

        //Transform to OrderResponse and return.

        return OrderMapper.ToOrderResponse(domainObject);

    }

}

In order to call the GET service method used in the previous example, we will use the XmlServiceClient which, by default, would communicate with the Content-Type: application/xml. The following example shows a test that uses the built-in ServiceStack client to perform a GET on the /orders/{Id} URI.

[TestMethod]

public void GetOrdersByOrderId()

{

    //ARRANGE --- 

    const int ORDER_ID = 1;

    var client = new XmlServiceClient("http://localhost:50712/");

 

    //ACT  ------ 

    var order = client.Get<OrderResponse>("/orders/" + ORDER_ID);

 

    //ASSERT ---- 

    Assert.IsTrue(order != null);

    Assert.IsTrue(order.Id == ORDER_ID);

}

Returning All Orders

In this case, the service accepts a GetOrders message. As we have seen in the ProductService, we follow the same procedure:

  1. Get data from the repository.
  2. Transform data from the domain to the service object model.

public OrdersResponse Get(GetOrders request)

{

    //Get data from the database.

    var orders = OrderRepository.GetAllOrders();

 

    //Transform to OrdersResponse and return.

    return new OrdersResponse()

    {

        Orders = OrderMapper.ToOrderResponseList(orders)

    };

}

To call GET /orders (to get the list of available orders), the client code would look like the following example.

[TestMethod]

public void GetAllOrders()

{

    //ARRANGE --- 

    var client = new XmlServiceClient("http://localhost:50712/");

 

    //ACT  ------ 

    var orders = client.Get<OrdersResponse>("/orders");

 

    //ASSERT ---- 

    Assert.IsTrue(orders != null);

    Assert.IsTrue(orders.Orders.Count > 0);

}

Creating a New Order

When creating new Orders, there are several things to note:

  • To create objects, the POST method is used.
  • Before doing any further checks, we will make sure that a correct Domain.Order is generated, and therefore a Status and Product are retrieved from the repository and assigned to the object.
  • Once the Order is created and successfully sent to the repository, we will return the full Order representation client as we would do with GET /orders/1.
  • To note that the object has been created, we will add the location in the header and inform the client of the object’s new location.
  • Status code 201 Created will be returned to the client.

public OrderResponse Post(Order request)

{

    //Transforming the IDs into real object objects.

    var newOrder = OrderMapper.ToOrder(request);

    newOrder.Status = StatusRepository.GetById(request.Status.Id);

 

    newOrder.Items.ForEach(x =>

        {

            x.Product = ProductRepository.GetById(x.Product.Id);

        });

 

    //Storing data to the database.

    newOrder = OrderRepository.Add(newOrder);

 

    //Transform to OrderResponse.

    var response = OrderMapper.ToOrderResponse(newOrder);

 

    //Manipulate the header and StatusCode.

    Response.AddHeader("Location", Request.AbsoluteUri + "/" + newOrder.Id);

    Response.StatusCode = (int)HttpStatusCode.Created;

 

    return response;

}

To insert a new Order using the ServiceStack client, we will test that the response is returning the 201 Created HTTP status and that the Location header has been filled.

[TestMethod]

public void CreateNewOrder()

{

    //ARRANGE --- 

    WebHeaderCollection headers = null;

    HttpStatusCode statusCode = 0;

    const string SITE = "http://localhost:50712";

    const string ORDERS = "/orders";

    const string URI = SITE + ORDERS;

 

    var client = new XmlServiceClient(SITE)

        {

            //Grabbing the header once the call is ended.

            LocalHttpWebResponseFilter =

                httpRes =>

                    {

                        headers = httpRes.Headers;

                        statusCode = httpRes.StatusCode;

                    }

        };

    var newOrder = new Order

      {

         CreationDate = DateTime.Now,

         IsTakeAway = true,                   

         Status = new Status {Id = 1}, //Active

         Items = new List<OrderItem>

           {

             new OrderItem {Product = new Product {Id = 1}, Quantity = 10},

             new OrderItem {Product = new Product {Id = 2}, Quantity = 10}

            }

       };

 

    //ACT  ------ 

    var order = client.Post<OrderResponse>(ORDERS, newOrder);

 

    //ASSERT ---- 

    Assert.IsTrue(headers["Location"] == URI + "/" + order.Id);

    Assert.IsTrue(statusCode == HttpStatusCode.Created);

    Assert.IsTrue(order.Items.Count == 2);

    Assert.IsTrue(order.Status.Id == 1); //Status is active.

}

After the Order is created, the following XML will be generated and headers will be returned to the client.

HTTP/1.1 201 Created

Cache-Control: private

Content-Type: application/xml

Location: http://localhost:50712/orders/5

Server: Microsoft-IIS/8.0

X-Powered-By: ServiceStack/3.956 Win32NT/.NET

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

Date: Wed, 14 Aug 2013 22:13:31 GMT

Content-Length: 722

<OrderResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

  <CreationDate>2013-08-13T00:00:00</CreationDate>

  <Id>5</Id>

  <IsTakeAway>false</IsTakeAway>

  <Items>

    <OrderItemResponse>

      <Id>8</Id>

      <Links>

        <Link>

          <Href>orders/5/items/8</Href>

          <Rel>self</Rel>

          <Title>self</Title>

          <Type i:nil="true" />

        </Link>

      </Links>

      <Product>

        <Id>1</Id>

        <Links>

          <Link>

            <Href>products/1</Href>

            <Rel>self</Rel>

            <Title>self</Title>

            <Type i:nil="true" />

          </Link>

        </Links>

        <Name>Pizza</Name>

        <Status>

          <Id>1</Id>

          <Name>Active</Name>

        </Status>

      </Product>

      <Quantity>10</Quantity>

    </OrderItemResponse>

  </Items>

  <Links>

    <Link>

      <Href>orders/5</Href>

      <Rel>self</Rel>

      <Title>self</Title>

      <Type i:nil="true" />

    </Link>

  </Links>

  <Status>

    <Id>1</Id>

    <Name>Active</Name>

  </Status>

</OrderResponse>

Updating an Order

To update an Order, our service will implement the PUT method, which will accept the Order message.

If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes should be sent to indicate successful completion of the request. In our case, if everything goes well, we don’t specify anything because 200 OK is the default response. If the Order doesn’t exist, I’ve chosen to return a “Not Found” message.

//Updates an existing order /orders/{id}.

public OrderResponse Put(Order request)

{

    var domainObject = OrderRepository.GetById(request.Id);

    if (domainObject == null)

    {

        Response.StatusCode = (int)HttpStatusCode.NotFound;

        return null;

    }

 

    var updatedOrder = OrderMapper.ToOrder(request);

    updatedOrder.Status = StatusRepository.GetById(request.Status.Id);

    updatedOrder.Items.ForEach(x =>

        {

            x.Product = ProductRepository.GetById(x.Product.Id);

        });

 

    //Store data to database.

    var order = OrderRepository.Update(updatedOrder);

 

    //Transform to OrderResponse and return.

    return OrderMapper.ToOrderResponse(order);

}

To test the Order update, we will change a couple of fields: CreationDate, IsTakeAway, Quantity, and the Product associated with the OrderItem with an Id of 6.

In the assert section, we will check that the newly created object has the values assigned to the updated Order.

[TestMethod]

public void UpdateOrder()

{

    //ARRANGE --- 

    HttpStatusCode statusCode = 0;

    const string SITE = "http://localhost:50712";

    const string ORDERS_LINK = "/orders/1";

    const string URI = SITE + ORDERS_LINK;

    const int NEW_PRODUCT_ID = 5;

    DateTime NEW_CREATION_DATE = new DateTime(2013, 08, 08);

 

    var client = new XmlServiceClient(SITE)

    {

        //Grabbing the header once the call is ended.

        LocalHttpWebResponseFilter =

            httpRes =>

            {

                statusCode = httpRes.StatusCode;

            }

    };

 

    var updateOrder = new Order

        {

            CreationDate = NEW_CREATION_DATE,

            IsTakeAway = false,

            Items = new List<OrderItem>()

                {

                    new OrderItem

                        {

                            Id = 6,

                            //Setting a different product!

                            Product = new Product {Id = NEW_PRODUCT_ID}, 

                            //Setting a different quantity.

                            Quantity = 100

                        }

                },

            Status = new Status {Id = 1}

        };

 

    //ACT  ------ 

    var orderResponse = client.Put<OrderResponse>(ORDERS_LINK, updateOrder);

 

    //ASSERT ---- 

    Assert.IsTrue(statusCode == HttpStatusCode.OK);

    Assert.IsTrue(orderResponse.CreationDate == NEW_CREATION_DATE);

    Assert.IsTrue(orderResponse.IsTakeAway == false);

    Assert.IsTrue(orderResponse.Items.Count == 1);

    Assert.IsTrue(orderResponse.Items[0].Product.Id == NEW_PRODUCT_ID);

}

When testing the data in the browser by accessing GET /Orders/6, we can see that the Order has been updated.

Deleting an Order

When deleting an Order, the important thing is to return the “No content” status after the deletion has taken place. When returning the “No content” status, the message body shouldn’t be returned back to the client.

public HttpResult Delete(DeleteOrder request)

{

    var domainObject = OrderRepository.GetById(request.Id);

    if (domainObject == null)

    {

        Response.StatusCode = (int)HttpStatusCode.NotFound;

    }

    else

    {

        //Delete order in the database. 

        OrderRepository.Delete(request.Id);

        Response.StatusCode = (int)HttpStatusCode.NoContent;

    }

 

    //Not returning any body!

    return null;

}

The following code tests the Delete method implemented in the service.

[TestMethod]

public void DeleteOrder()

{

    //ARRANGE --- 

    HttpStatusCode statusCode = 0;

    const string SITE = "http://localhost:50712";

 

    var client = new XmlServiceClient(SITE)

    {

        //Grabbing the header once the call is ended.

        LocalHttpWebResponseFilter =

            httpRes =>

            {

                statusCode = httpRes.StatusCode;

            }

    };

 

    //ACT  ------ 

    client.Delete<HttpResult>("/orders/2");

 

    //ASSERT ---- 

    Assert.IsTrue(statusCode == HttpStatusCode.NoContent);

}

OrderItem Service

OrderItemService will implement the following methods:

  • GET    /orders/1234/items
  • GET    /orders/1234/items/{id}

With this example, I want to show how to create a service that will enable navigating complex properties of a parent resource. While we can implement the creation or updating of an existing OrderItem, I think that is not as important since we have already seen how to create or update a resource in the previous two services. So, I’ve intentionally omitted the POST, PUT, and DELETE verbs for the OrderItem.

Service Model

OrderItemService’s service model is simple. The following classes will be used as the Request DTOs of the service.

public class GetOrderItem

{

    public int OrderId { getset; }

    public int ItemId { getset; }

}

public class GetOrderItems

{

    public int OrderId { getset; }

}

We will reuse the OrderItemResponse that we have previously created for the OrderService because it perfectly suits the need, and create a new class, OrderItemsResponse, that will carry on a list of OrderItem responses.

public class OrderItemsResponse

{

    public List<OrderItemResponse> Items { getset; }

}

Route Specification

In the application host, we need to register the various Request DTOs to their relative URIs.

Routes

.Add<GetOrderItem>("/orders/{OrderId}/items/{ItemId}","GET"," Get an OrderItem")

.Add<GetOrderItems>("/orders/{OrderId}/items""GET""Get a list of OrderItems");

Mapper Implementation

We will reuse the OrderMapper because it implements all of the necessary mappings.

Service Implementation

The following code example shows the public members of the OrderItemService. As in previous examples, public members will be injected by the IoC.

public interface IOrderItemService

{

    public IOrderRepository OrderRepository { getset; }

    public IProductRepository ProductRepository { getset; }

    public IStatusRepository StatusRepository { getset; }

    public IOrderMapper OrderMapper { getset; }

 

    OrderItemResponse  Get(GetOrderItem request){};

    OrderItemsResponse Get(GetOrderItems request){};

}

Returning All OrderItems Associated with a Specific Order

When calling GET /orders/1/items, the Get(GetOrderItem request) method will be called.

public OrderItemsResponse Get(GetOrderItems request)

{

    var order = OrderRepository.GetById(request.OrderId);

 

    List<Domain.OrderItem> orderItems = order.Items;

 

    if (orderItems == null || orderItems.Count == 0)

    {

        Response.StatusCode = (intHttpStatusCode.NotFound;

        return null;

    }

    else

    {

        return new OrderItemsResponse

            {

                Items = OrderMapper

                .ToOrderItemResponseList(request.OrderId, orderItems)

            };

    }

}

The client code will check the existence of the items.

[TestMethod]

public void GetOrderItemsByOrderId()

{

    //ARRANGE --- 

    int ORDER_ID = 1;

    string ITEMS_LINK = "/orders/" + ORDER_ID + "/items";

 

    var client = new XmlServiceClient("http://localhost:50712/");

 

    //ACT  ------ 

    var items = client.Get<OrderItemsResponse>(ITEMS_LINK);

 

    //ASSERT ---- 

    Assert.IsNotNull(items != null);

    Assert.IsNotNull(items.Items);

    Assert.IsTrue(items.Items.Count == 2);

}

Returning a Specific OrderItem

The particularity of this method is that GetOrderItem contains both the OrderId and ItemId properties.

public OrderItemResponse Get(GetOrderItem request)

{

    var order = OrderRepository.GetById(request.OrderId);

 

    Domain.OrderItem orderItem =

        order.Items

            .FirstOrDefault(x => x.Id == request.ItemId);

 

    if (orderItem == null)

    {

        Response.StatusCode = (int)HttpStatusCode.NotFound;

        return null;

    }

 

    OrderItemResponse response =

        OrderMapper

        .ToOrderItemResponse(order.Id, orderItem);

 

    return response;

}

And it contains the test code that fully tests the URI GET /orders/1/items/2.

[TestMethod]

public void GetOrderItemByOrderId()

{

    //ARRANGE --- 

    var ITEM_ID = "2";

    var client = new XmlServiceClient("http://localhost:50712/");

    string ITEM_LINK = "/orders/1/items/2";

 

    //ACT  ------ 

    var item = client.Get<OrderItemResponse>(ITEM_LINK);

 

    //ASSERT ---- 

    Assert.IsNotNull(item != null);

    Assert.IsTrue(item.Id.ToString() == ITEM_ID);

}

Conclusion

In this chapter, we have seen the implementation of all the verbs for the three web services as well as how to wire the requests to the right call.

We were able to test the implementation by using the unit tests, and we have seen how to use the JsonServiceClient and the XmlServiceClient to call the newly implemented services.

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.