Web Apps

Web Apps are a new approach to dramatically simplify .NET Wep App development and provide the most productive development experience possible whilst maximizing reuse and component sharing. They also open up a number of new use-cases for maintaining clean isolation between front-end and back-end development with front-end developers not needing any knowledge of C#/.NET to be able to develop UIs for high-performance .NET Web Apps. Web Apps also make it easy to establish and share an approved suite of functionality amongst multiple websites all consuming the same back-end systems and data stores.

Web Apps leverages Templates to develop entire content-rich, data-driven websites without needing to write any C#, compile projects or manually refresh pages - resulting in the easiest and fastest way to develop Web Apps in .NET!

Ultimate Simplicity

Not having to write any C# code or perform any app builds dramatically reduces the cognitive overhead and conceptual knowledge required for development where the only thing front-end Web developers need to know is Template's syntax and what filters are available to call. Because of Template's high-fidelity with JavaScript, developing a Website with Templates will be instantly familiar to JavaScript devs despite calling and binding directly to .NET APIs behind the scenes.

All complexity with C#, .NET, namespaces, references, .dlls, strong naming, packages, MVC, Razor, build tools, IDE environments, etc has been eliminated leaving all Web Developers needing to do is run a cross-platform web/app.dll .NET Core 2.0 executable and configure a simple web.settings text file to specify which website folder to use, which ServiceStack features to enable, which db or redis providers to connect to, etc. Not needing to build also greatly simplifies deployments where multiple websites can be deployed with a single rsync or xcopy command or if deploying your App in a Docker Container, you just need to copy your website files, or just the web.settings if you're using an S3 or Azure Virtual File System.

Rapid Development Workflow

The iterative development experience is also unparalleled for a .NET App, no compilation is required so you can just leave the web/app.dll running whilst you add the template .html files needed to build your App and thanks to the built-in Hot Reloading support, pages will refresh automatically as you save. You'll just need to do a full page refresh when modifying external .css/.js files to bypass the browser cache and you'll need to restart web/app.dll to pick up any changes to your web.settings or added any .dlls to your /plugins folder.

Getting Started

The easiest way to get started to either clone the WebAppStarter GitHub project:

git clone git@github.com:ServiceStack/WebAppStarter.git

Or download a zip of it from github.com/ServiceStack/WebAppStarter/archive/master.zip.

This contains 2 folders, /app contains all source code and assets for your Web App and /web contains a copy of the WebApp binaries from ServiceStack/WebApp. There's also start-app.bat and start-app.sh scripts for running your app in Windows, OSX and Linux. Both scripts run the same command:

dotnet web/app.dll ../app/web.settings

Which can be run instead of the script, it also means cat start-app.bat | sh will also run the app on OSX and Linux.

Once running you can view your App on http://localhost:5000/ which you can keep running whilst developing your app which will reload the page you're currently viewing on each file save using the built-in Hot Reloading.

/app is an example of a single Web App, you can have multiple Web Apps in different folders and run any of them with:

dotnet web/app.dll ../<app name>/web.settings

Cloud Apps Starter Projects

If you intend to deploy your Web App on AWS or Azure you may prefer to start with one of the example Cloud Apps below which come pre-configured with deployment scripts for deploying with Travis CI and Docker:

Example Web Apps

We've developed a number of Web Apps to illustrate the various features available and to showcase the different kind of Web Apps that can easily be developed. The source code for each app is available either individually from [github.com/NetCoreWebApps](https://github.com/NetCoreWebApps) or all combined in the same repo at NetCoreWebApps/WebApp/apps. Each app runs the same unmodified [Web App Binary](https://github.com/NetCoreWebApps/Web) that's also used in the WebAppStarter project above.

Running Web Apps locally

Each of these apps can be run locally by cloning NetCoreWebApps/WebApp and running the start.bat scripts in the /apps folder. If you want to use your local RDBMS you can use /support/northwind-data to quickly populate it with the Northwind database and /support/copy-files to populate your own S3 Bucket or Azure Blob Container with the /rockwind-vfs Web App's files. As each app runs on http://localhost:5000 you'll need to do a hard refresh with Ctrl+Shift+F5 after launching each App to tell the browser to ignore the cached .css and .js from the previous App.

Web App Starter

The Getting Started project contains a copy of the bare.web-app.io project below which is representative of a typical Company splash Website:

Bare WebApp screenshot

The benefits over using a static website is improved maintenance as you can extract and use its common _layout.html instead of having it duplicated in each page. The menu.html partial also makes menu items easier to maintain by just adding an entry in the JavaScript object literal. The dynamic menu also takes care of highlighting the active menu item.

{{ { '/':         'Home',
     '/about':    'About',
     '/services': 'Services',
     '/contact':  'Contact',
   } | toList | assignTo: links }}

Ideal for Web Designers and Content Authors

The other primary benefit is that this is an example of a website that can be maintained by employees who don't have any programming experience as Templates in their basic form are intuitive and approachable to non-developers, e.g: The title of each page is maintained as metadata HTML comments:

<!--
title: About Us
-->

Template's syntax is also the ideal way to convey variable substitution, e.g: <title>{{ title }}</title> and even embedding a partial reads like english {{ 'menu' | partial }} which is both intuitive and works well with GUI HTML designers.

web.settings

Below is the web.settings for a Basic App, with contentRoot being the only setting required as the rest can be inferred but including the other relevant settings is both more descriptive to other developers as well making it easier to use tools like sed or powershell to replace them during deployment.

debug true
name Bare Web App
port 5000
contentRoot ~/../bare
webRoot ~/../bare
debug true controls the level of internal diagnostics available and whether or not Hot Reloading is enabled.

Redis HTML

For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated Web App feels like with the performance of .NET Core 2.0 and Templates. We're pleasantly surprised with the result as when the App is run locally the responsiveness is effectively indistinguishable from an Ajax App. When hosted on the Internet there is a sub-second delay which causes a noticeable flicker but it still retains a pleasant UX that's faster than most websites.

The benefits of a traditional website is that it doesn't break the web where the back button and deep linking work without effort and you get to avoid the complexity train of adopting a premier JavaScript SPA Framework's configuration, dependencies, workflow and build system which has become overkill for small projects.

Redis HTML WebApp Screenshot

We've had a sordid history developing Redis UI's which we're built using the popular JavaScript frameworks that appeared dominant at the time but have since seen their ecosystem decline, starting with the Redis Admin UI (src) built using Google's Closure Library that as it works different to everything else needed a complete rewrite when creating redisreact.servicestack.net (src) using the hot new React framework, unfortunately it uses React's old deprecated ES5 syntax and Reflux which is sufficiently different from our current recommended TypeScript + React + Redux + WebPack JavaScript SPA Stack, that is going to require a significant refactor to adopt our preferred SPA tech stack.

Beautiful, succinct, declarative code

The nice thing about generating HTML is that it's the one true constant in Web development that will always be there. The entire functionality for the Redis Web App is contained in a single /RedisHtml/app/index.html which includes all Template and JavaScript Source Code in < 200 lines which also includes all as server logic as it doesn't rely on any back-end Services and just uses the Redis Filters to interface with Redis directly. The source code also serves as a good demonstration of the declarative coding style that Templates encourages that in addition to being highly-readable requires orders of magnitude less code than our previous Redis JavaScript SPA's with a comparable feature-set.

Having a much smaller code-base makes it much easier to maintain and enhance whilst being less susceptible to becoming obsolete by the next new JavaScript framework as it would only require rewriting 75 lines of JavaScript instead of the complete rewrite that would be required to convert the existing JavaScript Apps to a use different JavaScript fx.

web.settings

The web.settings for Redis is similar to Web App Starter above except it adds a redis.connection to configure a RedisManagerPool at the connection string provided as well as Redis Filters to give Templates access to the Redis instance.

debug true
name Redis Web App
port 5000
contentRoot ~/../redis-html
webRoot ~/../redis-html
redis.connection localhost:6379

Redis Vue

Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using Templates, we've also rewritten the Redis UI as a Single Page App which is the more suitable choice for an App like this as it provides a more optimal and responsive UX by only loading the HTML page once on Startup then utilizes Ajax to download and dynamically update incremental parts of the App's UI that needs changing.

Instead of using jQuery and server-side HTML this version has been rewritten to use Vue where the UI has been extracted into isolated Vue components utilizing Vue X-Templates to render the App on the client where all Redis Vue's functionality is contained within the Redis/app/index.html page.

Redis Vue WebApp Screenshot

Simple Vue App

Templates also provides a great development experience for Single Page Apps which for the most part gets out of your way letting you develop the Single Page App as if it were a static .html file, but also benefits from the flexibility of a dynamic web page when needed.

The containing _layout.html page can be separated from the index.html page that contains the App's functionality, where it's able to extract the title of the page and embed it in the HTML <head/> as well as embed the page's <script /> in its optimal location at the bottom of the HTML <body/>, after the page's blocking script dependencies:

<html>
<head>
<title>{{ title | default('Redis Vue WebApp') }}</title>
{{ ifDebug | select: <script>{ '/js/hot-loader.js' | includeFile }</script> }}

...
<link rel="stylesheet" href="../assets/css/bootstrap.css">
<link rel="stylesheet" href="../assets/css/default.css">
</head>
<body>
    <h2 id="title"><a href="/"><img src="/assets/img/redis-logo.png" /> {{ title }}</a></h2>

    {{ page }}

    <script src="../assets/js/html-utils.js"></script>
    <script src="../assets/js/vue{{ '.min' | if(!debug) }}.js"></script>
    <script src="../assets/js/axios.min.js"></script>
    
    {{ scripts | raw }} 
</body>
</html>

Redis Vue avoids the complexity of adopting a npm build system by refrencing Vue libraries as a simple script include:

<script src="../assets/js/vue{{ '.min' | if(!debug) }}.js">

Where it uses the more verbose and developer-friendly vue.js during development whilst using the production optimized vue.min.js for deployments. So despite avoiding the complexity tax of an npm-based build system it still gets some of its benefits like conditional deployments and effortless hot reloading.

Server Templates

Whilst most of index.html is a static Vue app, templates is leveraged to generate the body of the <redis-info/> Component on the initial home page render:

<script type="text/x-template" id="redis-info-template">
<div id="redis-info">
    <table class="table table-striped" style="width:450px">
        <tbody>
{{ redisInfo | toList 
   | select: <tr><th>{it.Key}</th><td title="{it.Value}">{ it.Value | substringWithElipsis(32) }</td></tr> }}
        </tbody>
    </table>
</div>
</script>

This technique increases the time to first paint by being able to render the initial Vue page without waiting for an Ajax call response whilst benefiting from improved SEO from server-generated HTML.

Server Handling

Another area Templates is used is to handle the HTTP POST where it calls the redisChangeConnection filter to change the current Redis connection before rendering the connection-info.html partial with the current connection info:

{{ ifHttpPost | use({ host, port, db, password }) | withoutEmptyValues | redisChangeConnection | end }}
<script type="text/x-template" id="connection-template">
{{ 'connection-info' | partial({ redis: redisConnection }) }}
</script>

Vue Ajax Server APIs

All other Server functionality is invoked by Vue using Ajax to call one of the Ajax APIs below implemented as API Pages:

search.html

Called when searching for Redis keys where the query is forwarded to the redisSearchKeys filter:

{{ limit | default(100) | assignTo: limit }}
{{ q     | default('')  | assignTo: q }}

{{ q     | append("*") | redisSearchKeys({ limit }) 
         | return }}
call.html

Called to execute an arbitrary Redis command on the connected instance, with the response from Redis is returned as a plain-text HTTP Response:

{{ { command } | ensureAllArgsNotEmpty }}

{{ ['flush','monitor','brpop','blpop'] | any: contains(lower(command), it)
   | assignTo: illegalCommand }}

{{ illegalCommand | ifThrowArgumentException('Command is not allowed.') }}

{{ command | redisCall | assignTo: json }}

{{ json | return({ 'Content-Type': 'text/plain' }) }}

The benefits of using API Pages instead of a normal C# Service is being able to retain the productive development workflow of Web Apps where the entire Redis Vue App can be built without any compilation.

Deep linking and full page reloads

The Redis Vue Single Page App also takes advantage of HTML5's history.pushState API to enable deep-linking and back-button support where most UI state changes is captured on the query string and used to initialize the Vue state on full-page reloads to proivde transparent history navigation and back-button support that functions like a traditional Web App but with the performance of a Single Page App.

Rockwind

The Rockwind website shows an example of combining multiple websites in a single Web App - a Rockstars Content Website and a dynamic data-driven UI for the Northwind database which can run against either SQL Server, MySql or SQLite database using just configuration. It also includes API Pages examples for rapidly developing Web APIs.

Rockstars

/rockstars is an example of a Content Website that itself maintains multiple sub sections with their own layouts - /rockstars/alive for living Rockstars and /rockstars/dead for the ones that have died. Each Rockstar maintains their own encapsulated mix of HTML, markdown content and splash image that intuitively uses the closest _layout.html, content.md and splash.jpg from the page they're referenced from. This approach makes it easy to move entire sub sections over by just moving a folder and it will automatically use the relevant layout and partials of its parent.

Rockwind WebApp screenshot

Northwind

/northwind is an example of a dynamic UI for a database containing a form to filter results, multi-nested detail pages and deep-linking for quickly navigating between referenced data. Templates is also a great solution for rapidly developing Web APIs where the /api/customers.html API Page below:

{{ limit | default(100) | assignTo: limit }}

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

{{ PathArgs | endIfEmpty | useFmt('{0} where Id = @id', sql) 
            | dbSingle({ id: PathArgs[0] }) 
            | return }}

{{ id       | endIfEmpty | use('Id = @id')           | addTo: filters }}
{{ city     | endIfEmpty | use('City = @city')       | addTo: filters }}
{{ country  | endIfEmpty | use('Country = @country') | addTo: filters }}
{{ filters  | endIfEmpty | useFmt('{0} where {1}', sql, join(filters, ' and '))     | assignTo: sql }}
{{ sql      | appendFmt(" ORDER BY CompanyName {0}", sqlLimit(limit))               | assignTo: sql }}

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

Is all the code needed to generate the following API endpoints:

/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

Multi platform configurations

In addition to being a .NET Core 2.0 App that runs flawlessly cross-platform on Windows, Linux and OSX, Web Apps can also support multiple RDBMS's and Virtual File Systems using just configuration.

web.sqlite.settings

SQLite uses a file system database letting you bundle your database with your App. So we can share the northwind.sqlite database across multiple Apps, the contentRoot is set to the /apps directory which can only be accessed by your App, whilst the webRoot is configured to use the Web Apps folder that hosts all the publicly accessible files of your App.

debug true
name Rockwind SQLite Web App
port 5000
contentRoot ~/..
webRoot ~/../rockwind
db sqlite
db.connection ~/northwind.sqlite

To run the Rockwind app using the northwind.sqlite database, run the command below on Windows, Linux or OSX:

dotnet web/app.dll ../rockwind/web.sqlite.settings
web.sqlserver.settings

To switch to use the Northwind database in SQL Server we just need to update the configuration to point to a SQL Server database instance. Since the App no longer need access to the northwind.sqlite database, the contentRoot can be reverted back to the Web Apps folder:

debug true
name Rockwind SQL Server Web App
port 5000
contentRoot ~/../rockwind
webRoot ~/../rockwind
db sqlserver
db.connection Server=localhost;Database=northwind;User Id=test;Password=test;

The /support/northwind-data project lets you quickly try out Rockwind against your local RDBMS by populating it with a copy of the Northwind database using the same sqlserver identifier and connection string from the App, e.g:

dotnet run sqlserver "Server=localhost;Database=northwind;User Id=test;Password=test;"
web.mysql.settings

You can run against a MySql database in the same way as SQL Server above but using a MySql db connection string:

debug true
name Rockwind MySql Web App
port 5000
contentRoot ~/../rockwind
webRoot ~/../rockwind
db mysql
db.connection Server=localhost;Database=northwind;UID=root;Password=test
web.azure.settings

The example Azure configuration is also configured to use a different Virtual File System where instead of sourcing Web App files from the filesystem they're sourced from an Azure Blob Container. In this case we're not using any files from the App so we don't need to set a contentRoot or webRoot path. This also means that for deployment we're just deploying the WebApp binary with just this web.settings since both the Web App files and database are sourced remotely.

# Note: values prefixed with '$' are resolved from Environment Variables
debug false
name Azure Blob SQL Server Web App
bind *
port 5000
db sqlserver
db.connection $AZURE_SQL_CONNECTION_STRING
files azure
files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind-fs}

# Reduces a Blob Storage API call, but takes longer for modified pages to appear
checkForModifiedPagesAfterSecs 60
defaultFileCacheExpirySecs     60

The /support/copy-files project lets you run Rockwind against your own Azure Blob Container by populating it with a copy of the /rockwind App's files using the same configuration above:

dotnet run azure "{ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}"

Multi-RDBMS SQL

As Templates is unable to use a Typed ORM like OrmLite to hide the nuances of each database, we need to be a bit more diligent in Templates to use parameterized SQL that works across multiple databases by using the sql* DB Filters to avoid using RDBMS-specific SQL syntax. The /northwind/customer.html contains a good example containing a number of things to watch out for:

{{ id | endIfEmpty | useFmt(
    `select o.Id, 
            {0} Employee, 
            OrderDate, ShipCountry, ShippedDate, 
            {1} Total 
        from {2} o
            inner join
            OrderDetail d on o.Id = d.OrderId
            inner join 
            Employee e on o.EmployeeId = e.Id
        where CustomerId = @id
        group by o.Id, EmployeeId, FirstName, LastName, OrderDate, ShipCountry, ShippedDate`,
            sqlConcat(["e.FirstName", "' '", "e.LastName"]), 
            sqlCurrency("sum((d.Unitprice * d.Quantity) - d.discount)"), 
            sqlQuote("Order"))
    | dbSelect({ id }) | assignTo: orders }}

Use sqlConcat to concatenate strings using the RDBMS-specific SQL for the configured database. Likewise sqlCurrency utilizes RDBMS-specific SQL functions to return monetary values in a currency format, whilst sqlQuote is used for quoting tables named after a reserved word.

Of course if you don't intend on supporting multiple RDBMS's, you can ignore this and use RDBMS-specific syntax.

Rockwind VFS

/rockwind-vfs is a clone of the Rockwind Web App with 3 differences: It uses the resolveAsset filter for each .js, .css and image web asset so that it's able to generate external URLs directly to the S3 Bucket, Azure Blob Container or CDN hosting a copy of your files to both reduce the load on your Web App and maximize the responsiveness to the end user.

To maximize responsiveness when using remote storage, all embedded files utilize caching:

{{ "content.md" | includeFileWithCache | markdown }}

The other difference is that each table and column has been quoted in "double-quotes" so that it works in PostgreSQL which otherwise treats unquoted symbols as lowercase. This version of Rockwind also works with SQL Server and SQLite as they also support "Table" quotes but not MySql which uses `BackTicks` or [SquareBrackets]. It's therefore infeasible to develop Apps that support both PostgreSQL and MySql unless you're willing to use all lowercase, snake_case or the sqlQuote filter for every table and column.

Rockwind VFS WebApp screenshot

resolveAsset

If using a remote file storage like AWS S3 or Azure Blob Storage it's a good idea to use the resolveAsset filter for each external file reference. By default it returns the same path it was called with so it will continue to work locally but then ServiceStack effectively becomes a proxy where it has to call the remote Storage Service for each requested download.

<link rel="stylesheet" href="{{ 'assets/css/bootstrap.css' | resolveAsset }}" />

<img src="{{ 'splash.jpg' | resolveAsset }}" id="splash" alt="Dave Grohl" />

ServiceStack asynchronously writes each file to the Response Stream with the last Last-Modified HTTP Header to enable browser caching so it's still a workable solution but for optimal performance you can specify an args.assetsBase in your web.settings to populate the assetsBase TemplateContext Argument the resolveAsset filter uses to generate an external URL reference to the file on the remote storage service, reducing the load and improving the performance of your App, especially if it's configured to use a CDN.

Pure Cloud Apps

Rockwind.Aws/web.settings

The AWS settings shows an example of this where every external resource rockwind-aws.web-app.io has been replaced with a direct reference to the asset on the S3 bucket:

# Note: values prefixed with '$' are resolved from Environment Variables
debug false
name AWS S3 PostgreSQL Web App
bind *
port 5000
db postgres
db.connection $AWS_RDS_POSTGRES
files s3
files.config {AccessKey:$AWS_ACCESS_KEY,SecretKey:$AWS_SECRET_KEY,Region:us-east-1,Bucket:rockwind}
args.assetsBase http://s3-postgresql.s3-website-us-east-1.amazonaws.com/

# Reduces an S3 API call, but takes longer for modified pages to appear
checkForModifiedPagesAfterSecs 60
defaultFileCacheExpirySecs     60

With all files being sourced from S3 and the App configured to use AWS RDS PostgreSQL, the AWS settings is an example of a Pure Cloud App where the entire App is hosted on managed cloud services that's decoupled from the .NET Core 2.0 binary that runs it that for the most part won't require redeploying the Web App binary unless making configuration changes or upgrading the web/app.dll as any App changes can just be uploaded straight to S3 which changes reflected within the checkForModifiedPagesAfterSecs setting, which tells the Web App how long to wait before checking for file changes whilst defaultFileCacheExpirySecs specifies how long to cache files like content.md for.

DockerFile

Deployments are also greatly simplified as all that's needed is to deploy the WebApp binary and web.settings of your Cloud App, e.g. here's the DockerFile for rockwind-aws.web-app.io - deployed to AWS ECS using the deployment scripts in Rockwind.Aws and following our .NET Core Docker Deployment Guideline:

FROM microsoft/dotnet:2.0-sdk
COPY web /web
ADD https://raw.githubusercontent.com/NetCoreWebApps/Rockwind.Aws/master/app/web.settings /web/web.settings
WORKDIR /web
EXPOSE 5000/tcp
ENV ASPNETCORE_URLS https://*:5000
ENTRYPOINT ["dotnet", "/web/app.dll"]
Rockwind.Azure/web.settings

We can also create Azure Cloud Apps in the same we've done for AWS above, which runs the same /rockwind-vfs Web App but using an Azure hosted SQL Server database and its files hosted on Azure Blob Storage:

# Note: values prefixed with '$' are resolved from Environment Variables
debug false
name Azure Blob SQL Server Web App
bind *
port 5000
db sqlserver
db.connection $AZURE_SQL_CONNECTION_STRING
files azure
files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}
args.assetsBase https://servicestack.blob.core.windows.net/rockwind/

# Reduces an S3 API call, but takes longer for modified pages to appear
checkForModifiedPagesAfterSecs 60
defaultFileCacheExpirySecs     60

Plugins

Up till now the Apps above only have only used functionality built into ServiceStack, to enable even greater functionality but still retain all the benefits of developing Web Apps you can drop .dll with custom functionality into your Web App's /plugins folder. The plugins support in Web Apps is as friction-less as we could make it, there's no configuration to maintain or special interfaces to implement, you're able to drop your existing implementation .dll's as-is into the App's `/plugins` folder.

Plugins allow "no touch" sharing of ServiceStack Plugins, Services, Template Filters Template Code Pages, Validators, etc. contained within .dll's or .exe's dropped in a Web App's /plugins folder which are auto-registered on startup. The source code for all plugins used in this App were built from the .NET Core 2.0 projects in the /example-plugins folder. The plugins.web-app.io Web App below walks through examples of using Custom Filters, Services and Validators:

Plugins WebApp screenshot

Registering ServiceStack Plugins

ServiceStack Plugins can be added to your App by listing it's Type Name in the features config entry in web.settings:

debug true
name Web App Plugins
port 5000
contentRoot ~/../plugins
webRoot ~/../plugins
features CustomPlugin, OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature
CustomPlugin { ShowProcessLinks: true }
ValidationFeature { ScanAppHostAssemblies: true }

All plugins listed in features will be added to your Web App's AppHost in the order they're specified. They can further customized by adding a separate config entry with the Plugin Name and a JavaScript Object literal to populate the Plugin at registration, e.g the config above is equivalent to:

Plugins.Add(new CustomPlugin { ShowProcessLinks = true });
Plugins.Add(new OpenApiFeature());
Plugins.Add(new PostmanFeature());
Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature { ScanAppHostAssemblies = true });

Custom Plugin

In this case it tells our CustomPlugin from /plugins/ServerInfo.dll to also show Process Links in its /metadata Page:

public class CustomPlugin : IPlugin
{
    public bool ShowDrivesLinks { get; set; } = true;
    
    public bool ShowProcessLinks { get; set; }

    public void Register(IAppHost appHost)
    {
        if (ShowDrivesLinks)
        {
            var diskFormat = Env.IsWindows ? "NTFS" : "ext2";
            appHost.GetPlugin<MetadataFeature>()
                .AddPluginLink("/drives", "All Disks")
                .AddPluginLink($"/drives?DriveFormatIn={diskFormat}", $"{diskFormat} Disks");
        }

        if (ShowProcessLinks)
        {
            appHost.GetPlugin<MetadataFeature>()
                .AddPluginLink("/processes", "All Processes")
                .AddPluginLink("/process/current", "Current Process");
        }
    }
}

Where as it was first registered in the list will appear before any links registered by other plugins:

Metadata screenshot

Built-in Plugins

It also tells the ValidationFeature to scan all Service Assemblies for Validators and to automatically register them which is how ServiceStack was able to find the ContactValidator used to validate the StoreContact request.

Other optional plugins registered in this Web App is the metadata Services required for Open API, Postman as well as support for CORS. You can check the /metadata/debug Template for all Plugins loaded in your AppHost.

ServiceStack Ecosystem

All Services loaded by plugins continue to benefit from ServiceStack's rich metadata services, including being listed in the /metadata page, being able to explore and interact with Services using /swagger-ui/ as well as being able to generate Typed APIs for the most popular Mobile, Web and Desktop platforms.

Chat

/chat is an example of the ultimate form of extensibility where instead of just being able to add Services, Filters and Plugins, etc. You can add your entire AppHost which Web Apps will use instead of its own. This vastly expands the use-cases that can be built with Web Apps as it gives you complete fine-grained control over how your App is configured.

Chat WebApp screenshot

Develop back-end using .NET IDE's

For chat.web-app.io we've taken a copy of the existing .NET Core 2.0 Chat App and moved its C# code to /example-plugins/Chat and its files to /apps/chat where it can be developed like any other Web App except it utilizes the Chat AppHost and implementation in the SelfHost Chat App.

Customizations from the original .NET Core Chat implementation includes removing MVC and Razor dependencies and configuration, extracting its _layout.html and converting index.html to use Templates from its original default.cshtml. It's also been enhanced with the ability to evaluate Templates from the Chat window, as seen in the screenshot above.

Chat AppHost

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
        var appSettings = new TextFileSettings("~/../../apps/chat/web.settings".MapProjectPath());
        app.UseServiceStack(new AppHost(appSettings));
    }
}

public class AppHost : AppHostBase
{
    public AppHost() : base("Chat Web App", typeof(ServerEventsServices).GetAssembly()) {}
    public AppHost(IAppSettings appSettings) : this() => AppSettings = appSettings;

    public override void Configure(Container container)
    {
        Plugins.AddIfNotExists(new TemplatePagesFeature()); //Already added if it's running as a Web App
        
        Plugins.Add(new ServerEventsFeature());

        SetConfig(new HostConfig
        {
            DefaultContentType = MimeTypes.Json,
            AllowSessionIdsInHttpParams = true,
        });

        this.CustomErrorHttpHandlers.Remove(HttpStatusCode.Forbidden);

        //Register all Authentication methods you want to enable for this web app.            
        Plugins.Add(new AuthFeature(
            () => new AuthUserSession(),
            new IAuthProvider[] {
                new TwitterAuthProvider(AppSettings),   //Sign-in with Twitter
                new FacebookAuthProvider(AppSettings),  //Sign-in with Facebook
                new GithubAuthProvider(AppSettings),    //Sign-in with GitHub
            }));

        container.RegisterAutoWiredAs<MemoryChatHistory, IChatHistory>();

        Plugins.Add(new CorsFeature(
            allowOriginWhitelist: new[] { "http://localhost", "http://null.jsbin.com" },
            allowCredentials: true,
            allowedHeaders: "Content-Type, Allow, Authorization"));
    }
}
Reusing Web App's web.setting and files

One nice thing from being able to reuse existing AppHost's is being able to develop all back-end C# Services and Custom Filters as a stand-alone .NET Core Project where it's more productive with access to .NET IDE tooling and debugging.

To account for these 2 modes we use AddIfNotExists to only register the TemplatePagesFeature plugin when running as a stand-alone App and add an additional constructor so it reuses the existing web.settings as its IAppSettings provider for is custom App configuration like OAuth App keys required for enabling Sign-In's via with Twitter, Facebook and GitHub when running on http://localhost:5000:

debug true
name Chat Web App
port 5000
contentRoot ~/../chat
webRoot ~/../chat

oauth.RedirectUrl http://localhost:5000/
oauth.CallbackUrl http://localhost:5000/auth/{0}
oauth.twitter.ConsumerKey JvWZokH73rdghDdCFCFkJtCEU
oauth.twitter.ConsumerSecret WNeOT6YalxXDR4iWZjc4jVjFaydoDcY8jgRrGc5FVLjsVlY2Y8
oauth.facebook.Permissions email
oauth.facebook.AppId 447523305684110
oauth.facebook.AppSecret 7d8a16d5c7cbbfab4b49fd51183c93a0
oauth.github.Scopes user
oauth.github.ClientId dbe8c242e3d1099f4558
oauth.github.ClientSecret 42c8db8d0ca72a0ef202e0c197d1377670c990f4

After the back-end has been implemented we can build and copy the compiled Chat.dll into the Chat's /plugins folder where we can take advantage of the improved development experience for rapidly developing its UI.

made with by ServiceStack