Serenity 10.4.0 Release Notes

These release notes detail the significant changes made in Serenity and StartSharp from version 10.3.7 to 10.4.0. For a complete list of changes, please refer to the Serenity Change Log.

Connection Key Fallbacks

Feature modules (Pro.Extensions, Meeting, WorkLog, etc.) traditionally required their own connection keys (e.g. ProMeeting, ProFeatures) to be explicitly configured in appsettings.json. If a key was missing, the feature failed at runtime even though a perfectly good Default connection existed.

Serenity now supports connection key fallbacks via the new IConnectionKeyFallbacks interface. DefaultConnectionStrings and DefaultSqlConnections implement it, and the fallback map is driven by assembly-level ConnectionKeyFallback attributes, so feature modules can declare their own logical connection key while still working in applications that only configure a fallback key:

// In a feature module (e.g. pro-features/src/pro.extensions):
[assembly: ConnectionKeyFallback(ProFeaturesConnectionKeys.ProFeatures, "Default")]

// In the Meeting module:
[assembly: ConnectionKeyFallback(MeetingConnectionKeys.ProMeeting,
    ProFeaturesConnectionKeys.ProFeatures)]

This defines a fallback chain like ProMeeting → ProFeatures → Default. When ProMeeting is not present in configuration, the next key in the chain that is configured is used.

A fallback can also be declared via configuration with Data:(ConnectionKey):FallbackFor, which accepts a semicolon separated list of connection keys. Config fallbacks override assembly attributes:

"Data": {
  "Default": {
    "ConnectionString": "...",
    "ProviderName": "System.Data.SqlClient",
    "FallbackFor": "ProFeatures;ProMeeting;ProWorkLog"
  }
}

The interface provides both directions of resolution:

public interface IConnectionKeyFallbacks
{
    // Ordered chain: the key itself + declared fallbacks
    IEnumerable<string> GetConnectionKeyFallbacks(string connectionKey);

    // First key in the chain that is actually configured (null if none)
    string ResolveConnectionKey(string connectionKey);

    // Reverse lookup: all keys that eventually resolve to the given key
    IEnumerable<string> GetConnectionKeysResolvingTo(string connectionKey);
}

Migrations for Fallback Connections

Migrations for fallback connections only run when TagBehavior.RequireAny is used. DataMigrations.cs in StartSharp (and Serene) now builds the migration tag list from the fallback chain, so a migration tagged [TargetDB("ProMeetingDB")] runs when ProMeeting resolves to Default via fallbacks:

options.Tags = (sqlConnections as IConnectionKeyFallbacks)?
    .GetConnectionKeysResolvingTo(databaseKey)?
    .Select(x => x + "DB").ToArray() ?? [databaseKey + "DB"];
options.IncludeUntaggedMigrations = databaseKey == "Default";

All StartSharp feature projects (Pro.Extensions, Meeting, WorkLog, OpenIddict, OpenIdClient, etc.) now use this feature instead of requiring every connection key to be configured.

SQL-Backed Distributed Cache (StartSharp)

A new SqlDistributedCache implementation of IDistributedCache (in Pro.Extensions) stores distributed cache entries in a SQL database table, useful for multi-server deployments where a Redis server is not available:

services.AddSqlDistributedCache(o =>
{
    o.ConnectionKey = "ProSqlDistributedCache"; // default
    // o.TableName = "DistributedCache";       // default
    // o.Prefix = ...;
    // o.DefaultSlidingExpiration = TimeSpan.FromMinutes(20);
    // o.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(30);
});

Related feature keys (SqlFileSystemMigrations, SqlDataProtectionMigrations, SqlDistributedCacheMigrations) were added so migrations for the FileSystem, DataProtectionKeys and DistributedCache tables can be optionally disabled via feature toggles — the corresponding migration classes are annotated with [RequiresFeature(...)].

DataProtection Improvements (StartSharp)

DataProtection configuration in Startup has been consolidated into a new DataProtectionSettings option type (section key DataProtection) and the AddDataProtectionWithSettings extension. The whole DataProtection setup in Startup.cs is now a single line:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddDataProtectionWithSettings(Configuration);
}

Everything else is configuration-driven:

"DataProtection": {
  "ConnectionKey": "Default",                     // persist keys in SQL via SqlXmlRepository
  "FolderPath": "App_Data/DataProtectionKeys",    // or persist to file system
  "ApplicationName": "StartSharp",
  "EncryptionKey": {
    "PrivateKey": "-----BEGIN PRIVATE KEY-----...", // PEM text or BASE64 DER blob
    "Password": "..."
  }
}

UPSERT Support in Fluent SQL

SqlInsert can now be converted to an UPSERT (INSERT or UPDATE depending on existence) statement:

var insert = new SqlInsert(DataProtectionKeysRow.TableName)
    .Set(nameof(DataProtectionKeys.FriendlyName), friendlyName)
    .Set(nameof(DataProtectionKeys.XmlData), xml);

// Executes dialect-specific UPSERT; falls back to a basic
// update-then-insert workaround for unknown dialects
insert.ExecuteUpsert(connection, [nameof(DataProtectionKeys.FriendlyName)]);

Query Introspection and Where Overload Removal (Breaking Change)

// Before:
new SqlQuery().Where("A = 1", "B = 2");

// After:
new SqlQuery().Where("A = 1").Where("B = 2");

Legacy overloads were also removed from DapperCore, with comments added to clarify its non-interception behavior.

Select2 Formatter Strings Are Now Text (Breaking Change)

[Breaking Change] Strings returned from Select2 formatters are now treated as text, not HTML markup. This prevents unescaped item text from being injected as HTML. If you specified any of the format options like formatResult, formatSelection etc. for select2 and returned HTML strings, you should modify them to return HTML elements / fragments instead:

// Before:
formatResult: (item) => '<b>' + item.text + '</b>'

// After:
formatResult: (item) => <b>{item.text}</b>

Internally, select2.ts was renamed to select2.tsx and containers are now created with JSX syntax, the deprecated e.which was replaced with e.key, and Select2.stripDiacritics now uses an alternative method with a smaller special case table.

UIDialogMaximizer Widget (dialogExtend Plugin Conversion)

The third-party jquery.dialogextend plugin was converted into a proper Serenity widget, UIDialogMaximizer, in @serenity-is/corelib:

import { DialogExtensions } from "@serenity-is/corelib";

DialogExtensions.dialogMaximizable(dialog);
// or manually:
new UIDialogMaximizer({ element: dialog.element[0] });

Options (UIDialogMaximizerProps) include dblclick (default true, double-click title bar toggles maximize) and showButton (default true). Related CSS changes make jQuery UI 1.13+ dialog close buttons look properly (close text hidden with proper alignment), including compat CSS adjustments.

UseNodeScriptRunner and Node Script Argument Parsing

A new UseNodeScriptRunner extension starts node scripts configured via the StartNodeScripts configuration key (semicolon separated script [args] entries) at application startup, and the NodeScriptRunner argument parsing was improved. pkgManagerCommand now defaults to node instead of npm, so entries like --run build execute via node --run:

public void Configure(IApplicationBuilder app, ...)
{
    // ...
    app.UseNodeScriptRunner();
}

StartSharp feature projects switched to it and removed their build:watch npm scripts.

Puppeteer-Specific PDF Options (StartSharp)

IHtmlToPdfOptions gained EditLaunchOptions and EditPdfOptions callbacks, which can only be used by the Puppeteer engine (they are ignored by other engines like WKHtmlToPdf):

var options = new HtmlToPdfOptions
{
    EditLaunchOptions = opt =>
    {
        if (opt is LaunchOptions launch)
        {
            // Puppeteer LaunchOptions (e.g. args, headless mode)
        }
    },
    EditPdfOptions = opt =>
    {
        if (opt is PdfOptions pdf)
        {
            // Puppeteer PdfOptions (e.g. PreferCSSPageSize)
        }
    }
};

Code Generation Improvements

A New Serenity Docs Site

The Serenity documentation site at https://serenity.is/docs received a complete redesign, with a modern layout and dark theme support:

Docs Dark Theme Docs Light Theme

Reorganized and Expanded Framework Documentation

The table of contents was reorganized into two top-level sections — Backend Framework (.NET) and Frontend Framework (TypeScript) — and around 70 new framework topics were added.

New backend (.NET) topics include:

New frontend (TypeScript) topics include:

API Reference Generated from Source Comments

New StartSharp Feature Docs

Test Coverage Improvements

Bugfixes

Serenity:

StartSharp:

Package Updates

Notable package updates in this release:

Upgrading to 10.4.0

  1. Update NuGet packages to 10.4.0.
  2. Where overload removal: If you called Where("A", "B") with multiple conditions on SqlQuery or SqlUpdate, chain separate Where calls instead: .Where("A").Where("B").
  3. Select2 formatters: If any of your formatResult / formatSelection etc. callbacks return HTML strings, change them to return HTML elements / fragments (see the Select2 section above).
  4. Connection keys: Feature module connection keys (e.g. ProMeeting, ProFeatures) no longer need to be configured if a fallback is declared. You may remove redundant connection entries, or declare config-based fallbacks via Data:(ConnectionKey):FallbackFor.
  5. DataProtection: If you configured DataProtection manually in Startup.cs, replace it with services.AddDataProtectionWithSettings(Configuration) and move settings to the DataProtection configuration section. If you used CertificatePassword / PrivateKeyPassword, rename it to Password.
  6. Encryption keys: If you loaded encryption keys/certificates with custom code, consider migrating to EncryptionKeySpec + IEncryptionKeyLoader.
  7. build:watch scripts: If your project relied on feature build:watch npm scripts, they were removed in favor of StartNodeScripts configuration consumed by app.UseNodeScriptRunner().
  8. Regenerate typings: Run sergen to regenerate server typings / rows so generated files pick up the new XML doc comments and the DateTimeOffsetField support.