Serenity 6.4.3 Release Notes (2023-01-07)

Flutter-Based Mobile Applications for Android/iOS (StartSharp Enterprise)

You can now explore our new Flutter-based mobile applications at https://demo.serenity.is.

Mobile App Links on Login Page

These samples showcase integration with Serenity application services using OpenID Connect/JWT authentication and include the WorkLog module implementation, localization, and dark theme options.

Work Log Mobile

Please note that access to the source code and support for the mobile application is exclusively available to StartSharp Enterprise customers.

OpenIddict Integration and JWT Authentication Options (StartSharp)

We have introduced a new package called Serenity.Pro.OpenIddict for StartSharp customers. This package facilitates JWT authentication through the OpenID Connect protocol.

To enable OpenIddict in a new StartSharp project, simply set the Enabled flag to true in the appsettings.json file:

OpenIdSettings: {
    `Enabled`: true
}

We will provide a document with instructions on adding OpenID integration to an existing StartSharp project.

Introducing AutoValidateAntiforgeryIgnoreBearerFilter Attribute

Antiforgery in ASP.NET Core is a mechanism designed to prevent request forgery attacks. You can learn more about it at the following link:

https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-7.0

While anti-forgery tokens mechanism in ASP.NET Core helps protect against such attacks when using Cookies authentication, it can be challenging to validate the anti-forgery token when utilizing JWT and Bearer authentication headers, particularly in mobile application service calls.

To address this issue, we have introduced a new attribute called AutoValidateAntiforgeryIgnoreBearerAttribute that you can use in place of AutoValidateAntiforgeryTokenAttribute in the Startup.cs file:

- options.Filters.Add(typeof(AutoValidateAntiforgeryTokenAttribute));
+ options.Filters.Add(typeof(AutoValidateAntiforgeryIgnoreBearerAttribute));

Unlike AutoValidateAntiforgeryTokenAttribute, AutoValidateAntiforgeryIgnoreBearerAttribute will bypass anti-forgery validation if the request contains an Authentication: Bearer header and does not include any cookie headers.

We believe this change will not introduce any security vulnerabilities, as we believe that XCSRF attacks are not possible with such requests. However, if you have concerns regarding this risk, you should avoid using this attribute.

New Glassy Light Theme (StartSharp)

We have introduced a new theme called Glassy Light (glassy-light) to the Serenity.Pro.Theme with background and transparency effects:

Glassy Light Theme

[Breaking Change] Renaming of Q.getTypeName to Q.getTypeShortName

The full name for a type registered with @Decorators.registerClass can usually be accessed using Q.getTypeFullName. Previously, we had a Q.getTypeName method that returned only the name portion (e.g., without the namespace), similar to the Type.Name property in .NET. However, this method was often confused with getTypeFullName, so we renamed it to Q.getTypeShortName.

We have also improved the handling of some internal type properties, such as the __typeName static property, which stored the full name for registered types. This property is now non-enumerable, along with its internal counterpart __typeName$, which has been removed. These properties were originally necessary for TypeScript/Saltaralle and older browsers like IE9, but we no longer support them.

Improved Handling of Type Registries such as EnumTypeRegistry, EditorTypeRegistry, and FormatterTypeRegistry

Serenity's type system relies on various type registries to locate types, such as editors and enums, using their registration names or keys. There is also a Q.initTypes method that is called after the document.ready event and scans the global object (window) to discover types.

Some of the registries did not function correctly if the initialization method was not executed or if they were unable to find types registered or loaded after the initialization method ran (e.g., dynamically loaded scripts or dynamic imports). This occasionally caused issues, especially after switching to ES modules.

All of the type registries have been rewritten to use a unified discovery mechanism and can locate types that are dynamically loaded after initialization or even if the initialization method has not yet been executed, as long as the types have decorators like registerClass with a proper key.

This should significantly reduce the occurrence of error messages such as Editor XYZ could not be found, etc. In rare cases where the system does not work, you will at least receive a more informative error message like the following:

SomeSampleName formatter class not found! 
Ensure there is a formatter type under the project root namespace with namespace parts starting with capital letters, like MyProject.MyModule.MyFormatter.

If using ES modules, make sure the formatter type has a decorator like @Decorators.registerFormatter('MyProject.MyModule.MyFormatter') with the full name of your formatter type and "side-effect-import" this formatter class from the current "page.ts/grid.ts/dialog.ts" file (import "./thepath/to/MyFormatter.ts").

After applying fixes, build and run "node ./tsbuild.js" (or "tsc" if using namespaces) from the project folder.

[Breaking Change] Default HTML Escaping for Toastr Notification Functions like Q.notifyError

The notification library that we rely on, Toastr, did not HTML-escape messages by default, making it vulnerable to HTML script injection attacks if untrusted user input was passed to notification methods such as Q.notifyAlert, etc. While recent versions of Toastr have added an escape option, it is not the default.

To address this issue, we now HTML-escape any message strings passed to these functions by default and set Toastr's default HTML escape option to true.

This change may cause issues if you were using one of these methods and expected it to accept HTML input, as Toastr does by default. In this case, you will need to pass escapeHtml: false (after evaluating the risks and manually escaping any user input) to continue accepting HTML input.

Q.notifyAlert(`<p>Some HTML message<b>User Entered:</b> ${htmlEncode(userInput)}</p>`, { escapeHtml: false });

Removed jQuery ScrollIntoView Dependency

When you click a category link in a Serenity form, we scroll the relevant category into view using the native JavaScript version (if the jQuery scrollintoview plugin is not available).

You may remove the following script from appsettings.bundles.json:

"~/Serenity.Assets/Scripts/jquery.scrollintoview.js",

Removed jquery.iframe-transport.js Dependency

This script was previously used by jquery.fileupload for older browsers like IE 9, but it is no longer necessary as we no longer support Internet Explorer.

You may remove it from appsettings.bundles.json:

"~/Serenity.Assets/Scripts/jquery.iframe-transport.js",

Removed Serenity.Pro.UI.js Dependency (StartSharp)

Serenity.Pro.UI is a legacy project that integrates Serenity Widgets as React components. It is currently used only by the Email client sample. If you don't use it, you may safely remove it from your bundles:

"~/Serenity.Pro.UI/index.js",

The email client sample is currently being rewritten with ES modules, and after that is completed, Serenity.Pro.UI will become obsolete.

Removed TemplateBundle, Introduced ColumnAndFormBundle

You might have the following bundle definition in your appsettings.bundles.json:

"Site": [
    "dynamic://ColumnsBundle",
    "dynamic://FormBundle",
    "dynamic://TemplateBundle"
    "dynamic://FormBundle",
    //...
]

The TemplateBundle contained dialog templates and other templates that were defined in *.ts.html or .Template.html files. Since these templates are no longer used, and assuming you don't have any such files in your project, you can safely remove the TemplateBundle.

The ColumnsBundle and FormBundle still contain essential column and form definitions, but they are now bundled together in a package named ColumnAndFormBundle. You can replace the bundle definition above with the following:

"Site": [
    "dynamic://ColumnAndFormBundle",
    //...
]

Rewrote Dashboard with ES Modules (StartSharp)

As part of our transition to ES modules, we have rewritten the Dashboard page using ES modules. In this process, we removed many legacy and outdated components, such as jquery.knob, jquery.icheck, jquery.sparkline, and jvectormap. Instead, we have incorporated modern alternatives like ChartJS and jsvectormap.

Additionally, we have optimized the size of the Dashboard page by using data URIs for profile images and the WEBP image format, which offers superior compression compared to PNG and JPG formats.

For more details, please refer to the latest DashboardIndex.cshtml and DashboardPage.ts in the StartSharp repository.

GroupItemMetadataProvider is Ported to ES Modules

We continue the process of porting SlickGrid plugins to SleekGrid and ES modules. The latest plugin to undergo this transition is GroupItemMetadataProvider.

This plugin now offers the ability to display totals directly on group rows. This feature complements or replaces separate total rows, which are typically hidden when groups are collapsed. A StartSharp sample is currently in progress...

Q.alert and Q.confirm Methods are Suffixed with Dialog

The methods in the Q namespace (@serenity-is/corelib/q) that display message dialogs, such as alert, confirm, and prompt, were occasionally confused with the browser's default methods of the same names, especially after the switch to ES modules, which removed the Q. namespace prefix.

To prevent this issue, we have added the suffix Dialog to these methods. For example, Q.alert has become Q.alertDialog, and Q.confirm is now Q.confirmDialog.

The old functions are still available for the time being, but they are considered obsolete, and we recommend avoiding their use.

The TsBuild Trigger Argument is Now Obsolete

In your package.json, you may have the following:

"scripts": {
    "prepare": "node ./tsbuild.js --trigger"
}

This allowed us to restore ES module typings from your project and package references to the node_modules directory after running the npm install command. The npm install command deleted the fake module folders we created, so we had to recreate them after the installation was completed.

To trigger the target that handles restoration, we used a fake _trigger.ts file. This prompted Visual Studio to run a design-time build when its file system watcher detected a change in a TypeScript file. However, this method did not work correctly if Visual Studio was not open during the npm install. We have now discovered a more effective way to address this issue.

"scripts": {
    "prepare": "dotnet build -target:RestoreTypings"

This command runs the RestoreTypings target directly using MSBuild, eliminating the reliance on Visual Studio's behavior.

[Breaking Change] Some Row Properties Are Now Only Available via IRow or IEditableRow Interface

In the base Row class, there are many properties and methods, such as TrackWithChecks, TrackAssignments, IdField, NameField, IgnoreConstraints, and IsAnyFieldAssigned, that are mostly implementation details and are rarely used. However, they still appear in IntelliSense when viewing the member list of an entity in Visual Studio.

Even though our JsonRowConverter omits these properties during serialization/deserialization, System.Text.Json (which we will be switching to soon) and several third-party tools, such as ApiExplorer used by Swagger, consider them properties that should be passed to/from service requests. This can even break the Swagger UI as it tries to traverse the IdField and NameField properties, which may contain circular references through RowFields and Joins metadata.

These properties are now only explicitly available via the IRow interface. If you accessed them directly, you may need to cast your object, for example, "((IRow)myRow).IdField" instead of "myRow.IdField".

There was also another set of properties that were only intended to be used with desktop applications such as WinForms and WPF, including BeginEdit, EndEdit, IsAnyFieldChanged, and PreviousValues. These properties are now exclusively accessible via the IEditableRow interface.

Issues Causing Swagger UI Failures Are Resolved

Several changes were made to the Row class, including making some properties only available via the IRow interface. These changes were made to improve the completion list for row instances and resolve issues with Swagger integration.

Other changes to the way ServiceEndpoint actions handle parameter binding should also resolve several issues with Swagger. These issues included expecting the service request object to be passed from the query string instead of the request body and assuming that IDbConnection and IUnitOfWork arguments are passed from the client.

While Swagger is not required to make your services available to third parties, it can be a useful tool for providing a user-friendly interface for others to discover and try your API.

We have not enabled Swagger by default in StartSharp because we believe it is not needed by most users. However, we will be adding a tutorial on enabling Swagger integration in the StartSharp repository for those who are interested.

[Breaking Change] Slick.Event is Renamed to Slick.EventEmitter, and Slick.EventHandler is Renamed to Slick.EventSubscriber

Similar to the Q.alert style of functions, the Event and EventHandler types in the Slick namespace were causing confusion with the default browser types. To resolve this, the following renames were made:

  • Slick.Event was changed to Slick.EventEmitter
  • Slick.EventHandler was changed to Slick.EventSubscriber
  • Slick.Handler was changed to Slick.EventListener

Note that these types are typically only used with the SleekGrid and Serenity libraries. If you have used the old names, you may need to apply these changes.

The old names are still available at runtime for compatibility with legacy plugins, but the type names are not present in the TypeScript declarations.

Added TransactionSettings with IsolationLevel and DeferStart Options

A new option type called TransactionSettings has been added, which includes the following properties:

public class TransactionSettings
{
    public IsolationLevel? IsolationLevel { get; set; }
    public bool? DeferStart { get; set; }
}

It is used to control the UnitOfWork transaction parameters that are automatically created in ServiceEndpoint actions when the action includes an IUnitOfWork typed parameter:

public SaveResponse Update(IUnitOfWork uow, SaveRequest<MyRow> request)

When an action includes an IUnitOfWork parameter, Serenity will automatically create a UnitOfWork object, which wraps a database transaction. You can now use the new TransactionSettingsAttribute to override the transaction isolation level and defer transaction start options for actions or their controllers:

[TransactionSettings(IsolationLevel.ReadCommitted, DeferStart = true)]
public class SomeController : ServiceEndpoint
{
    public SaveResponse Update(IUnitOfWork uow, SaveRequest<MyRow> request)
}

The UnitOfWork constructor now includes optional IsolationLevel and DeferStart arguments to control the transaction isolation level and whether to defer the start of the transaction.

The default for IsolationLevel is Unspecified, which means the isolation level is determined by the connection provider as it was before.

By default, when a UnitOfWork object is created, the connection is opened, and the transaction is started. This means that even if the method or request handler being called does not need to open a connection or start a transaction, it will still be done.

For example, if a save handler receives an entity with a null value for a non-null field, it will raise a validation error before any database operations are performed. In this case, the connection and transaction are not needed, but they are still created in the calling action.

This is not a problem for actions with an IDbConnection argument, as the connections are not opened until they are needed. However, UnitOfWork objects had to open the connection and start the transaction in their constructor.

The DeferStart argument allows you to delay starting the transaction until the connection is opened. The StateChange event of the DbConnection is used to detect when the transaction is needed for the first time, such as when the connection state changes to Open.

The default behavior is still to start the transaction immediately, as this has been the previous behavior, and changing it may have unexpected side effects. However, if you are aware of the potential risks and want to override the default behavior, you can do so in the Startup.cs file:

services.Configure<TransactionSettings>(opt => opt.DeferStart = true);

Improved TSTypeLister Performance

The TSTypeLister class is used by sergen and Serenity.Pro.Coder to extract types from the TypeScript files in your project. It first needs to identify the list of included files in your tsconfig.json, which it does by performing a directory scan for .ts files in your project directory.

Previously, this directory scan would include full scans of large folders such as node_modules, obj, bin, etc., even if the tsconfig.json included a pattern like Modules/**/* in the includes field.

We have now optimized the directory scanner to avoid unnecessary scans of these large folders when such an includes pattern is present. This can lead to significant improvements in build time, particularly for source generators that run in the background for every change in the source code. In some projects, we have seen a 10x improvement, with transform durations dropping from 5 seconds to 500ms.