May 14, 2025
ServiceStack v8.8

We're happy to announce that all ServiceStack's ASP .NET Core Identity Auth Tailwind Templates have been upgraded to Tailwind CSS v4.0 - an all-new version optimized for performance and flexibility, with a reimagined configuration and customization experience, and taking full advantage of the latest advancements in the modern web platform.

TailwindCSS v4

Tailwindcss v4 is now the default version used in all throughout ServiceStack and Blazor, Razor Pages, MVC, Vue, React and Angular Templates, including:

Standalone CLI: Use Tailwind CSS without Node.js

Whilst TailwindCSS v4 brings many quality of life improvements, it also posed a challenge for usage in non-Node.js templates which used npx tailwindcss@v3 to maintain simplicity and avoid needing to install any node dependencies.

Unfortunately using npx without node_modules is no longer possible in Tailwind v4, instead we've switched to using the Standalone CLI which allows using Tailwind CSS without Node.js.

tailwindcss is a single executable that can be added anywhere in your System PATH, to simplify the process we've added support for detecting, downloading and installing the tailwindcss executable if missing in each templates postinstall.js which can be run with:

npm install

In non-Node.js templates this doesn't install any node dependencies, instead it's used to run one-off tasks like running DB Migrations to create the App Database, update any local 3rd Party dependencies and now downloading and installing the tailwindcss executable if missing on Windows, macOS and Linux operating systems, which can also be rerun at anytime with:

node postinstall.js

Blazor Templates

The latest TailwindCSS v4 Standalone CLI is now used in all ServiceStack's Blazor Templates:

Blazor
Blazor WASM
Blazor Vue

Razor Pages & MVC

The TailwindCSS v4 Standalone CLI is also in Razor Pages and MVC Templates:

Razor Pages
MVC
  • razor - Identity Auth & Entity Framework Razor Pages Template
  • mvc - Identity Auth & Entity Framework MVC Template

React, Vue & Angular SPA Templates

All React, Vue and Angular Single Page App templates have been upgraded to Vite 6 and use Tailwind v4's First-party Vite plugin for seamless integration and even greater incremental live reload performance.

Vue SPA
React SPA
Angular SPA
  • vue-spa - Identity Auth, Vite 6 Vue 3.5 SPA Template
  • react-spa - Identity Auth, Vite 6 React 19 SPA Template
  • angular-spa - Identity Auth, Angular 19 SPA Template

Razor & Markdown Statically Generated Templates

The Razor SSG template is recommended for creating any statically generated websites with Razor like Blogs, Portfolios & Marketing sites like servicestack.net which can be hosted for FREE on GitHub Pages CDN or Cloudflare Pages.

Whilst Razor Press is optimized for developing and maintaining documentation-centric websites like docs.servicestack.net:

Razor SSG
Razor Press

React and Vue Statically Generated Templates

For those preferring a pure Vite and Node.js stack we've also upgraded our press-vue.servicestack.net and press-react.servicestack.net website templates to Vite 6, latest Vue and React and TailwindCSS v4:

Vite Vue
Vite React
  • press-vue - Statically generated Vite 6 Vue 3.5 SPA Template
  • press-react - Statically generated Vite 6 React 19 SPA Template

Universal Markdown Features

These templates implement the Vite Press Plugin which enables access to a suite of universal markdown-powered features that can be reused across Vue, React and .NET Razor and Blazor projects, allowing you to incorporate same set of markdown feature folders to power markdown content features across a range of websites built with different technologies.

AutoBatch Requests now supported in Endpoint Routing

The Auto Batched Requests feature is now available in the new Endpoint Routing where implicit hidden routes are registered per ServiceStack API allowing you to batch multiple requests into a single request:

var client = new JsonApiClient(BaseUrl);
var requests = new[]
{
    new Request { Id = 1, Name = "Foo" },
    new Request { Id = 2, Name = "Bar" },
    new Request { Id = 3, Name = "Baz" },
};

List<Response> responses = client.SendAll(requests);

Async Command Timeouts in ServiceStack.Redis

In addition to configuring the SendTimeout/ReceiveTimeout on the connected TCP Socket async commands are also given a LinkedTokenSource to cancel pending Async requests with the configured timeout.

ServiceStack.Redis can be configured on the RedisClient instance itself or globally on the RedisConfig class or via the Redis Connection String:

var sendTimeout = 60 * 1000; // 1 minute
var recvTimeout = 60 * 1000; // 1 minute

// Using RedisConfig
RedisConfig.SendTimeout = sendTimeout;
RedisConfig.ReceiveTimeout = recvTimeout;

// Using Connection String
var connString = $"redis://{Host}?SendTimeout={sendTimeout}&ReceiveTimeout={recvTimeout}";
var redisManager = new RedisManagerPool(connString);

AsObject JsonElement extension method

To simplify lazy parsing a JSON document with System.Text.Json we've added the JsonElement.AsObject() extension method which will convert a JsonElement into a .NET dynamic data structure with generic Dictionary<string,object> collections for objects, List<object> for arrays and .NET primitive type for other JsonValueKind values.

You can use this to convert either just a fragment or an entire JSON data structure with:

var doc = System.Text.Json.JsonDocument.Parse(json);
var obj = doc.RootElement.AsObject();
obj.PrintDump();

Which will let you traverse a JSON data structure with a more idiomatic and familiar C# pattern matching style:

if (obj is Dictionary<string, object> dict)
{
    if (dict.GetValueOrDefault("id") is int id)
    {
        //...
    }
    if (dict.GetValueOrDefault("name") is string name)
    {
        //...
    }
    // Use ConvertTo<T>() extension method to convert to any .NET type, e.g:
    var seed = dict.GetValueOrDefault("seed").ConvertTo<long>();
}

The AsObject extension method essentially has the same behavior as JavaScript Utils which is preferable if you never need to access the underlying JsonElement:

var obj = JSON.parse(json);
obj.PrintDump();

Custom Metadata Examples

You can now customize the Example Objects used in the metadata pages with the new CreateExampleObjectFn property on the MetadataFeature plugin which you can customize like:

services.ConfigurePlugin<MetadataFeature>(feature => {
    feature.CreateExampleObjectFn = type => {
        if (type == typeof(CreateJob))
        {
            return new CreateJob {
                Title = "Example Job",
                Company = "Acme",
                Description = "Job Description",
                SalaryRangeLower = 50_000,
                SalaryRangeUpper = 100_000,
            };
        }
        if (type == typeof(Job))
        {
            return new Job {
                Id = 1,
                Description = "Job Description",
                Company = "Acme",
                SalaryRangeLower = 50_000,
                SalaryRangeUpper = 100_000,
                CreatedBy = "Admin",
                CreatedDate = DateTime.UtcNow.Date,
            };
        }
        return null; // default behavior
    };
});

This will use these example objects in the metadata pages instead of the default auto-generated examples.