Code Pages

Another solution that enables greater control and functionality are Code Pages which utilize the full unlimited power of the C# programming language to enable precise control over the rendering of pages and partials. As Code Pages are normal C# classes they also benefit from the rich editing and debugging experience in C# IDEs and don't require any special design-time editors, build tools or pre-compilation like Razor as they're just regular classes in your Solution.

Creating Code Pages

A Code Page is a class that inherits TemplateCodePage and is annotated with a [Page(virtualPath)] attribute which is used to resolve the page in the same way .html pages are resolved by their virtualPath. Code Pages take precedence over .html pages so you can start with an .html page and when you need more power and expressiveness, create a Code Page at the same virtualPath as the page you want to replace and it will get used instead. They also follow the same layout resolution rules as normal pages so they can be easily substituted when needed.

Code Pages can also have metadata using the [PageArg] attribute which have the same behavior as args in .html pages.

render Method

The last thing a Code Page needs is a render method which is what gets called to execute the page. The render method can have any number of parameters which will be populated with arguments of the same name that's in scope at the time when the Code Page is called. This enables the nice UX where the arguments can be utilized in a C# interpolated string.

A complete CodePage implementation using all these features is below:

ProductsPage.cs

using System.Linq;
using System.Collections.Generic;
using ServiceStack;
using ServiceStack.Templates;

namespace TemplatePages
{
    [Page("products")]
    [PageArg("title", "Products")]
    public class ProductsPage : TemplateCodePage
    {
        string render(Product[] products) => $@"
        <table class='table table-striped'>
            <thead>
                <tr>
                    <th></th>
                    <th>Name</th>
                    <th>Price</th>
                </tr>
            </thead>
            {products.OrderBy(x => x.Category).ThenBy(x => x.ProductName).Map(x => $@"
                <tr>
                    <th>{x.Category}</th>
                    <td>{x.ProductName.HtmlEncode()}</td>
                    <td>{x.UnitPrice:C}</td>
                </tr>").Join("")}
        </table>";
    }
}

Where with just this implementation, it can now be called by navigating to its virtualPath:

/products

You typically wont need to explicitly register Code Pages since the TemplatePagesFeature populates its ScanAssemblies with the AppHost's Service assemblies which will automatically find and register any TemplateCodePage's that are in the same Assembly as ServiceStack Services and just like Services, Code Pages are autowired and resolved from the IOC transiently, so they can also be injected with any IOC registered dependencies by declaring them as public properties.

MVC Code Pages

You can also combine the functionality enabled by levaraging ServiceStack Services in MVC Pages with the precise layout and expressiveness of Code Pages, where just like you can render a Page in a PageResult, you can render a CodePage by resolving it with its virtualPath:

ProductServices.cs

using ServiceStack;
using ServiceStack.Templates;

namespace TemplatePages
{
    [Route("/products/view")]
    public class ViewProducts
    {
        public string Id { get; set; }
    }

    public class ProductsServices : Service
    {
        public object Any(ViewProducts request) =>
            new PageResult(Request.GetCodePage("products")) {
                Args = {
                    ["products"] = TemplateQueryData.Products
                }
            };
    }
}

Which is viewable at the Services Route:

/products/view

Which displays exactly the same content as calling ProductsPage directly, but instead uses the Routing and the argument populated by the ServiceStack Service, instead of the argument populated in the TemplatePagesFeature Arguments.

Using the Request.GetCodePage() extension method is the recommended way to resolve Code Pages as it will automatically inject the current IRequest on Code Pages that implement IRequiresRequest like ServiceStackCodePage's. It's the same as resolving the Code Page from the ITemplatePages dependency and injecting the IRequest manually:

public ITemplatePages Pages { get; set; }

public object Any(ViewProducts request) 
{
    var codePage = Pages.GetCodePage("products");
    (codePage as IRequiresRequest)?.Request = Request;
    return new PageResult(codePage) { ... };
}

Code Pages as Partials

Another area Code Pages are useful are as partials where they're able to escape the normal sandbox of Template Pages and and use C# to access the existing functionality available within your Web App.

Partial Arguments

You can use an Object literal to pass arguments to Code Page partials (i.e. same as page partials), where each property creates a scoped argument that the Code Page has access to.

We'll show an example of this by building a simple navLinks Code Page partial where we pass in the links we want generated when embed the partial:

{{ 'navLinks' | partial(
    { 
        links: { 
            '/docs/model-view-controller': 'MVC', 
            '/docs/view-engine': 'View Engine', 
            '/docs/code-pages': 'Code Pages'
        } 
    }) 
}}

Which will generate the following links of progressive advanced features, with the link of the current page highlighted:

Using the navLinks implementation below where it can access both the PathInfo PageResult argument and the links argument created when the partial was called:

CodePagePartials.cs

using System.Linq;
using System.Collections.Generic;
using ServiceStack;
using ServiceStack.Templates;

namespace TemplatePages
{
    [Page("navLinks")]
    public class NavLinksPartial : TemplateCodePage
    {
        string render(string PathInfo, Dictionary<string, object> links) => $@"
        <ul>
            {links.Map(entry => $@"<li class='{GetClass(PathInfo, entry.Key)}'>
                <a href='{entry.Key}'>{entry.Value}</a>
            </li>").Join("")}
        </ul>";

        string GetClass(string pathInfo, string url) => url == pathInfo ? "active" : ""; 
    }

    [Page("customerCard")]
    public class CustomerCardPartial : TemplateCodePage
    {
        public ICustomers Customers { get; set; }

        string render(string customerId) => renderCustomer(Customers.GetCustomer(customerId));

        string renderCustomer(Customer customer) => $@"
        <table class='table table-bordered'>
            <caption>{customer.CompanyName}</caption>
            <thead class='thead-inverse'>
                <tr>
                    <th>Address</th>
                    <th>Phone</th>
                    <th>Fax</th>
                </tr>
            </thead>
            <tr>
                <td>
                    {customer.Address} 
                    {customer.City}, {customer.PostalCode}, {customer.Country}
                </td>
                <td>{customer.Phone}</td>
                <td>{customer.Fax}</td>
            </tr>
        </table>";
    }
}

The customerCard partial shows another example where Code Pages are useful where they're able to access any IOC dependency and existing Web App functionality, which can be called with:

{{ 'customerCard' | partial({ customerId: "ALFKI" }) }}

To render a nice Customer Card snapshot:

Alfreds Futterkiste
Address Phone Fax
Obere Str. 57 Berlin, 12209, Germany 030-0074321 030-0076545

ServiceStack Code Pages

ServiceStackCodePage is a convenience base class that inherits TemplateCodePage and enhances it with the same functionality that's available in ServiceStack's Service base class, e.g: access to the current Request context, Gateway, Database, Redis, CacheClient, Users Session, AuthRepository, etc.

So you could rewrite the previous Code Page Partials to inherit ServiceStackCodePage instead where NavLinksPartial can access the IRequest directly without having to request the PathInfo argument and CustomerCardPartial has direct access to the configured database:

using System.Linq;
using System.Collections.Generic;
using ServiceStack;
using ServiceStack.Templates;

namespace TemplatePages
{
    [Page("navLinks")]
    public class NavLinksPartial : ServiceStackCodePage
    {
        string render(Dictionary<string, object> links) => $@"
        <ul>
            {links.Map(entry => $@"<li class='{GetClass(entry.Key)}'>
                <a href='{entry.Key}'>{entry.Value}</a>
            </li>").Join("")}
        </ul>";

        string GetClass(string url) => url == Request.PathInfo ? "active" : ""; 
    }

    [Page("customerCard")]
    public class CustomerCardPartial : ServiceStackCodePage
    {
        string render(string customerId) => renderCustomer(Db.SingleById<Customer>(customerId));

        string renderCustomer(Customer customer) => $@"
        <table class='table table-bordered'>
            <caption>{customer.CompanyName}</caption>
            <thead class='thead-inverse'>
                <tr>
                    <th>Address</th>
                    <th>Phone</th>
                    <th>Fax</th>
                </tr>
            </thead>
            <tr>
                <td>
                    {customer.Address} 
                    {customer.City}, {customer.PostalCode}, {customer.Country}
                </td>
                <td>{customer.Phone}</td>
                <td>{customer.Fax}</td>
            </tr>
        </table>";
    }
}

Since they have access to the same functionality ServiceStack Services do, ServiceStackCodePage's can provide an alternative to using Services in Model View Controller.

made with by ServiceStack