Skip to content

Serenity 10.5.2 Release Notes (2026-09-16)

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

10.5.0 was the release that introduced the async request handlers and its behavior interface split. 10.5.1 was a small patch (an ImageUploadEditor fix and an older-versions selector for this documentation site), so the bulk of this document covers 10.5.2, whose main theme is nullable reference type support across the framework, generated code and templates.

Nullable Reference Types: The Big Theme

Serenity and StartSharp were originally written when nullable reference types (NRT) did not exist, and most projects had <Nullable>disable</Nullable> (or left it unset). In 10.5.2 the framework and all feature projects now compile with nullable reference types enabled, and the code generators were taught to emit nullable annotations.

Because turning on full nullable warnings in a large existing application produces thousands of warnings, the two concerns were separated:

  • <Nullable>enable</Nullable> — nullable annotation context and warnings. Used by the framework projects (src/, common-features/src/, pro-features/src/, business features) which were cleaned up to compile warning-free.
  • <Nullable>annotations</Nullable> — nullable annotation context only, warnings off. This is what tests, Serene and StartSharp use, so nullable annotations can be emitted without a wall of warnings.

The Nullable MSBuild property now also flows into the code generators, so generated code follows the setting of the project it is generated into:

Project / item 10.5.0 10.5.2
Serenity src/ (Core, Services, Web, CodeGenerator) disabled <Nullable>enable</Nullable>
Common / pro / business feature projects disabled <Nullable>enable</Nullable>
Tests disabled <Nullable>annotations</Nullable>
Serene.Web disabled <Nullable>annotations</Nullable> (templates) / <Nullable>enable</Nullable> (samples)
StartSharp.Web disabled <Nullable>annotations</Nullable> (templates) / <Nullable>enable</Nullable> (samples)
New projects from templates disabled <Nullable>annotations</Nullable>

New projects created from the Serene and StartSharp templates now default to <Nullable>annotations</Nullable> (previously nullable was disabled). This only turns on the annotation context, not warnings, so existing code keeps compiling silently while you get nullable annotations in generated code.

Enabling Full Nullable Checking in Your Project

<Nullable>annotations</Nullable> does not enable nullable warnings. If you want the compiler to actually check nullability, set <Nullable>enable</Nullable> in your project file yourself:

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

You will likely need to clear the nullable warnings this produces (the framework itself was cleaned up, but your own code may not be). Because generated code already contains the annotations, you do not need to regenerate everything when you make this switch.

What the Generators Emit

The generators use the same rule for annotations and enable when deciding whether to emit ? and = null!, so generated code stays annotation-correct even if you later change the project to <Nullable>enable</Nullable>.

Regenerated files (client types, row fields, server typings and other transform output) now get a directive at the top:

// <Nullable>enable</Nullable>
#nullable enable

// <Nullable>annotations</Nullable>
#nullable enable annotations

Files generated by sergen g (e.g. Row.cs, Columns.cs, Form.cs) belong to you and are not regenerated automatically, so they do not get any #nullable directive — only the annotations.

The generated row fields class now emits nullable annotations for reference-typed fields:

public class RowFields : RowFieldsBase
{
    // <Nullable>enable</Nullable> or <Nullable>annotations</Nullable>
    public Int32Field CategoryId = null!;
    public StringField CategoryName = null!;

    // nullable disabled / not set: now explicitly = null to avoid CS0649
    public StringField CategoryName = null;
}

When nullable is disabled (or not set), the field declarations changed from a bare public StringField CategoryName; to public StringField CategoryName = null;. This removes the CS0649 "field is never assigned" warning that shows up for private nested test rows, which previously had to be suppressed with #pragma warning disable or .editorconfig.

Async Unit of Work

10.5.0 made the request handlers and data access layer truly async. 10.5.2 finishes the job by extending async support to the unit of work and the MVC endpoint pipeline.

UnitOfWork.CommitAsync / DisposeAsync

UnitOfWork and TransactionlessUnitOfWork now implement IAsyncDisposable and expose an async CommitAsync:

// 10.5.0
using var uow = new UnitOfWork(connection);
handler.Create(uow, request);
uow.Commit();

// 10.5.2
await using var uow = new UnitOfWork(connection);
await handler.CreateAsync(uow, request, cancellationToken);
await uow.CommitAsync(cancellationToken);

CommitAsync calls DbTransaction.CommitAsync when the underlying transaction supports it (falling back to the synchronous Commit), and DisposeAsync uses the transaction's async dispose (IAsyncDisposable) when available, falling back to the synchronous Dispose otherwise. TransactionlessUnitOfWork provides no-op async implementations for both.

ServiceEndpoint Is Now IAsyncDisposable

ServiceEndpoint now implements IAsyncDisposable and commits / disposes its unit of work and connection asynchronously in the endpoint pipeline. The post-action filter that used to run synchronously in OnActionExecuted is now split:

  • OnActionExecutedAsync(ActionExecutedContext) — this is the IAsyncActionFilter implementation and the one MVC normally calls. Override this instead of OnActionExecuted.
  • OnActionExecuted(ActionExecutedContext) — the synchronous IActionFilter implementation, kept for compatibility and normally not called.

The connection is disposed through IAsyncDisposable when available, falling back to the synchronous Dispose.

InTransactionAsync Controller Extension

A new ControllerBase.InTransactionAsync<TResponse> extension runs a callback with a unit of work and converts any exception to a service response, mirroring the existing synchronous InTransaction helper:

[HttpPost, AuthorizeCreate(typeof(MyRow))]
public Task<Result<SaveResponse>> Create(MyRequest request, CancellationToken cancellationToken)
{
    return this.InTransactionAsync<SaveResponse>("Default", async (uow, ct) =>
    {
        var handler = HttpContext.RequestServices.GetRequiredService<IMySaveHandler>();
        return await handler.CreateAsync(uow, new SaveRequest<MyRow>
        {
            Entity = new MyRow { /* ... */ }
        }, ct);
    }, cancellationToken);
}

Application Parts Recovery

Rarely, the generated application parts assembly info (e.g. *.MvcApplicationPartsAssemblyInfo.cs) is not included in the build — for example during some incremental or parallel builds. When that happens, the ApplicationPartManager only contains the entry assembly, so pages, navigation items and other types from referenced assemblies silently disappear from the application.

ApplicationPartsTypeSource now includes a best-effort recovery algorithm. When it detects that the entry assembly has no ApplicationPartAttribute and that Serenity.Net.Web itself is missing from the part manager, it reads the application .deps.json file, finds the assemblies that reference MVC (checking the assembly metadata without loading them first), and adds them to the part manager exactly like the Razor SDK would have done at build time. It also repairs the part manager itself and is thread-safe (concurrent callers block until recovery completes).

Recovery is enabled by default and can be turned off if needed:

services.AddApplicationPartsTypeSource(partManager, tryPartRecovery: false);

TransformInclude Now Accepts a Type Name

TransformInclude is the TypeScript interface used by client-side options/classes that should be transformed to C# by the client types generator. Previously the target namespace was always derived from the project root namespace. It can now carry the target namespace / type name as a generic argument, just like registerEditor does:

import { TransformInclude } from "@serenity-is/corelib";
import { nsProExtensions } from "./Ns";

// namespace constant (typeof ns...)
export interface ConfigureEmailTwoFactorProps extends TransformInclude<typeof nsProExtensions> {
    // ...
}

// namespace literal
export interface ResetPasswordOptions extends TransformInclude<"Serenity.Extensions.Membership."> {
    token: string;
}

// fully qualified type name
export interface SomeOptions extends TransformInclude<"Serenity.Extensions.SomeOtherModule.SomeOptions"> {
    // ...
}

TransformInclude without a type argument keeps the old behavior (uses the root namespace).

AsyncLocal Overrides for Global Settings

Some legacy settings are global statics, which makes them awkward to use in tests and background tasks (setting them affects every thread). 10.5.2 adds AsyncLocal-backed local overrides that only affect the current thread and async context, leaving the global default untouched. Each SetLocal... method returns the previous local value:

// set only for the current thread / async context, returns the old local value
var old = DecimalEditorAttribute.SetLocalAllowNegativesByDefault(true);
try
{
    // ... test or background work
}
finally
{
    DecimalEditorAttribute.SetLocalAllowNegativesByDefault(old);
}

Available local setters:

  • DecimalEditorAttribute.SetLocalAllowNegativesByDefault
  • IntegerEditorAttribute.SetLocalAllowNegativesByDefault
  • SqlSettings.SetLocalCommandTimeout
  • RowJsonConverter.SetLocalShouldSerializeExtension / SetLocalShouldDeserializeExtension

GetIdField() Extension Method

A new IRow.GetIdField() extension returns the row's IdField, but unlike the IdField property it throws an InvalidOperationException when the field is null, making misconfigured rows easier to catch:

// 10.5.0 -- silently returns null if the row has no IdField
Field field = row.IdField;

// 10.5.2 -- throws if the row's IdField is null
Field field = row.GetIdField();

The framework (and StartSharp's samples) were updated to use GetIdField() internally.

Deprecation Comments in Generated Code

@deprecated JSDoc tags were replaced with plain deprecated comments in generated Form.ts files and corelib declarations:

// 10.5.0
/** @deprecated use getLookupAsync instead */
static getLookup() { /* ... */ }

// 10.5.2
/** **deprecated** use getLookupAsync instead */
static getLookup() { /* ... */ }

Visual Studio's new JS/TS tooling reports @deprecated tags as warnings with no way to turn them off, so removing the tag stops generated files from polluting the warning list while keeping the note visible.

Documentation

  • Serenity Docs (https://serenity.is/docs) now allows viewing documentation for older versions, so you can check the docs for the version of the framework you are using. (10.5.1)

Bugfixes

  • ImageUploadEditor with allowNonImage (10.5.1) — fixed the editor not allowing normal (non-image) files even when the allowNonImage option was true (e.g. when using [FileUploadEditor]). ImageUploadEditor and MultipleImageUploadEditor no longer overwrite an explicitly specified allowNonImage value.
  • Companion handler wrapper direction — the I(Delete/List/Retrieve/Undelete)RequestProcessorAsync companion wrapper type in the CompanionHandlerType attribute was inverted. It now correctly points to SyncToAsync...RequestProcessorWrapper, so a synchronous custom handler can be found and wrapped when an async request processor interface is requested.
  • ESMAssetBasePath casing — sergen now reads the ESMAssetBasePath project property from the correct casing (previously looked up EsmAssetBasePath), so a custom ESM asset base path set in the project file is honored. Nullable is now read the same way.

Upgrading to 10.5.2

  1. Update NuGet packages to 10.5.2 (StartSharp samples reference Serenity.* 10.5.2).

  2. Nullable is optional, but recommended. Existing projects keep compiling unchanged because nullable is not enabled in your project file. To opt in:

    • Add <Nullable>annotations</Nullable> to get nullable annotations in generated code without warnings, or
    • Add <Nullable>enable</Nullable> for full nullable checking (you will need to clean up the resulting warnings in your own code).
    • New projects from the Serene and StartSharp templates already use <Nullable>annotations</Nullable>.
  3. Regenerate code if you want nullable annotations. Run your normal build / sergen transforms so that row fields, client types, server typings and other generated files pick up the #nullable directive and annotations. Files previously generated by sergen g (Row, Columns, Form) must be edited individually if you want the annotations, as they belong to you and are not overwritten automatically.

  4. Async unit of work. If you dispose or commit a UnitOfWork manually in async code, prefer await uow.CommitAsync(ct) and await uow.DisposeAsync() / await using. If you override ServiceEndpoint.OnActionExecuted, override OnActionExecutedAsync instead — that is the method MVC now calls.

  5. Existing static settings keep working. The AsyncLocal variants are additive; the static properties and setters behave exactly as before.