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)

Even though source maps are very useful in development, they can be a security concern in production. We added a middleware to block access to your .js.map and .css.map files in non development environments, so that prying eyes can't look at your TypeScript source code. It is enabled in Startup via:

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

This blocks access to map files in environments except development.

There is also an option to specify a permission in appsettings.json -> SourceMapSkipPermission so that a user with that permission can still access .map.js files even in production (use with care!).

The middleware for this feature resides in Serenity.Pro.Extensions package.

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

You may try this middleware in action at serenity.is and our demo

Added a Workaround for getPropertyItemsData Change

In Serenity 6.1.0, we added an ability to send more metadata, in addition to what available in form / columns properties by changing the format of the JSON returned from Form / Columns scripts.

Before that a form / columns 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 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 see, the items are moved into a items property, and there is an additionalItems property that is unused in this sample. We would like to use these additionalItems to pass some info about items that are not used in the Form or Columns definitions.

For example, if a columns definition has 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 Product editing sample (https://demo.serenity.is/Northwind/Product). It uses SleekGrid's (our SlickGrid rewrite) cell navigation / editing functionality, while the legacy sample uses, well a legacy method.

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

This is made possible by this additionalItems property, passing some server side information about editing in columns script.

We added Q.getFormData(), Q.getColumnsData() (and their Async versions) to support this new format. But, to keep backwards compatibility, we left Q.getForm(), Q.getColumns methods as is, 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 keep compatibility as much as possible, we left them as is, but there is 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 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 the Serenity.Web and Serenity.Scripts are 6.1.0+. Otherwise, as the JSON sent by the server is different than what the script code expects, it might break your forms / grids.

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

class SomeDialog {
    // note, 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 tried to load that form script from the server.

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

We are aware that not many users actually read change log / release notes so 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: [] };
}

Above, we try to identify in getPropertyItemsData method, if the user overriden getPropertyItems in its subclass, while not overriding getFormKey method (e.g. don't have a form), and the default form name calculated from the class name is not available in registered scripts. This will probably handle 99% percent of cases, but if you are reading this, please override getPropertyItemsData instead to be sure.