Serenity 8.6.0 Release Notes (2024-07-13)

These release notes detail the significant changes made between versions 8.2.2 and 8.6.0. For a complete list of changes, please refer to the Serenity Change Log.

Complete Removal of jQuery Dependency

The Serenity core library no longer depends on jQuery or any packages that use jQuery. The following script entries in appsettings.bundles.json can be safely removed unless explicitly used in some of your pages:

        "~/Serenity.Assets/jquery/jquery.js",
        "~/Serenity.Assets/Scripts/jquery.autoNumeric.js",
        "~/Serenity.Assets/Scripts/jquery.colorbox.js",
        "~/Serenity.Assets/Scripts/jquery.maskedinput.js",
        "~/Serenity.Assets/Scripts/jquery.validate.js",
        "~/Serenity.Assets/Scripts/select2.js",

Select2, Autonumeric, and Validate are now integrated into the core library with jQuery dependencies removed. Images will open in a new tab if jquery.colorbox is not available. We currently do not have a recommendation for maskedinput due to its limited use; you may add a validation rule to check for the expected format.

If you still need jQuery or any libraries that depend on it, it is okay to leave them as is. Serenity will work fine with jQuery loaded, and Fluent will redirect events through jQuery's event system for interoperability.

Renaming of Select2Editor to ComboboxEditor

The base Select2Editor class, which LookupEditorBase and others are based on, has been renamed to ComboboxEditor to abstract the dependency on a specific dropdown type. Currently, ComboboxEditor uses the integrated version of select2, but we may replace it or implement other combobox types via external components in the future.

  • Support for Template.html and .ts.html template files has been removed.
  • TemplatedWidget is deprecated; use Widget instead.
  • TemplatedPanel is deprecated; use BasePanel.
  • TemplatedDialog is deprecated; use BaseDialog.

These changes reflect the deprecation of the legacy template mechanism in version 8.2.0. Use the renderContents method to return markup via TSX/JSX-DOM or Fluent(...).getNode(). While the legacy getTemplate() will still be supported internally, it is not recommended.

Deprecation of TypeScript Experimental Decorators

Serenity uses several decorators like @Decorators.registerClass() similarly to C# attributes. In the past, enabling decorator support in TypeScript required using an experimental flag in tsconfig.json:

"experimentalDecorators": true

As of TypeScript 5, experimental decorator support is no longer required. Serenity still supports both the experimental and modern versions, but it is recommended to remove the line above from your tsconfig.json after updating Serenity and TypeScript (e.g., your Visual Studio version).

Since the new version of TypeScript decorators is only supported by ESBuild 0.21+, update the @serenity-is/tsbuild package reference to 8.6.0 or later in package.json:

"devDependencies": {
    "@serenity-is/tsbuild": "8.6.0"
}

If you have a separate esbuild entry in your package.json, it is recommended to remove it as tsbuild already includes a reference.

Deprecation of Decorators.option

The @Decorators.option() decorator, used to mark a property as an option, is deprecated. Use constructor options/props instead, as JSX components require options to be passed via the constructor. Setting some options via properties when a widget is created with jsx-dom is not possible.

Unhandled Rejection Handler

Serenity's service-related methods, like serviceCall, have switched from jQuery Ajax (XMLHTTPRequest) to fetch. Since fetch uses promises, errors thrown from promises should be caught, unlike with jQuery Ajax calls. The following simple service call might log an unhandled rejection error in the browser console if the server returns an error:

SomeService.Delete({
    EntityId: 5;
})

// Uncaught (in promise) ...

With promises, it is expected to add a catch statement:

SomeService.Delete({
    EntityId: 5;
}).catch((error) => { 
    // handle error
})

We could add a catch inside the serviceCall method to make it compatible with the jQuery version, but this would prevent the caller from catching the exception and performing some action based on the error.

To avoid such errors in the console, add the following line in errorhandling-init.ts (or ScriptInit.ts if not available in your app):

window.addEventListener("unhandledrejection", ErrorHandling.unhandledRejectionHandler);

FilePage.HandleUploadRequest Return Type Change

Due to differences between System.Text.Json and Newtonsoft.Json, change the return type of the HandleUploadRequest method in FilePage.cs to UploadResponse from ServiceResponse:

[NonAction]
private UploadResponse HandleUploadRequest(HttpContext context)
{

If this change is not made, upload editors will not work properly, resulting in errors like Uploaded file is not an image even if the file is an image, as System.Text.Json does not serialize properties of UploadResponse otherwise.

IUploadProcessor.Process No Longer Returns a Success Flag

The Process method of the IUploadProcessor used to return an object with the Success property, which was true when upload validation was successful or false when it was not, and the ErrorMessage property contained information about the problem.

This is no longer the case, and it simply throws an exception in case of an error. Although the Success property is still in the returned object, it is deprecated and will always be true.

The if (uploadInfo.success) line and its else block in FilePage.cs are no longer necessary. Please see the latest version of FilePage.cs in the StartSharp/Serene repository.

Introduction of the ILocalTextInitializer Interface

We have introduced a new ILocalTextInitializer abstraction, which should be used instead of the InitializeLocalTexts method in Startup.cs.

Unless you have custom code in InitializeLocalTexts, please remove it and use AddLocalTextInitializer() in the ConfigureServices method:

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

and call app.InitializeLocalTexts() in the Configure method:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    RowFieldsProvider.SetDefaultFrom(app.ApplicationServices);
    app.InitializeLocalTexts();
    //...
}

This interface allows us to reinitialize local texts when necessary, for example, after saving texts in the Translation screen.

If you have custom code in the InitializeLocalTexts method, implement the ILocalTextInitializer interface (you may subclass DefaultLocalTextInitializer) and register it in Startup.cs.

Introduction of the IPermissionKeyLister Interface

A new IPermissionKeyLister abstraction has been introduced to get a list of permission keys.

public interface IPermissionKeyLister
{
    IEnumerable<string> ListPermissionKeys(bool includeRoles);
}

The code block in UserPermissionRepository for discovering permission keys has been moved to the AppServices.PermissionKeyLister class, which implements this interface and is registered in Startup.cs:

services.AddSingleton<IPermissionKeyLister, AppServices.PermissionKeyLister>();

Currently, there is no default implementation for this abstraction in Serenity itself, as it may change from application to application. For example, some apps might need to load available permission keys from a database table in addition to attributes.

Using this abstraction, we removed the direct dependency on UserPermissionRepository from several classes like PermissionKeysDataScript, UserPermissionEndpoint, etc. Please see the latest code in the StartSharp repository.

Default Implementations of IPermissionService and IUserAccessor with Transient Grant/Impersonation Support

The ITransientGrantor interface allows transiently (temporarily in memory) granting permissions to the current request user before executing an operation.

Similarly, the IImpersonator interface allows transiently (temporarily in memory) impersonating another user before executing an operation.

This permits a user to perform actions they would not normally be allowed to, such as calling a service only available to admins, given the necessary security checks are in place.

For transient granting to work, the IPermissionService implementation must also implement the ITransientGrantor interface. Similarly, the IUserAccessor implementation must also implement the IImpersonator interface.

As the default implementations in StartSharp do not support this, we had to wrap our implementations in Startup.cs to enable this feature:

services.AddSingleton<IPermissionService>(services => 
    new TransientGrantingPermissionService(
        ActivatorUtilities.CreateInstance<AppServices.PermissionService>(services),
        services.GetRequiredService<IHttpContextItemsAccessor>()));

services.AddSingleton<IUserAccessor>(services => 
    new ImpersonatingUserAccessor(
        ActivatorUtilities.CreateInstance<AppServices.UserAccessor>(services),
        services.GetRequiredService<IHttpContextItemsAccessor>());

The new AppServices.PermissionService already implements the ITransientGrantor interface, so wrapping is no longer necessary:

public class PermissionService( /* ... */ ) : IPermissionService, ITransientGrantor {

}

It uses the TransientGrantingPermissionService internally to easily implement the interface. Please see the latest code of PermissionService.cs in the StartSharp repository.

Similarly, the default UserAccessor implementation implements the IImpersonator interface:

public class UserAccessor( /* ... */ ) : IUserAccessor, IImpersonator {

}

As transient granting/impersonation support might be necessary for some operations like reporting callbacks, it is recommended to apply this change to your permission and user accessor services, even if you don't directly use these features in your application.

New AddSingletonWrapped Helper

Wrapping services in Startup.cs might be necessary to add extra abilities to some services, like transient granting, impersonation, etc.

For example, to enable impersonation support for our IUserAccessor service, we would need to make the following changes in Startup.cs:

services.AddSingleton<IUserAccessor>(services => 
    new ImpersonatingUserAccessor(
        ActivatorUtilities.CreateInstance<AppServices.UserAccessor>(services), services.GetRequiredService<IHttpContextItemsAccessor>());

With the new AddSingletonWrapper interface, it is much simpler:

services.AddSingletonWrapped<IUserAccessor, ImpersonatingUserAccessor, AppServices.UserAccessor>();

Note that in the current StartSharp version, the default IUserAccessor implementation already has built-in impersonation support, so the above line won't be necessary. It is used in Startup.cs for other wrapping, like OpenIddict.

New Fluent Methods

To improve compatibility with jQuery (note that we don't target 100% compatibility) and make porting code easier, the following methods have been added:

  • Fluent.byId: a shortcut for Fluent(document.getElementById)
  • Fluent.each and Fluent.style: execute a callback with the node or CSS style declaration if the element is not null
  • Fluent.matches: similar to $.is but doesn't support any custom selectors and calls element.matches
  • Fluent.class: directly sets the className property, preferred to addClass for element creation
  • Fluent.isDefaultPrevented: helps overcome issues when jQuery exists or not, checking both e.defaultPrevented and e.isDefaultPrevented?.()
  • Fluent.eventProp: reads a property from the event, event.originalEvent, or event.detail, as jQuery does not pass custom properties like route to the synthetic event
  • Fluent.getWidget and Fluent.tryGetWidget: get a reference to a widget on the element
  • Fluent.findEach: executes a callback for each element found with a Fluent object
  • Fluent.after and Fluent.before: similar to jQuery methods

Breaking Change for JSX-DOM Ref Callbacks

As jsx-dom has recently fixed an issue (before version 8.1.4) where ref callbacks were not executed for React-style class components like Serenity Widgets, we will no longer call the ref callback from the Widget itself to avoid double execution. This might cause problems when using an older version of jsx-dom. After updating to Serenity 8.4.4+, please update jsx-dom in your package.json to version 8.1.4+ and run npm install.

Category links on top of forms, hidden since version 8.4.8, are now completely removed. They were generally confusing for end users and were not particularly useful unless the form was very long.

Updated NPM Packages

The following package references in StartSharp/Serene have been updated:

"@preact/signals": "1.2.3",
"jsvectormap": "1.6.0",
"jsx-dom": "8.1.4",
"moment": "2.30.1",
"preact": "10.22.0",
"chart.js": "4.4.3"

Although there are no known breaking changes, it is recommended to keep these packages in sync in your application.

Updated NuGet Packages

The following package references in StartSharp/Serene have been updated:

<PackageReference Include="FluentMigrator.Runner" Version="5.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.7" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.5.3" 
    PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" />

There are other transitive package dependencies that are referenced by Serenity NuGet packages but they will be updated when you update Serenity packages, so the above ones are the ones you should manually update.

Resolved Potential Issue with Publishing and Static Web Assets

During publish you may sometimes get errors about files not being found, especially under "~/wwroot/esm/".

Those errors are usually caused by MSBuild trying to copy these files to publish directory while ESBuild clean plugin deleting them before the publish starts.

Please check latest StartSharp.Web.csproj file for a workaround to such issues.

You should delete CreateTSCInputs target, replace CompileTSCBefore with RunTSBuild target and replace CompileTSCAfter with TranformRunTSBuild target.

SleekGrid's useLegacyUI Defaults to False [Breaking Change]

SlickGrid adds some legacy jQuery UI classes like ui-state-default and for compatibility we used to also add these classes with our SleekGrid.

The flag that controls this is now false by default.

Serene users should add following line in ScriptInit.ts as SlickGrid's default CSS still depends on those legacy classes.

gridDefaults.useCssVars = false;
gridDefaults.useLegacyUI = true; // add this line

StartSharp users don't need this as the premium theme does not use SlickGrid's default css.

Once we update the common style and slick.grid.css files in Serenity.Assets, Serene users may remove that line.