Previously, when multiple sorted columns were defined in Column.cs
, the sorting order behaved unexpectedly. For instance:
public class SomeColumns
{
[SortOrder(1)]
public string Column1 { get; set; }
[SortOrder(2, descending: true)]
public string Column2 { get; set; }
}
The grid was expected to be sorted by Column1
followed by Column2 DESC
, but it was the opposite: Column2 DESC
followed by Column1
.
This issue was caused by the way sort orders were passed to the client side, where descending orders were represented as negative values (e.g., Column2
had a sort order of -2
).
This has been fixed by introducing a simple Math.abs()
call during the initial sort order calculation (thanks to @RaportTrap, e.g. A. Schlögelhofer).
The node script file tsbuild.js
, responsible for calling esbuild
to compile modular-style TypeScript
code, has now been distributed as an npm package. tsbuild.js
now imports that package, allowing for easier updates and fixes when necessary.
In the TwoLevelCache
system in Serenity, a cache group key-based cache invalidation feature is present, enabling the expiration of items belonging to a common group in the distributed cache, without needing to know the keys for individual cached items.
For example, if you have cached data derived from UserRow
records, like individual user information cached by id/username, it's possible to expire all such items when something in the User
row changes.
The ExpireGroupItems
method changes the generation number
of the group
, automatically expiring all cached items that use the old generation number
. This is called by the InvalidateOnCommit
extension method within the Save/Delete handlers after the transaction is committed.
Previously, during a batch update where multiple records were updated in a transaction, group generation changes could occur multiple times for the same entity type. This caused the distributed cache to be updated unnecessarily. An optimization was introduced to bundle those generation changes into a single change per group key.
This optimization worked correctly until the transition to .NET dependency injection. Due to the porting to .NET 5 DI, it was observed that this optimization was causing unnecessary group key generation changes originating from unrelated transactions.
For example, if a change was made in the Users
table, this helper would reset only the Users
group generation, as expected. However, if a subsequent call modified the Language
table, it would also reset the cache for Users
in addition to the Language
table.
This issue has been resolved, but it might have some side effects. As the cache is no longer reset for unrelated entities, you may occasionally notice that it used to randomly reset the cache before the fix but no longer does so.
Recently, we received error reports from our users regarding sergen
being unable to connect to the SQL database due to a certificate issue. The solution to this problem was adding TrustServerCertificate=true
to the connection string.
Interestingly, the portal could connect to the SQL server without needing that setting. In response to this, we conducted an investigation to identify the cause. The only notable difference we found was related to the version of the Microsoft.Data.SqlClient
package. While sergen
had a direct reference to Microsoft.Data.SqlClient 4.1.0
, StartSharp
/Serene
indirectly used version 2.0.1
via FluentMigrator
and Serenity.Data
packages.
We discovered the following document on the internet:
Released: General Availability of Microsoft.Data.SqlClient 4.0
This document highlighted a crucial change; in versions 4.x+
, Encrypt=true
, meaning wire encryption for the connection between the client and SQL server, became the default setting for Microsoft.Data.SqlClient
. Encryption necessitates the presence of a certificate, and most SQL server installations have local untrusted or development certificates. Some older versions do not even support encryption.
As the document advises, it's recommended to install a trusted certificate on your SQL server. If you cannot do this, you may use Encrypt=false
in your connection string or TrustServerCertificate=true
. However, these settings are not usually recommended for security reasons.
We acknowledge the reasons behind this change, but it represents a significant breaking change, which uniquely affected sergen
as it was using 4.1.0
. Consequently, most users assumed that the issue was with sergen
itself.
In the future, we plan to use the latest versions of Microsoft.Data.SqlClient
, which is currently 5.x
. However, we understand that most users do not read the change logs. If we were to update the package, it could potentially cause problems for them, particularly if these issues surfaced in a production environment. As a result, for the time being, we are reverting to the latest 3.x
release, specifically 3.1.1
, for both sergen
and Serenity.Data
.
You have the option to update the Microsoft.Data.SqlClient
version in your application or set Encrypt=true
in your connection strings to use encrypted connections (which is recommended). Alternatively, you can wait for our package update to see if it resolves the issue.
As you may already know, we have been working on ES module support since version 6.1. Previously, users had to manually run node tsbuild --watch
in their project directory during development to enable compile-on-save support, similar to TypeScript's compileOnSave
option.
To streamline this process, we've introduced the NodeScriptRunner
and a relevant IApplicationBuilder
extension method to handle this task automatically. Now, during application startup, the watching process is initiated automatically, and you will receive output from esbuild
in your console log. When the application stops, the background watch process should also terminate.
The blog post detailing the transition to ES modules has been updated to reflect this new feature: Read more
This feature comes pre-enabled in projects created from StartSharp 6.2.6+. For others, you can enable it by following the instructions provided in the blog post.
In-Place Add Dialog Types are Automatically Imported into the *Form.ts
Files Generated by Sergen / Pro.Coder
We offer an InplaceEdit
option along with the corresponding DialogType
property in LookupEditor
attributes. When enabled, it displays a button next to the editor, allowing you to add or edit the records listed in the dropdown editor.
When the DialogType
is not explicitly specified, we use the lookup key, as long as they match. For example, if the lookup key is Administration.Role
and the dialog class is MyProject.Administration.RoleDialog
, the system handles it seamlessly.
In the context of namespaces, finding a dialog class by its full or partial name was possible by searching under the global scope (e.g., window
). However, with the shift to ES module-style dialogs, they are not loaded into the global scope. They may not even be loaded at all unless explicitly imported somewhere.
This transition to ES modules posed some challenges for the in-place add feature, particularly when clicking the in-place button. What worked before may have seemed to stop working.
To address this, we've implemented a workaround. As long as you use the corresponding Form.ts
files in your calling dialogs (which sergen
automatically does when generating the code for the first time), the system will handle the dialog types seamlessly.
// UserDialog.ts
import { UserForm, UserRow, UserService } from "../";
//...
@Decorators.registerClass()
export class UserDialog extends EntityDialog<UserRow, any> {
// ...
protected form = new UserForm(this.idPrefix);
In the example above, we import UserForm
and use it. Keep in mind that importing is not enough; you also need to reference or use it somewhere, as esbuild
may remove the import from the output if it's unused.
Suppose you've added InplaceAdd = true
to the UserForm.cs
roles property:
[FormScript("Administration.User")]
public class UserForm
{
//...
[LookupEditor(typeof(RoleRow), Multiple = true, InplaceAdd = true)]
public List<int> Roles { get; set; }
}
For this to work, there must be a way for the Roles editor
in UserDialog
to locate the RoleDialog
class.
This form definition in C# is serialized to JSON and passed to the client side. Therefore, the only thing the Roles editor
knows about the dialog type is a simple string: Administration.Role
.
When you click the in-place add button in UserDialog
, it attempts to find YourProject.Administration.RoleDialog
, but it can't. This is because the role dialog is defined within a module, not in the global scope:
A manual workaround involves manually importing RoleDialog
from the UserDialog
:
// UserDialog.ts
import { UserForm, UserRow, UserService } from "../";
import { RoleDialog } from "../Role/RoleDialog";
RoleDialog.name.toString();
//...
@Decorators.registerClass()
export class UserDialog extends EntityDialog<UserRow, any> {
// ...
protected form is new UserForm(this.idPrefix);
This workaround enables the RoleDialog
to be located by the Roles editor
in the UserDialog
.
This simply imports the RoleDialog
and pretends to be using it so that it is not erased by esbuild
/TypeScript.
However, this may not work if the @registerClass
decorator on RoleDialog
did not include the full name:
@Decorators.registerClass()
export class RoleDialog extends EntityDialog<RoleRow, any> {
In the sample above, Serenity type registration knows nothing more than the class name is RoleDialog
. Therefore, it is not possible to match this class with an Administration.RoleDialog
since there could be multiple Role
dialog classes in different modules.
But in namespaces mode, the class was placed in the StartSharp.Administration
namespace. So Serenity could find its full name by scanning the window
(this is normally done on jQuery's ready event). This is not possible with ES modules.
To address this, let's add the full name to the registerClass
call:
@Decorators.registerClass('StartSharp.Administration.RoleDialog')
export class RoleDialog extends EntityDialog<RoleRow, any> {
Now, we have our RoleDialog
displayed properly:
Starting from Sergen
version 6.2.6, it will automatically add the full name to the registerClass
decorators it generates. However, for the existing dialogs that you convert manually, you'll need to set the full name yourself as well.
Nonetheless, even after doing this, we still need to "fake import" the RoleDialog
class into the UserDialog.ts
file.
To simplify this process for our users who are accustomed to having the in-place add feature work as it did before, we are now generating these "fake imports" in the UserForm.ts
file:
import { RoleDialog } from "@/Administration/Role/RoleDialog";
export interface UserForm {
//..
}
export class UserForm extends PrefixedContext {
//..
}
[RoleDialog]; // In-place add dialog types
This should resolve most such cases, but you still need to add the full name to the registerClass
decorators for the ES module dialog classes you use for the in-place adding feature.
As mentioned in the preceding topic, sergen
now automatically appends the full name to @registerClass
for newly generated modules, as it is a necessary step for proper registration of ES module classes:
@Decorators.registerClass('StartSharp.Administration.MyNewDialog')
export class MyNewDialog extends EntityDialog<RoleRow, any> {
For your existing classes that you convert to ES modules, please ensure to add the full name during the conversion process, taking into account your project name, module name, and class name.
In StartSharp 6.1.8
, we introduced a SourceMapSecurityMiddleware
that allows you to optionally disable source maps in production.
Previously, we returned a 403, indicating a "not allowed" response, which led to some warnings being displayed in the browser console. To address this, we have now updated it to return an empty source map with an HTTP 200 status code, instead of a 403 response.