How to: Expose Domain Methods
This article is relevant to entity models that utilize the deprecated Visual Studio integration of Telerik Data Access. The current documentation of the Data Access framework is available here.
This article will show you how to expose domain methods mapped to database stored procedures (or table-valued functions) through a ServiceStack layer generated by Service Wizard.
Suppose that you created a stored procedure called ReadProductDetails in the Northwind sample database. This procedure takes a single parameter - the ID of a given product, and returns a subset of the columns from the Products table and the Categories table. Suppose the procedure is mapped to a domain method in your model. The method's signature is public IEnumerable<ProductDetails> ReadProductDetails(int? productID).
To expose this method in your ServiceStack layer, you will extend the generated code with entirely new service. You can follow the next steps:
-
In the DTOs project, add a class file called ProductDetailsDTO.cs(.vb). This file will hold the ProductDetailsDTO class and will contain the properties of ProductDetails (the complex type mapped to the stored procedure's result shape). The class has to be in the same namespace as the rest of the DTOs in your ServiceStack layer. For example:
using System; using System.Collections.Generic; namespace EntitiesModelServicesDTOs.CoreDTOs { public partial class ProductDetailsDTO { public string ProductName { get; set; } public string CompanyName { get; set; } public string CategoryName { get; set; } public string QuantityPerUnit { get; set; } public decimal? UnitPrice { get; set; } public short? UnitsInStock { get; set; } public short? UnitsOnOrder { get; set; } public short? ReorderLevel { get; set; } public bool Discontinued { get; set; } } }
Namespace CoreDTOs Partial Public Class ProductDetailsDTO Public Property ProductName() As String Public Property CompanyName() As String Public Property CategoryName() As String Public Property QuantityPerUnit() As String Public Property UnitPrice() As Decimal? Public Property UnitsInStock() As Short? Public Property UnitsOnOrder() As Short? Public Property ReorderLevel() As Short? Public Property Discontinued() As Boolean End Class End Namespace
-
In the DTOs project, add a class file called ProductDetailsServiceMessages.cs(.vb). It will hold the request and response messages used by ServiceStack. For example:
using System; using ServiceStack; using EntitiesModelServicesDTOs.CoreDTOs; namespace EntitiesModelServicesDTOs.ProductDetailsServiceMessages { public partial class GetProductDetailsById : IReturn<GetProductDetailsByIdResponse> { public int ProductId { get; set; } } public partial class GetProductDetailsByIdResponse { public ProductDetailsDTO Product { get; set; } } }
Imports ServiceStack Imports EntitiesModelServicesDTOs.CoreDTOs Namespace ProductDetailsServiceMessages Partial Public Class GetProductDetailsById Implements IReturn(Of GetProductDetailsByIdResponse) Public Property ProductId() As Integer End Class Partial Public Class GetProductDetailsByIdResponse Public Property Product() As ProductDetailsDTO End Class End Namespace
-
In the project which holds the code base of the ServiceStack layer, add a class file called EntitySpecificRepositories.partial.cs(.vb). This file will hold the repository dedicated to the stored procedure. For example:
using ServiceStack.Model; using System.Linq; namespace ServiceStack.Demo.Repositories { public partial interface IProductDetailsRepository : IBaseRepository<ServiceStack.Model.ProductDetails, ServiceStack.Model.EntitiesModel> { ProductDetails ReadProductDetails(int productId); } public partial class ProductDetailsRepository : BaseRepository<ServiceStack.Model.ProductDetails, ServiceStack.Model.EntitiesModel>, IProductDetailsRepository { public ProductDetails ReadProductDetails(int productId) { ProductDetails product = this.context.ReadProductDetails(productId).SingleOrDefault(); return product; } } }
Namespace Repositories Public Interface IProductDetailsRepository Inherits IBaseRepository(Of Model.ProductDetails, Model.EntitiesModel) Function ReadProductDetails(ByVal productId As Integer) As Model.ProductDetails End Interface Partial Public Class ProductDetailsRepository Inherits BaseRepository(Of Model.ProductDetails, Model.EntitiesModel) Implements IProductDetailsRepository Public Function ReadProductDetails(ByVal productId As Integer) As Model.ProductDetails _ Implements IProductDetailsRepository.ReadProductDetails Dim product As Model.ProductDetails = Me.context.ReadProductDetails(productId).SingleOrDefault() Return product End Function End Class End Namespace
In the project which holds the code base of the ServiceStack layer, add a class file called ProductDetailsService.cs(.vb). This file will hold the ServiceStack service for the domain method and its implementation.
-
In the ProductDetailsService.cs(.vb) file, add the IProductDetailsServiceImplementation interface. For example:
using System; using System.Linq; using System.Net; using System.Collections.Generic; using ServiceStack; using ServiceStack.Demo.DynamicQueryUtils; using ServiceStack.Demo.Repositories; using ServiceStack.Model; using EntitiesModelServicesDTOs.CoreDTOs; using EntitiesModelServicesDTOs.ProductDetailsServiceMessages; namespace ServiceStack.Demo { public partial interface IProductDetailsServiceImplementation { Service ServiceReference {get; set; } GetProductDetailsByIdResponse Get(GetProductDetailsById request); } }
Imports EntitiesModelServicesDTOs.ProductDetailsServiceMessages Imports ServiceStack.Demo.VB.Repositories Imports System.Net Imports EntitiesModelServicesDTOs.CoreDTOs Imports ServiceStack.Model Public Interface IProductDetailsServiceImplementation Property ServiceReference() As Service Function [Get](ByVal request As GetProductDetailsById) As GetProductDetailsByIdResponse End Interface
-
In the same file, add the ProductDetailsServiceImplementation class, which will hold the actual implementation of the service method. For example:
public partial class ProductDetailsServiceImplementation : IProductDetailsServiceImplementation { private IProductDetailsRepository repository; public Service ServiceReference { get; set; } public ProductDetailsServiceImplementation(IProductDetailsRepository repository) { this.repository = repository; } public virtual GetProductDetailsByIdResponse Get(GetProductDetailsById request) { ServiceStack.Model.ProductDetails product = this.repository.ReadProductDetails(request.ProductId); if (product == null) { throw new HttpError(HttpStatusCode.NotFound, "Resource not available.", "A Product object with the specified identificator is not available."); } GetProductDetailsByIdResponse response = new GetProductDetailsByIdResponse(); response.Product = product.ConvertTo<ProductDetailsDTO>(); return response; } }
Partial Public Class ProductDetailsServiceImplementation Implements IProductDetailsServiceImplementation Private repository As IProductDetailsRepository Public Property ServiceReference() As Service _ Implements IProductDetailsServiceImplementation.ServiceReference Public Sub New(ByVal repository As IProductDetailsRepository) Me.repository = repository End Sub Public Overridable Function [Get](ByVal request As GetProductDetailsById) As GetProductDetailsByIdResponse _ Implements IProductDetailsServiceImplementation.Get Dim product As ProductDetails = Me.repository.ReadProductDetails(request.ProductId) If product Is Nothing Then Throw New HttpError(HttpStatusCode.NotFound, "Resource not available.", "A Product object with the specified identificator is not available.") End If Dim response As New GetProductDetailsByIdResponse() response.Product = product.ConvertTo(Of ProductDetailsDTO)() Return response End Function End Class
-
In the same file as well, add the ProductDetailsService class, which will hold the ServiceStack web service method. For example:
public partial class ProductDetailsService : Service { private static string productDetailsBasePath = @"/ProductDetails"; public static string ProductDetailsBasePath { get { return productDetailsBasePath; } protected set { productDetailsBasePath = value; } } private IProductDetailsServiceImplementation productDetailsSvcImp; public ProductDetailsService(IProductDetailsServiceImplementation productDetailsSvcImp) { this.productDetailsSvcImp = productDetailsSvcImp; this.productDetailsSvcImp.ServiceReference = this; } public GetProductDetailsByIdResponse Get(GetProductDetailsById request) { return this.productDetailsSvcImp.Get(request); } }
Partial Public Class ProductDetailsService Inherits Service Private Shared _productDetailsBasePath As String = "/ProductDetails" Public Shared Property ProductDetailsBasePath() As String Get Return _productDetailsBasePath End Get Protected Set(ByVal value As String) _productDetailsBasePath = value End Set End Property Private productDetailsSvcImp As IProductDetailsServiceImplementation Public Sub New(ByVal productDetailsSvcImp As IProductDetailsServiceImplementation) Me.productDetailsSvcImp = productDetailsSvcImp Me.productDetailsSvcImp.ServiceReference = Me End Sub Public Function [Get](ByVal request As GetProductDetailsById) As GetProductDetailsByIdResponse Return Me.productDetailsSvcImp.Get(request) End Function End Class
-
Finally, in order to register the new service, you need to extend the ServiceStack service host class in a partial class and to implement the ApplyCustomIocContainerEntityRepositoriesConfiguration, ApplyCustomIocContainerServiceImplementationsConfiguration, and RegisterCustomRoutes methods. In the generated code, this would be the web application that host the service layer. For example:
using System; using System.Reflection; using Funq; using ServiceStack; using ServiceStack.Demo; using ServiceStack.Demo.Repositories; using EntitiesModelServicesDTOs.ProductServiceMessages; using EntitiesModelServicesDTOs.ProductDetailsServiceMessages; namespace ServiceStack.Demo { public partial class AppHost { partial void ApplyCustomIocContainerEntityRepositoriesConfiguration(Funq.Container container, ref bool shouldRegisterDefaultEntityRepositories) { shouldRegisterDefaultEntityRepositories = true; container.RegisterAutoWiredAs<ProductDetailsRepository, IProductDetailsRepository>(). ReusedWithin(ReuseScope.None); } partial void ApplyCustomIocContainerServiceImplementationsConfiguration(Funq.Container container, ref bool shouldRegisterDefaultServiceImplementations) { shouldRegisterDefaultServiceImplementations = true; container.RegisterAutoWiredAs<ProductDetailsServiceImplementation, IProductDetailsServiceImplementation>(). ReusedWithin(ReuseScope.None); } partial void RegisterCustomRoutes(ref bool shouldRegisterDefaultRoutes) { shouldRegisterDefaultRoutes = true; this.Routes.Add<GetProductDetailsById>(ProductDetailsService.ProductDetailsBasePath + "/{ProductId}", ApplyTo.Get); } } }
Imports ServiceStack.Demo.Repositories Imports Funq Imports EntitiesModelServicesDTOs.ProductDetailsServiceMessages Partial Public Class AppHost Private Sub ApplyCustomIocContainerEntityRepositoriesConfiguration(ByVal container As Funq.Container, ByRef shouldRegisterDefaultEntityRepositories As Boolean) shouldRegisterDefaultEntityRepositories = True container.RegisterAutoWiredAs(Of ProductDetailsRepository, IProductDetailsRepository)() _ .ReusedWithin(ReuseScope.None) End Sub Private Sub ApplyCustomIocContainerServiceImplementationsConfiguration(ByVal container As Funq.Container, ByRef shouldRegisterDefaultServiceImplementations As Boolean) shouldRegisterDefaultServiceImplementations = True container.RegisterAutoWiredAs(Of ProductDetailsServiceImplementation, IProductDetailsServiceImplementation)() _ .ReusedWithin(ReuseScope.None) End Sub Private Sub RegisterCustomRoutes(ByRef shouldRegisterDefaultRoutes As Boolean) shouldRegisterDefaultRoutes = True Me.Routes.Add(Of GetProductDetailsById)(ProductDetailsService.ProductDetailsBasePath & _ "/{ProductId}", ApplyTo.Get) End Sub End Class
Build your solution and start your application.
-
You can test the new service method on a URL similar to the following:
http://localhost:<port_number>/ProductDetails/{ProductId}