Serenity 6.1.8 Release Notes (2022-09-29)

We'll publish these release notes for versions that deserve more explanation than short descriptions available in our change log.

Added SourceMapSecurity Middleware (StartSharp)

While source maps are very useful in development, they can raise security concerns in production. We have introduced a middleware to block access to your .js.map and .css.map files in non-development environments, preventing unauthorized access to your TypeScript source code. This middleware can be enabled in Startup via the following code:

if (!env.IsDevelopment())
{
    app.UseSourceMapSecurity(new()
    {
        SkipPermission = Configuration["SourceMapSkipPermission"]
    });
}

This middleware effectively restricts access to map files in environments other than development.

Furthermore, you have the option to specify a permission in appsettings.json using SourceMapSkipPermission. This allows users with the specified permission to access .map.js files even in production (use with care!).

It's worth noting that the middleware for this feature resides in the Serenity.Pro.Extensions package.

We strongly recommend enabling ScriptBundling / CssBundling in production sites through appsettings.json files.

You can experience this middleware in action on serenity.is and our demo.

Added a Workaround for getPropertyItemsData Change

In Serenity 6.1.0, we introduced the ability to send more metadata in addition to what is available in form and column properties by changing the format of the JSON returned from form and column scripts.

Before that, a form or column script was simply a JSON array of PropertyItem objects:

[
     {
          "name": "LanguageId",
          "title": "Db.Administration.Language.LanguageId",
          "editorParams": {
               "maxLength": 10
          },
          "maxLength": 10,
          "required": true,
          "width": 150
     },
     {
          "name": "LanguageName",
          "title": "Db.Administration.Language.LanguageName",
          "editorParams": {
               "maxLength": 50
          },
          "maxLength": 50,
          "required": true,
          "width": 150
     }
]

Now it is structured as a class:

{
     "items": [
          {
               "name": "LanguageId",
               "title": "Db.Administration.Language.LanguageId",
               "editorParams": {
                    "maxLength": 10
               },
               "maxLength": 10,
               "required": true,
               "width": 150
          },
          {
               "name": "LanguageName",
               "title": "Db.Administration.Language.LanguageName",
               "editorParams": {
                    "maxLength": 50
               },
               "maxLength": 50,
               "required": true,
               "width": 150
          }
     ],
     "additionalItems": []
}

As you can see, the items are moved into an items property, and there is an additionalItems property that is unused in this sample. We plan to use these additionalItems to pass information about items that are not used in the Form or Columns definitions.

For example, if a column definition has the Country property but not its related CountryId property, we may not know if the foreign key property CountryId is mandatory (not null) or not, or what its editor / formatter type is.

We used these additionalItems for a new inline grid editing sample in StartSharp (https://demo.serenity.is/AdvancedSamples/InlineEditing) that is more advanced than the Product editing sample (https://demo.serenity.is/Northwind/Product). It uses SleekGrid's (our SlickGrid rewrite) cell navigation and editing functionality, while the legacy sample uses, well, a legacy method.

The code written for the new inline editing sample is just 36 lines of code (actually just 5 lines for editing), compared to the 297 lines required in the legacy sample ;)

This is made possible by the additionalItems property, which passes some server-side information about editing in the columns script.

We added Q.getFormData(), Q.getColumnsData() (and their Async versions) to support this new format. But, to maintain backward compatibility, we left the Q.getForm() and Q.getColumns() methods as they are, e.g., they return an array as they did before.

These functions are also used in classes like PropertyDialog, EntityDialog, EntityGrid via their getPropertyItems() methods to receive information about Columns and Form items. Again, to maintain compatibility as much as possible, we left them as they are, but there are now getPropertyItemsData methods in those classes that should be overridden instead of getPropertyItems where possible.

The getPropertyItems methods in those classes are now just a return this.propertyItemsData.items line, and the actual loading is done in the getPropertyItemsData methods:

protected getPropertyItems() {
    return this.propertyItemsData?.items || [];
}

protected getPropertyItemsData(): PropertyItemsData {
    var formKey = this.getFormKey();

    if (!Q.isEmptyOrNull(formKey)) {
        return Q.getFormData(formKey);
    }

    return { items: [], additionalItems: [] };
}

The code above is an excerpt from EntityDialog.ts. This is not a breaking change for ordinary subclasses, as long as both Serenity.Web and Serenity.Scripts are 6.1.0+. Otherwise, as the JSON sent by the server is different from what the script code expects, it might break your forms or grids.

However, this change caused a breaking issue in a rare case where a dialog's getFormKey is not overridden (e.g., the dialog does not have a corresponding form class), but the dialog returns its hardcoded form items by overriding its getPropertyItems method:

class SomeDialog {
    // Note that there is no `getFormKey()` method here.

    getPropertyItems() {
        return [
            {
                name: 'Some',
                //...
            }
        ]
    }
}

So, this code does not call super.getPropertyItems(). The original getPropertyItems used to call getFormKey, followed by Q.getFormData, which attempted to load the form script from the server.

However, when this user updated to 6.1.0+, the dialog broke. Even though they didn't call super.getPropertyItems(), the getPropertyItemsData() method is still called from EntityDialog itself. It tried to load the default form based on the class name (Form.SomeDialog), resulting in a can't load script data... error.

For users who don't often read the change log or release notes, we added a workaround in 6.1.8:

protected getPropertyItems() {
    return this.propertyItemsData?.items || [];
}

protected getPropertyItemsData(): PropertyItemsData {
    var formKey = this.getFormKey();

    if (this.getFormKey === EntityDialog.prototype.getFormKey &&
        this.getPropertyItems !== EntityDialog.prototype.getPropertyItems &&
        !ScriptData.canLoad('Form.' + formKey)) {
        return {
            items: this.getPropertyItems(),
            additionalItems: []
        }
    }

    if (!Q.isEmptyOrNull(formKey)) {
        return Q.getFormData(formKey);
    }

    return { items: [], additionalItems: [] };
}

In the code above, we attempt to identify, in the getPropertyItemsData method, if the user has overridden getPropertyItems in their subclass, but has not overridden the getFormKey method (meaning they don't have a form). Additionally, the default form name calculated from the class name is not available in registered scripts. This will likely handle 99% of cases. However, if you are reading this, please override getPropertyItemsData to be sure.