Serenity 6.4.2 Release Notes (2022-12-09)
Set Default for TOptions Generic Argument for Widget as Any (6.3.3)
The Widget
class which is the base class for all UI components was defined as:
class Widget<TOptions> {
// ...
}
Now we have a default for the TOptions
generic argument:
class Widget<TOptions = any> {
}
So it is possible to use it like "Widget
" instead of "Widget<any>
".
If you are getting an error like:
Generic type `Widget<TOptions>` requires 1 type argument(s)
You should update "@serenity-is/corelib
" package in your package.json
file to the latest version (6.3.3 or higher), execute "npm install
", then restart Visual Studio.
Added Option to Disable Module Re-Exports in sergen.json
While generating ServerTypes
for your modules, Sergen
also generates module re-exports or module indexes like the following:
This allows importing multiple types from a module with one import statement:
import { RoleRow, UserColumns, UserRow, UserService } from "@/ServerTypes/Administration";
But this also means your Grid.ts
file will have an indirect dependency on all the types re-exported from the Administration.ts
file. In this sample LanguageColumns
and RoleColumns
, etc. will also be imported even if we don't use them.
Even though tree-shaking should reduce its effects, you may not want it in some cases. We added an option to disable these re-export files in sergen.json
if desired:
{
"ServerTypings": {
"ModuleReExports": false
}
}
Better Detection for Formatter Types for Client Types Transform (6.3.5)
In some cases, especially when your custom formatter type is defined in an ESM module, attributes corresponding to the formatter types would not be generated.
We now check for more interface matches including:
@serenity-is/corelib:Formatter
@serenity-is/corelib/slick:Formatter
and more attribute types:
@serenity-is/corelib:Decorators.registerFormatter
Decorators.registerFormatter
registerFormatter
Fixed SleekGrid Auto Column Hints Plugin
SleekGrid
is our ES6 rewrite of the SlickGrid. We tried to drop jQuery
dependency and make it optional. This means if jQuery
is available in the global scope, SleekGrid
can make use of it.
As we used the addEventHandler
calls to bind events, as it works both with and without jQuery, there were some unexpected side effects.
We rewrote the core and grid classes in ES6
but not all plugins are converted to ES modules yet. Some of them, including the AutoTooltips
plugin, are still used from legacy SlickGrid code, and they are dependent on jQuery and its synthetic event system.
Because of this, tooltips were not shown when hovering over cells with overflowed text content.
We are now using jQuery.on
to bind events when jQuery
is available instead of the addEventHandler
calls to fix such issues.
Fixed RTL Mode Scroll Column Rendering (6.3.6, SleekGrid 1.4.4)
SlickGrid
did not originally support RTL
mode. We modified the original SlickGrid code years ago to add experimental RTL support.
As reported in this issue:
https://github.com/serenity-is/Serenity/issues/6595
SleekGrid
was not rendering properly when doing horizontal scroll in RTL and mobile mode, e.g. narrow screens.
We thought it was a bug introduced by our SleekGrid
rewrite, but that was not the case.
Seems like this bug was there for years, also with our old SlickGrid
code and it was reported just now.
The reason was due to the browser returning scrollLeft
as a negative number in RTL mode. We now fixed that issue by taking the absolute value of the scrollLeft
.
Added XML Docs for Serenity.Net.Services / Serenity.Net.Web
We had XML doc comments for Serenity.Net.Core
, Serenity.Net.Data
, Serenity.Net.Entity
libraries, but not for Serenity.Net.Services
and Serenity.Net.Web
.
All these libraries now have XML comments, and the API reference docs (https://serenity.is/docs/api/dotnet/Serenity.Net.Web/README) have descriptions for all classes/methods/properties.
Updated Serenity Docs Topics for .NET 6 and ES Modules
Some of the docs in the documentation site were still for old Serenity versions or were written for Namespace
style instead of ES Modules
style like our Movie Tutorial
.
Serene is now also using ES modules by default and the Movie Tutorial
is updated to reflect this.
We updated many topics including Introduction
, DI
, Configuration
, Authentication
, Localization
, Caching
, and Connections
.
There are still some that need to be updated but we are hoping to complete them as soon as possible.
jQuery Path in the Serenity.Assets
Package is Changed
Normally jQuery
was included in the following path:
// appsettings.bundles.json
"~/Serenity.Assets/Scripts/jquery-{version}.js",
The Scripts
folder and {version}
suffix come from the jQuery NuGet
packages and their default installation locations. We previously used third-party NuGet
packages to obtain static script libraries, but we have since switched to using the Libman
tool (Microsoft Library Manager) and our Serenity.Assets
package to install and update static scripts such as Bootstrap
and jQuery
as web assets. We have kept the original file structure for compatibility purposes.
jQuery` is now located at:
// appsettings.bundles.json
"~/Serenity.Assets/jquery/jquery.js",
You should update your includes in the appsettings.bundles.json
file accordingly.
Bootstrap and jQuery Are Updated
We are now using Bootstrap 5.2.3 and jQuery 3.6.1. Bootstrap is in the same location as before in the Serenity.Assets
package but you should change the jQuery
path as mentioned above.
Introduced Full Set of Undelete Handler Related Types and Behaviors (6.4.0)
We had the request handler and service behavior systems for Retrieve
, List
, Save
and Delete
operations but we didn't have similar interfaces and service behaviors for the Undelete
operation.
Starting from 6.4.0, there is now a full set of dependency injectable request handlers and relevant service behaviors for undelete operations.
namespace Serenity.Services
{
public interface IUndeleteBehavior
{
void OnPrepareQuery(IUndeleteRequestHandler handler, SqlQuery query);
void OnValidateRequest(IUndeleteRequestHandler handler);
//...
}
public interface IUndeleteHandler<TRow, TUndeleteRequest,
TUndeleteResponse>
: IRequestHandler<TRow, TUndeleteRequest, TUndeleteResponse>
where TRow : class, IRow, new()
where TUndeleteRequest : UndeleteRequest
where TUndeleteResponse : UndeleteResponse, new()
{
TUndeleteResponse Undelete(IUnitOfWork uow,
TUndeleteRequest request);
}
//....
Breaking Change - IUploadStorage.CopyFrom
and IUploadStorage.WriteFile
Method Arguments Changed
Our upload storage abstraction, which is named IUploadStorage
had the following two methods in addition to others:
public interface IUploadStorage
{
string CopyFrom(IUploadStorage sourceStorage,
string sourcePath, string targetPath, bool? autoRename);
string WriteFile(string path, Stream source,
bool? autoRename);
The last parameter, which was named autoRename
was not very clear about its purpose.
If it was passed false
, the upload storage was expected to raise an exception if a file at the same path exists.
If it was passed true
, the upload storage would try to find a suitable name like File (2).jpg
if a file at the same path exists.
If it was passed null, the upload storage would overwrite the existing file.
We now have an OverwriteOption
enumeration:
public enum OverwriteOption
{
/// <summary>Raise an error</summary>
Disallowed = 0,
/// <summary>Overwrite the target file</summary>
Overwrite = 1,
/// <summary>Try to find a suitable name</summary>
AutoRename = 2
}
and use that instead of the autoRename
flag:
public interface IUploadStorage
{
string CopyFrom(IUploadStorage sourceStorage,
string sourcePath, string targetPath, OverwriteOption overwrite);
string WriteFile(string path, Stream source,
OverwriteOption overwrite);
This is a breaking change only if you implemented a custom IUploadStorage
object before. Otherwise, it should not cause any issues as we also added extension methods (though obsolete) with the autoRename
flag for compatibility.
Switched to PNPM from NPM for Serenity Projects
This should not affect you if you are not using Serenity
as a submodule or copying common-features
, pro-features
, etc. projects into your solution.
PNPM
is a better, faster, more efficient, and no-nonsense alternative to NPM. After having some issues with NPM versions we decided to give PNPM
a go:
StartSharp and Serene still use npm
as we wanted to test PNPM
a bit more before asking our customers to switch.
You may install PNPM
via NPM:
npm install -g pnpm
or follow the installation instructions at:
Interface Checks Via Q.isAssignableFrom
Use Type Name Instead of Relying Only on Object References
We had some issues while writing JEST
tests for Serenity using esbuild
. Some of the errors were about some types like formatters not found.
Upon further inspection, we determined that @serenity-is/corelib/q
, e.g. the Q
helper had multiple copies in memory and some interface classes had multiple copies as well.
This was caused by Q
and some other packages getting bundled into multiple intermediate output files by esbuild
.
It was mostly a configuration issue, but we thought this might happen for our users in the future even if we fixed the configuration for ourselves.
So in addition to fixing the esbuild configuration, we also added a workaround that allows discovering formatter types and marker interfaces, etc. even if they have multiple copies in memory, e.g. they are included in multiple bundles.
To do this, we are checking their registration names, e.g. two separate ISlickFormatter
interface copies that both have Serenity.ISlickFormatter
as their registration name is now considered a match. So, FormatterTypeRegistry.get()
can now locate formatters defined in another bundle even if the marker interfaces for them are different.
SixLabors.ImageSharp Dependency is Abstracted (6.4.0)
We are using SixLabors.ImageSharp
instead of System.Drawing
which does not work properly in Linux
etc.
The library is used mainly for upload image scaling and thumb generation.
Unfortunately, SixLabors.ImageSharp
changed the licensing terms recently, and we may have to switch to another library if it will become incompatible with our MIT (Serenity/Serene) / commercial (StartSharp) licenses.
So, instead of having a hard-coded dependency on SixLabors
or another library, we abstracted the dependency via the new IImageProcessor
interface:
(int width, int height) GetImageSize(object image);
object Load(Stream source, out ImageFormatInfo formatInfo);
object Scale(object image, int width, int height,
ImageScaleMode mode, string backgroundColor, bool inplace);
void Save(object image, Stream target, string mimeType,
ImageEncoderParams encoderParams);
This interface only contains the most basic methods we need for upload image processing.
It will allow us to switch to another library in the future if required.
You may also use a completely different library if desired via dependency injection, or customize the default one.
UploadProcessor is Obsolete and Replaced with IUploadProcessor Abstraction
The UploadProcessor
class that we use in FilePage.cs
temporary upload method is now obsolete.
You should inject the new IUploadProcessor
interface and use it in the HandleUploadRequest
method:
public class FileController : Controller
{
private readonly IUploadStorage uploadStorage;
private readonly IUploadProcessor uploadProcessor;
public FileController(IUploadStorage uploadStorage,
IUploadProcessor uploadProcessor)
{
//...
this.uploadProcessor = uploadProcessor ??
throw new ArgumentNullException(nameof(uploadProcessor));
}
[NonAction]
private ServiceResponse HandleUploadRequest(HttpContext context)
{
// ...
IUploadOptions uploadOptions = new UploadOptions
{
ThumbWidth = 128,
ThumbHeight = 96,
ThumbMode = ImageScaleMode.PreserveRatioNoFill
};
var uploadIntent = Request.Query["uploadIntent"];
if (!string.IsNullOrEmpty(uploadIntent))
{
// if desired modify uploadOptions here based on uploadIntent
}
var uploadInfo = uploadProcessor.Process(file.OpenReadStream(),
file.FileName, uploadOptions);
}
Added Optional UploadIntent Property to Upload Editor Attributes
The options you specify on an upload editor like ImageUploadEditor(...)
have no effect during temporary uploads as there was no connection between the temporary upload processing and the editor that triggered it.
To explain better, let's say we have such a property defined in one of our rows:
[FileUploadEditor(MinSize = 1_000, MaxSize = 10_000_000)]
public string SomeFile { get; set; }
This property is not supposed to allow files smaller than 1KB and bigger than 10MB.
If you opened the form containing this property and uploaded a file, the first upload will be a temporary one, handled by FilePage.TemporaryUpload
method.
As the FilePage.TemporaryUpload
method has no information about where this temporary file will be used, e.g. which upload editor attribute was used to initiate this temporary upload, it has no way to check the constraints and uses default options:
var processor = new UploadProcessor(UploadStorage, Logger)
{
ThumbWidth = 128,
ThumbHeight = 96
};
There are no file size constraints here other than the thumb width/height to generate. And even if you specify the thumb size in the upload editor attribute it won't be used during temporary upload as well.
When you click the Save
button, the upload behavior kicks in, locates the editor attribute, and checks constraints.
But until clicking the Save
button, the user does not know that the file she is uploading is not conforming to the constraints.
This may not be very critical, but showing the error early to the end user might be helpful, especially when the form is big and has multiple upload editors.
We now have a free-form string UploadIntent
property that you may pass to upload editors, and which they pass back to the temporary upload method via the query string:
// SomeRow.cs
// ...
[FileUploadEditor(MinSize = 1_000, MaxSize = 10_000_000, UploadIntent = "ProfileImage")]
public string SomeFile { get; set; }
// ...
// FilePage.cs
IUploadOptions uploadOptions = new UploadOptions
{
ThumbWidth = 128,
ThumbHeight = 96,
ThumbMode = ImageScaleMode.PreserveRatioNoFill
};
var uploadIntent = Request.Query["uploadIntent"];
if (!string.IsNullOrEmpty(uploadIntent))
{
// if desired modify uploadOptions here based on uploadIntent
switch (uploadIntent) {
case "ProfileImage":
var attr = typeof(SomeRow).GetProperty("SomeFile")
.GetAttribute<FileUploadEditor>();
uploadOptions.MinSize = attr.MinSize;
uploadOptions.MaxSize = attr.MaxSize;
// ...
break;
}
}
Restructured Upload Editor Interfaces and Introduced BaseUploadEditorAttribute (6.4.0)
We started abstracting and restructuring upload editor attributes in 6.3.0 and the interfaces got another overhaul in 6.4.0:
We now have the following interfaces implemented by upload editor attributes and the new UploadOptions
class:
IUploadEditor
- Marker interface for all upload editor attributes. It contains
IsMultiple
,DisableDefaultBehavior
, andUploadIntent
properties.
- Marker interface for all upload editor attributes. It contains
IUploadFileConstraints
- Contains
AllowNonImage
,AllowedExtensions
(default empty, e.g. allow all extensions),ExtensionBlacklist
(file extensions considered dangerous like .exe, .aspx, etc.),ImageExtensions
(set of extensions that are considered images, default is ".jpg, .jpeg, .gif, .png". Image validation is only performed for these extensions.),MaxSize
,MinSize
properties.
- Contains
IUploadFileOptions
- Additional file options like
CopyToHistory
,DisplayFileName
, FilenameFormat, etc.
- Additional file options like
IUploadImageConstraints
- These constraints are checked when a file is considered an image based on its extension. Contains
MinWidth
,MaxWidth
,MinHeight
,MaxHeight
,IgnoreExtensionMismatch
(e.g. when an uploaded .jpg file contains a PNG image),IgnoreEmptyBitmap
(allow 0x0 images),IgnoreInvalidImage
(if an uploaded image is invalid, assume it as a regular file instead of raising an error)
- These constraints are checked when a file is considered an image based on its extension. Contains
IUploadImageOptions
- Additional image options like
ThumbWidth
,ThumbQuality
,ScaleWidth
, etc.
- Additional image options like
All upload editors now implement these interfaces and they derive from a base abstract class named BaseUploadEditorAttribute
which you may also subclass to create a custom upload editor attribute.
There is also a UploadOptions
class that implements all upload interfaces except the IUploadEditor
as it does not apply to temporary uploads.
It also provides the default values for all upload editor attribute properties like ImageExtensions
.
The UploadOptions
class is used in FilePage
to initialize the defaults for temporary uploads and customize it via the UploadIntent
if desired.
A new IFilenameFormatSanitizer
interface that you can inject via DI allows customizing the sanitizing behavior for file name format strings with placeholders like |SomeField|/|AnotherField|
.