Serenity has a special middleware called DynamicScriptMiddleware that handles any call to the specific routes starting with ~/DynJS.axd/
and ~/DynamicData/
.
The .axd
extension originates from the time when it was defined as an HTTP handler in the classic ASP.NET, which usually preferred .axd
for such handlers, and mapping extensionless URLs in IIS was complicated back then.
DynJS
is the abbreviation for Dynamic JS
as its initial purpose was to only serve dynamically generated Javascript which is now extended to CSS
, in addition to some other types like HTML Templates
, etc.
The contents of these script files were mainly used to pass data from the server side to the client side by setting some global variables. So they were not usual script blocks with functions etc. but merely a few statements to set data.
Recently, the DynamicScriptMiddleware
also started to handle the ~/DynamicData
route to retrieve JSON
data, which is mainly intended for mobile apps.
Even though we call them Dynamic Script
due to historical reasons, it is more like Dynamic Content
today.
IDynamicScript is the abstraction that all the dynamic script (or content) objects should implement:
public interface IDynamicScript
{
string GetScript();
void CheckRights(IPermissionService permissions,
ITextLocalizer localizer);
string GroupKey { get; }
TimeSpan Expiration { get; }
}
The dynamic script object should return its dynamically generated content via the GetScript
method.
The source for the dynamic content can be anything, from a database query; to some data generated from the application code itself via reflection.
CheckRights
is the method the dynamic script should implement to validate if the current user has access to this content. You may think of it as the Authorize
attribute.
GroupKey
is used for cache invalidation and is closely related to the ITwoLevelCache
interface we mentioned in the Caching topic. So if for instance, this is a dynamic script that generates its content from a Country
table, it should return its GroupKey
so that when a new Country is added to that table via a Create
handler, the dynamic script should reload its data.
Expiration
is the duration a dynamic script should be kept in the cache.
The class implementing IDynamicScriptManager is the central location for dynamic scripts.
It contains a dictionary of registered dynamic scripts. Every registered script has a unique registration key
or name
. Some script types that implement the INamedDynamicScript
interface may provide their predefined names, while others must be specified during registration:
void Register(INamedDynamicScript script);
void Register(string name, IDynamicScript script);
When a script is requested first time by specifying its registration name, DynamicScriptManager
calls its GetScript
method to generate the content and caches it.
Next time the same script is requested, it returns the cached data instead of calling the GetScript
method unless the content has expired.
It provides these methods to retrieve dynamic script contents:
string GetScriptText(string name, bool json = false);
IScriptContent ReadScriptContent(string name, bool json = false);
While GetScriptText
directly returns the content as text, ReadScriptContent
returns an IScriptContent object which provides more details like the hash, last generation time, content as a byte array, Brotli/Gzip compressed versions of the content, etc.
The default implementation for IDynamicScriptManager
is registered in Startup.cs
via the services.AddDynamicScripts()
call.
There is a bunch of dynamic script types including but not limited to:
- Columns definitions
- Form definitions
- Lookups
- Data Scripts (like
UserData
,PermissionKeys
) - Distinct Field Values
- Local Text Script
- HTML Templates
- Script bundles
- CSS bundles
We'll visit and explain some of them in the next chapters.
Most of these script types are defined via special attributes like [LookupScript]
, [FormScript]
, etc., discovered via reflection and are automatically registered by services.AddDynamicScripts()
call in the Startup.cs
file.
Script bundles and CSS bundle contents are defined in the appsettings.bundles.json
file.
Local text scripts are lazily initialized and registered when the site is displayed first time in a specific language.
Some users might think that as the name Dynamic
implies, they can use dynamic scripts like Services/Actions
to generate dynamic content on every request, e.g. return a different set of data based on query string parameters, or context variables like the current user, tenant, etc.
Dynamic scripts are generated the first time they are requested, and unless otherwise specified, aggressively cached both on the server and client side, so on every subsequent request the content received will be the same, regardless of the current user or query string parameters.
Even though it is possible to disable caching and apply some workarounds to make them act as if parameterized, you would be losing the primary advantage and misusing dynamic scripts for something that they are not intended to.
If you need parameterized dynamic scripts, we recommend defining proper services instead. For frequently changing data
, dynamic scripts are NOT the right tool for the job
. Think of them like a static dictionary, with dynamic content that is generated once, maybe twice, etc.
We'll try to explain this in more detail in the Lookups
topic.