API Pages

When not being used as a templating language to render text to a stream, ServiceStack Templates can be used as a high-level wrist-friendly dynamic language that provides a glue to your App's .NET APIs that lets you easily compose C# methods and evaluate binding expressions.

We can also take advantage of its real-time feedback and optimal development experience to also rapidly develop high-performance Web APIs which like Web Pages can be developed whilst the Web App is running without any pre-compilation enabling the fastest way to develop Web APIs in .NET.

To enable this we need to specify the path where our "API Pages" are located when registering TemplatePagesFeature:

Plugins.Add(new TemplatePagesFeature 
    ApiPath = "/api"

All pages within the /api folder are then treated as Web APIs where instead of writing their response to the response stream, their return value is serialized in the requested Content-Type.

You can return a result in Templates with the return filter:

{{ response | return }}

This will immediately terminate evaluation of the page and store the response in the PageResult the Page was rendered using. The return value is then used as the response for the Web API.

API Page Examples

To demonstrate API Pages in action we've added Web APIs equivalents for Rockwind's customers and products HTML pages with the implementation below:


The entire implementation of the customers API is below:

{{ limit ?? 100 | assignTo: limit }}

{{ 'select Id, CompanyName, ContactName, ContactTitle, City, Country from Customer' | assignTo: sql }}

{{#if !isEmpty(PathArgs)}}
   {{ `${sql} where Id = @id` | dbSingle({ id: PathArgs[0] }) 
      | return }}

{{#if id}}      {{ 'Id = @id'           | addTo: filters }} {{/if}}
{{#if city}}    {{ 'City = @city'       | addTo: filters }} {{/if}}
{{#if country}} {{ 'Country = @country' | addTo: filters }} {{/if}}

{{#if !isEmpty(filters)}}
  {{ `${sql} WHERE ${join(filters, ' AND ')}` | assignTo: sql }}

{{ `${sql} ORDER BY CompanyName ${sqlLimit(limit)}` | assignTo: sql }}

{{ sql | dbSelect({ id, city, country }) 
       | return }}

These are some of the API's that are made available with the above implementation:

/customers API
All Customers
Accept HTTP Header also supported
Alfreds Futterkiste Details
As List
Customers in Germany
Customers in London
Combination Query /api/customers?city=London&country=UK&limit=3


The Products API is an example of a more complex API where data is sourced from multiple tables:

{{ limit ?? 100 | assignTo: limit }}

{{ `select p.Id, 
           c.CategoryName Category,
           s.CompanyName Supplier, 
           ${sqlCurrency("UnitPrice")} UnitPrice, 
           UnitsInStock, UnitsOnOrder, ReorderLevel   
      from Product p
           inner join Category c on p.CategoryId = c.Id
           inner join Supplier s on p.SupplierId = s.Id
     where Discontinued = 0`
  | assignTo: sql }}

{{#if !isEmpty(PathArgs)}}
  {{ `${sql} and p.Id = @id` | dbSingle({ id: PathArgs[0] }) 
     | return }}

{{#if id}}           {{ 'p.Id = @id'                 | addTo: filters }} {{/if}}
{{#if category}}     {{ 'c.CategoryName = @category' | addTo: filters }} {{/if}}
{{#if supplier}}     {{ 's.CompanyName = @supplier'  | addTo: filters }} {{/if}}
{{#if nameContains}} {{ 'ProductName LIKE @name'     | addTo: filters }} {{/if}}

{{#if !isEmpty(filters)}}
  {{ `${sql} and ${join(filters, ' and ')}` | assignTo: sql }}

{{ `${sql} ORDER BY CompanyName ${sqlLimit(limit)}` | assignTo: sql }}

{{ sql | dbSelect({ id, category, supplier, name: `%${nameContains}%` }) 
       | return }}

Some API examples using the above implementation:

/products API
All Products
Chai Product Details
As List
Beverage Products
Bigfoot Breweries Products
Products containing Tofu

Untyped APIs

As these APIs don't have a Typed Schema they don't benefit from any of ServiceStack's metadata Services, i.e. they're not listed in Metadata pages, included in Open API or have Typed APIs generated using Add ServiceStack Reference.

made with by ServiceStack