Handling Query Request Parameters
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 give you details about the handling of the parameters provided by the query request, when you call a queryable SeviceStack service method.
General Information
Suppose you have a ServiceStack layer generated with Service Wizard. The layer exposes the Category and Product entities from the Northwind sample database. If you take a look at the generated service messages for the CategoryDTO class (in the EntitiesModelServicesDTOs class library), you will notice that the QueryCategories message implements the IQueryRequest interface.
public partial class QueryCategories
: QueryBase<CategoryDTO>, IQueryRequest
{
public string Filter { get; set; }
public string Include { get; set; }
public bool InlineCount { get; set; }
}
Partial Public Class QueryCategories
Inherits QueryBase(Of CategoryDTO)
Implements IQueryRequest
Public Property Filter As String Implements IQueryRequest.Filter
Public Property Include As String Implements IQueryRequest.Include
Public Property InlineCount As Boolean Implements IQueryRequest.InlineCount
Public Property Skip_Reroute As Nullable(Of Integer) Implements IQueryRequest.Skip
Get
Return MyBase.Skip
End Get
Set(value As Nullable(Of Integer))
MyBase.Skip = value
End Set
End Property
Public Property Take_Reroute As Nullable(Of Integer) Implements IQueryRequest.Take
Get
Return MyBase.Take
End Get
Set(value As Nullable(Of Integer))
MyBase.Take = value
End Set
End Property
Public Property OrderBy_Reroute As String Implements IQueryRequest.OrderBy
Get
Return MyBase.OrderBy
End Get
Set(value As String)
MyBase.OrderBy = value
End Set
End Property
Public Property OrderByDesc_Reroute As String Implements IQueryRequest.OrderByDesc
Get
Return MyBase.OrderByDesc
End Get
Set(value As String)
MyBase.OrderByDesc = value
End Set
End Property
End Class
This interface, gives Telerik Data Access the ability to do server-side filtering, sorting, paging, and loading of related objects based on parameters provided in the query string. The ServiceStack layers generated with Service Wizard support the following keywords:
Parameter | Syntax | Example |
Filter | <PropertyName> <ComparisonOperator> ‘<Value>’ <and/or> … | http://localhost/Categories?filter=CategoryID+gt+'3' |
OrderBy* | <PropertyName> | http://localhost/Categories?OrderBy=CategoryName |
OrderByDesc* | <PropertyName> | http://localhost/Categories?OrderByDesc=CategoryName |
GroupBy* | <PropertyName> | http://localhost/Categories?GroupBy=CategoryID |
Include | <PropertyName> | http://localhost/Categories?Include=Products |
InlineCount | <true/false> | http://localhost/Categories?InlineCount=true |
Skip* | <SkipCount> | http://localhost/Categories?Skip=1 |
Take* | <TakeCount> | http://localhost/Categories?Take=2 |
Format* | <xml/json/jsv/csv> | http://localhost/Categories?Format=xml |
*These properties are inherited from the QueryBase<T> class.
For example, the parameters in the query request can be combined like this:
http://localhost/Categories?filter=CategoryID+gt+'3'&GroupBy=CategoryID&InlineCount=true&Include=Products&Skip=1&Take=2&Format=xml
By design, such query request will evoke the QueryResponse
How to: Handle The Include Parameter
Through the Include parameter you can expose a single navigational property. If your scenario requires you to expose more navigational properties, you need to implement the workflow demonstrated in the How to: Retrieve Related Entities article.
By default, the DTO classes generated by Service Wizard do not expose the navigational properties of a given entity, and the service methods neither retrieve them, nor populate them. To change this, you need to make a few modifications in the default behaviour of the queryable methods (for examle, the QueryResponse
-
In the DTOs project, add a class file called CategoryDTO.partial.cs(.vb). In it file you will extend CategoryDTO in a partial class. For example:
namespace EntitiesModelServicesDTOs.CoreDTOs { public partial class CategoryDTO { public IList<ProductDTO> Products { get; set; } } }
Namespace CoreDTOs Partial Public Class CategoryDTO Public Property Products() As IList(Of ProductDTO) End Class End Namespace
When your ServiceStack layer exposes multiple related entities, make sure to avoid circular references between the corresponding DTO classes.
-
In the project which holds the code base of the ServiceStack layer, add a new class file called CategoryServiceImplementationExtended.cs(.vb). In it you will create a new class, which derives from the generated CategoryServiceImplementation. In this class you will override QueryResponse
Get(QueryCategories request) in order to implement the required handling for the navigational property. The new body of the method will use the code from the base implementation with a few additional statements. These statements will build the IList<ProductDTO> Products property for each of the returned categories. For example:public partial class CategoryServiceImplementationExtended : CategoryServiceImplementation, ICategoryServiceImplementation { private ICategoryRepository repository; public CategoryServiceImplementationExtended(ICategoryRepository repository) : base (repository) { this.repository = repository; } public override QueryResponse<CategoryDTO> Get(QueryCategories request) { IDynamicQueryInfo dynamicQuery = new DynamicQueryInfo<ServiceStack.Model.Category>(request); int? totalCategoriesCount = null; List<ServiceStack.Model.Category> categories = this.repository.GetAllDynamic(dynamicQuery, out totalCategoriesCount).ToList(); List<CategoryDTO> responseCategories = new List<CategoryDTO>(); foreach (ServiceStack.Model.Category category in categories) { //Code which handles the Products navigational property List<ProductDTO> products = new List<ProductDTO>(); foreach (ServiceStack.Model.Product product in category.Products) { ProductDTO productDTO = product.ConvertTo<ProductDTO>(); products.Add(productDTO); } CategoryDTO categoryDTO = category.ConvertTo<CategoryDTO>(); categoryDTO.Products = products; responseCategories.Add(categoryDTO); // } QueryResponse<CategoryDTO> response = new QueryResponse<CategoryDTO>(); response.Results = responseCategories; if (totalCategoriesCount.HasValue) { response.Total = totalCategoriesCount.Value; } if (request.Skip.HasValue && request.Skip.Value > 0) { response.Offset = request.Skip.Value; } return response; } }
Imports ServiceStack.Demo.Repositories Imports EntitiesModelServicesDTOs.CategoryServiceMessages Imports ServiceStack.Demo.DynamicQueryUtils Imports EntitiesModelServicesDTOs.CoreDTOs Partial Public Class CategoryServiceImplementationExtended Inherits CategoryServiceImplementation Implements ICategoryServiceImplementation Private repository As ICategoryRepository Public Sub New(ByVal repository As ICategoryRepository) MyBase.New(repository) Me.repository = repository End Sub Public Overrides Function [Get](ByVal request As QueryCategories) As QueryResponse(Of CategoryDTO) Dim dynamicQuery As IDynamicQueryInfo = New DynamicQueryInfo(Of ServiceStack.Model.Category)(request) Dim totalCategoriesCount? As Integer = Nothing Dim categories As List(Of ServiceStack.Model.Category) = Me.repository.GetAllDynamic(dynamicQuery, totalCategoriesCount).ToList() Dim responseCategories As New List(Of CategoryDTO)() For Each category As ServiceStack.Model.Category In categories 'Code which handles the Products navigational property Dim products As New List(Of ProductDTO)() For Each product As ServiceStack.Model.Product In category.Products Dim productDTO_Renamed As ProductDTO = product.ConvertTo(Of ProductDTO)() products.Add(productDTO_Renamed) Next product Dim categoryDTO_Renamed As CategoryDTO = category.ConvertTo(Of CategoryDTO)() categoryDTO_Renamed.Products = products responseCategories.Add(categoryDTO_Renamed) ' Next category Dim response As New QueryResponse(Of CategoryDTO)() response.Results = responseCategories If totalCategoriesCount.HasValue Then response.Total = totalCategoriesCount.Value End If If request.Skip.HasValue AndAlso request.Skip.Value > 0 Then response.Offset = request.Skip.Value End If Return response End Function End Class
-
In the web application that holds your ServiceStack layer, add a new class file called AppHost.partial.cs(.vb). In it you will extend the ServiceStack service host, in order to register the newly created service implementation class. This requires you to implement the ApplyCustomIocContainerServiceImplementationsConfiguration method. For example:
public partial class AppHost : AppHostBase { partial void ApplyCustomIocContainerServiceImplementationsConfiguration(Funq.Container container, ref bool shouldRegisterDefaultServiceImplementations) { shouldRegisterDefaultServiceImplementations = false; container.RegisterAutoWiredAs<CategoryServiceImplementationExtended, ICategoryServiceImplementation>() .ReusedWithin(ReuseScope.None); container.RegisterAutoWiredAs<ProductServiceImplementation, IProductServiceImplementation>() .ReusedWithin(ReuseScope.None); } }
Partial Public Class AppHost Inherits AppHostBase Private Sub ApplyCustomIocContainerServiceImplementationsConfiguration(ByVal container As Funq.Container, _ ByRef shouldRegisterDefaultServiceImplementations As Boolean) shouldRegisterDefaultServiceImplementations = False container.RegisterAutoWiredAs(Of CategoryServiceImplementationExtended, ICategoryServiceImplementation)() _ .ReusedWithin(ReuseScope.None) container.RegisterAutoWiredAs(Of ProductServiceImplementation, IProductServiceImplementation)() _ .ReusedWithin(ReuseScope.None) End Sub End Class
At this point it is important to set shouldRegisterDefaultServiceImplementations to false before registering the new service implementation, and to make sure that the registrations for the rest of the entities in your service layer is placed in this method.
Build your solution and run the application.
- You can test the new method on a URL similar to the following:
http://localhost/Categories?filter=CategoryID+gt+'3'&GroupBy=CategoryID&InlineCount=true
&Include=Products&Skip=1&Take=2&Format=xml