SJS.Permalink
1.0.6
dotnet add package SJS.Permalink --version 1.0.6
NuGet\Install-Package SJS.Permalink -Version 1.0.6
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SJS.Permalink" Version="1.0.6" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SJS.Permalink" Version="1.0.6" />
<PackageReference Include="SJS.Permalink" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SJS.Permalink --version 1.0.6
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: SJS.Permalink, 1.0.6"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SJS.Permalink@1.0.6
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SJS.Permalink&version=1.0.6
#tool nuget:?package=SJS.Permalink&version=1.0.6
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
SJS.Permalink – permanent links for files (Dynamicweb 10)
WordPress-style media permalinks for the Dynamicweb Files archive: a folder is enabled for permalinks, every file in it can get a permanent URL with a custom slug, and the file behind that URL can be replaced with a different file at any time without the link changing.
https://www.example.com/permalink/product-brochure → /Files/Documents/Brochure_2026_v3.pdf
What it contains
| Part | Location | Purpose |
|---|---|---|
| Request pipeline | Web/PermalinkPipeline.cs, Web/PermalinkMiddleware.cs |
IPipeline add-in (rank 5000, before the classic frontend) that answers GET/HEAD /{prefix}/{slug}: streams the current file (content type, Content-Disposition: inline; filename=…, ETag/Last-Modified, 304 on conditional requests, Cache-Control: public, max-age=0, must-revalidate) or, in Redirect mode, 302s to the file. Unknown slugs fall through to Dynamicweb's normal 404. …/{slug}.pdf also resolves when the extension matches the current file. |
| Data | Data/ – tables SJS_PermalinkFolder, SJS_Permalink, SJS_PermalinkHistory |
Enabled folders, permalinks (slug, current file, download name, note, active, hits, last access) and the replacement history. Created automatically on first use. |
| Rules | Services/PermalinkService.cs, Services/SlugHelper.cs, Services/FilePaths.cs |
Folder enablement is inherited by sub folders. Slugs are lower case a-z 0-9 - _ . with / for sub paths, unique, suggested from the file name (Product Brochure 2026.pdf → product-brochure-2026, -2, -3… when taken). All lookups use an in-memory cache (5 min, invalidated on every change). |
| Files area integration | UI/Injectors/AssetListScreenInjector.cs |
Injects into Dynamicweb's asset list without modifying it: the folder's Actions menu gets Enable permalinks for this folder / Enable and create permalinks for all files, or – when enabled – Permalinks of this folder, Create permalinks for all files, Disable permalinks for this folder. Each file row in an enabled folder gets Create permalink… / Permalink / replace file… and Open /permalink/…. Sub folder rows get Enable permalinks / Disable permalinks. |
| Screens | UI/Screens/ |
PermalinkEditScreen (slide-over: slug, URL, active, File selector (general file selector, all file types) to replace the file, download name, note; History tab with hits and replacements), PermalinkListScreen (all permalinks or one folder; edit, open, go to folder, delete; bulk delete), PermalinkSettingsEditScreen (enabled, URL prefix, delivery mode, status). |
| Categories & tags (v1.1) | Data/PermalinkRepository.cs (table SJS_PermalinkCategory, columns PermalinkCategoryId, PermalinkTags), UI/Screens/PermalinkCategoryScreens.cs, UI/Queries/PermalinkFilterProvider.cs |
Each permalink has one category (managed under Permalinks → Settings → Categories / Manage categories button) and any number of tags (free text). Tags are edited with Dynamicweb's selector component: selected tags appear as chips with a remove button and Select opens a searchable slide-over listing every tag in use; tags that do not exist yet are typed into Add new tags (comma separated, existing tags suggested while typing). Tags are de-duplicated case-insensitively and take the spelling already in use. The permalink list shows both as columns (tags as badges) and offers filters for Category, Tags, Active and File exists; a category's context menu has Show permalinks. Deleting a category leaves its permalinks uncategorized. |
| Tree | UI/Tree/PermalinkNodes.cs |
Files → Permalinks section (All permalinks, Settings → Categories) and Settings → Areas → Assets → Permalinks. |
| Settings | PermalinkSettings.cs – /Globalsettings/SJS/Permalink/* |
Enabled (default true), Prefix (default permalink; files and admin are refused), Mode (Serve default / Redirect). |
How to use it
- Build the solution. The post-build step copies
SJS.Permalink.dlltoSolmetex/wwwroot/Files/System/AddIns/Local/. - In the backend open Settings → Apps, source Local, and install
SJS.Permalink. Bump<Version>in the csproj for every new build. - Restart the site (app pool recycle). The request pipeline is only picked up at startup; the screens appear immediately.
- Open Files, go to a folder and choose Actions → Enable permalinks for this folder (or Enable and create permalinks for all files to link every existing file right away). Sub folders inherit the setting.
- Right-click a file → Create permalink…. Adjust the suggested slug if wanted and save. Share the URL shown in the editor.
- To replace the file: right-click the file (or open Files → Permalinks → All permalinks) → Permalink / replace file…, pick the new file in the File selector (upload it to the archive first with the normal Upload button), optionally add a note, save. The URL stays the same; the replacement is recorded on the History tab. The old file is left in the archive.
- Settings (Files → Permalinks → Settings): switch the feature off, change the URL prefix, or switch to redirect mode.
Disabling a folder stops new permalinks from being created there; existing ones keep working until they are deleted. Deleting a permalink never deletes a file.
Behaviour worth knowing
- Moving, renaming or deleting a file in the Files area does not update its permalinks (Dynamicweb raises no notification for file operations). The permalink then answers 404 with a short text, the list screen shows a warning icon in File exists, and the editor shows a warning; point it at the new location to repair it.
- The permalink URL is public, like the
/Files/...URL of the file itself. Files that must stay private should not be in an enabled folder. - Hits are counted asynchronously (
Hits,Last openedin the list and editor). - Everything can be driven through the Management API with a bearer token, for example:
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkFolderEnableCommand {"FolderPath":"/Files/Documents","Enabled":true,"CreateForExistingFiles":false}
GET /Admin/Api/SJS.Permalink.UI.Queries.PermalinkByFileQuery?FilePath=/Files/Documents/brochure.pdf
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkSaveCommand {"Model":{"Id":0,"Slug":"product-brochure","FilePath":"/Files/Documents/brochure.pdf","Active":true}}
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkSaveCommand {"Model":{"Id":1,"Slug":"product-brochure","FilePath":"/Files/Documents/brochure-2026.pdf","Note":"2026 edition","Active":true}}
GET /Admin/Api/SJS.Permalink.UI.Queries.PermalinksQuery?FolderPath=/Files/Documents
GET /Admin/Api/SJS.Permalink.UI.Queries.PermalinksQuery?Filter=CategoryFilter|in|1,2;TagsFilter|in|uk (operators: in, ==, <, <=, >, >=)
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkSaveCommand {"Model":{"Id":1,"Slug":"product-brochure","FilePath":"/Files/Documents/brochure.pdf","CategoryId":2,"Tags":["uk"],"NewTags":"2026, print","Active":true}}
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkCategorySaveCommand {"Model":{"Id":0,"Name":"Brochures","Description":"","Sort":10}}
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkCategoryDeleteCommand {"Id":2}
POST /Admin/Api/SJS.Permalink.UI.Commands.PermalinkDeleteCommand {"Id":1}
Implementation notes (Dynamicweb 10.28)
- Custom frontend URLs from an add-in: implement
Dynamicweb.Host.Core.IPipelineand register middleware inRegisterApplicationComponents. Pipelines run ordered byRank; the classic frontend usesint.MaxValue. Exceptions thrown fromRegisterApplicationComponentsare swallowed silently by the host, so keep it trivial. - Adding actions to a Dynamicweb list screen:
ListScreenInjector<AssetListScreen, AssetListDataModel, AssetDataModel>withGetScreenActions()(folder Actions menu) andGetListItemActions(model)(row context menu).ActionGroup.Namemust be set, otherwise the entries render disabled.GetListItemActionsruns once per row – use the cache, not the database. - A
NavigationSectionwith an explicit id builds its tree path fromNamebut is resolved byId, so its nodes never load. Leave the id out; the type's full name is then used for both. - Icons are static members of
Dynamicweb.CoreUI.Icons.Icon(Link,LinkAdd,LinkBroken,ExternalLinkAlt…); invented names fail to compile. - Do not bind a file to a
SelectedMediaproperty for documents: Dynamicweb's default editor for it is the media selector, which filters folders to video/audio extensions and shows a PDF folder as empty. Use astringproperty withSelectorBuilder.CreateFileSelector(value, folder: …, lockedToFolder: false)instead (fixed in 1.0.2). - List filters: implement
FilterProvider<TRowModel>(auto-discovered for the row model), mark the model propertiesisFilterable: true, and useFilterOperator.InwithMultiValueFilter. Values are applied in memory on the mapped rows: anintproperty matches when it is in the selection; aList<string>property matches when any element is in the selection (case-sensitive). The URL/query syntax isFilter=Name|in|a,b;Other|==|True. - Multi-value pickers:
SelectorBuilder.CreateOptionSelector(options, multiselect: true)binds to anIEnumerable<string>model property and renders chips + a searchable slide-over (the same component Dynamicweb uses for user groups etc.). Do not setHinton its options: the hint is appended to the chip label.Text.PredefinedValuesrenders a<datalist>for autocomplete.TagCloudListexists but is unused by Dynamicweb itself and its add-action wiring is undocumented.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Dynamicweb (>= 10.28.15)
- Dynamicweb.Application.UI (>= 10.28.15)
- Dynamicweb.Core (>= 10.28.15)
- Dynamicweb.CoreUI (>= 10.28.15)
- Dynamicweb.Files.UI (>= 10.28.15)
- Dynamicweb.Host.Core (>= 10.28.15)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
### 1.0.4
- Current release