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:
- Vue Component Library
- Blazor Components
- All of ServiceStack's Built-in Auto UIs
- Build and deployment GitHub Actions
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 - Identity Auth Blazor Server Template
- Blazor WASM - Identity Auth Blazor WASM and Interactive Auto Template
- Blazor Vue - No compromises, Identity Auth statically-rendered Blazor
Razor Pages & MVC​
The TailwindCSS v4 Standalone CLI is also in Razor Pages and MVC Templates:
- 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 - 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:
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:
- 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.