File upload no longer blocked with 401: removed the [Authorize] attribute layered onto FileUploadController. The framework's login state is a custom encrypted cookie rather than a standard ASP.NET Core authentication scheme, so the attribute made requests validate against the default authentication scheme before reaching the controller — when a host application uses its own site-user cookie as the default scheme, administrators were rejected with 401 and the IsLogin() check inside the controller never ran. Login is now decided solely by IsLogin(), returning 401 when not signed in.
Editor "save remote images" CSRF failure fixed: the editor now fetches an Antiforgery token and sends it in the request header before every write (the header name comes from the server instead of being hard-coded), retrying once with a refreshed token on failure. A failed token fetch is logged to the console instead of failing silently.
📝 [v2.3.0] - 2026.09.17
🔒 Security Hardening
Notification hub privilege escalation fixed: NotificationHub now requires authentication, and a user is only added to the group resolved from the server-side identity; the client-controllable JoinGroup / LeaveGroup methods were removed and the client now connects with a signed ticket
CSRF protection for HtmlEditor uploads: upload and remote-image endpoints now require authentication and an Antiforgery token, returning 400 instead of 500 on failure; the editor sends the token via the X-CSRF-TOKEN header
SSRF hardening for remote images: automatic redirects are disabled and every hop is re-validated (max 3, no HTTPS downgrade); a connect callback pins the validated IP to prevent DNS rebinding; responses are streamed to disk instead of buffered in memory
Data permission for Excel import: import, batch update and batch delete all run through the data-permission check, so forged primary keys can no longer touch other users' data
Private file download: a gated endpoint /api/files/{id} verifies login, tenant, file record and authorization; public files keep the existing static URL behaviour
Path safety: a unified safe-path service performs root containment checks for code generation, upload, download and template paths, plus a whitelist for generated route paths
Login ticket authentication: a new authentication handler lets [Authorize] recognise the framework's existing encrypted-cookie login state
🔐 Multi-Tenant Isolation
No more secondary container: the multi-tenant extension no longer calls BuildServiceProvider(); FreeSqlCloud is registered through a service-provider factory
Concurrency-safe tenant creation: initialisation for the same tenant is serialised so concurrent requests cannot create the same database and tables twice
Tenant code whitelist: tenant codes are validated before they can reach database names, file paths or cache keys
Cache and Redis isolation: system config, permission, select/tree/dictionary caches and chat Redis keys all carry the tenant prefix, so tenants no longer share business caches or locks
⚖️ Approval Consistency
Transactional: submit, approve, reject, revoke and transfer all run inside a database transaction and roll back as a whole on any failure, eliminating half-applied state
Concurrency protection: every state change is a conditional update, so concurrent approve/transfer/revoke can only succeed once and cannot advance a node twice or duplicate approval records
Notification timing: in-app messages and real-time pushes are sent after the transaction commits; a failed commit never notifies success
Server authority: approver, current node, next node and level are all derived by the server from the flow configuration; clients cannot specify them or skip nodes
Transfer eligibility: the transfer target must belong to the current tenant and be enabled, and transferring to the submitter is rejected
XSS-safe comments: approval comments are stored as plain text with HTML entity encoding and are length-limited
Flow configuration validation: level continuity, approver source validity and the node-count cap are validated on load; invalid configuration is refused
Error message containment: failures return a generic message plus a TraceId, with details written to the log only
Audit fields: approval records now capture the operator, before/after status and the flow instance identifier, making transfers and revocations fully traceable
🐛 Bug Fixes
Duplicate bell notifications: fixed the same message being inserted twice when the database unread list and the SignalR push arrived together
PBKDF2 parameters upgraded: hashes now carry a version and iteration count; existing passwords still work and are transparently re-hashed after a successful login
Semantic upload status codes: 401 unauthenticated, 403 forbidden, 400 bad request, 413 too large, 415 unsupported type instead of always returning 500
Two-layer upload size limit: the 10 MB limit is enforced both while parsing HTTP and in the business layer
WeChat anonymous endpoint protection: login, decrypt and mini-program QR endpoints now validate parameters and apply rate limiting and timeouts, and no longer leak upstream error details
Database logger back-pressure: a bounded queue with exponential backoff and recovery replay, plus TraceId, request path, user and tenant fields
Synchronous blocking removed: GetAwaiter().GetResult() and similar sync-over-async waits were eliminated
Configurable scheduler time zone: the time zone is no longer hard-coded and keeps the previous China time-zone behaviour by default
Snowflake startup validation: the worker id stays 6-bit and out-of-range values fail fast with guidance for multi-instance deployments
✨ New Features
Unified security options: a new Security configuration section centralises upload size, redirect count, login lockout, CSRF and rate-limit settings, with a documented example
Global exception handling: API exceptions are converted into a friendly message plus a TraceId, with details written to the log only
📝 [v2.2.18] - 2026.09.08
✨ New Features
Stay on the current page after save: editing existing data no longer jumps back to page one after saving; only new records return to the first page (consistent for “Save and Close” and “Save without Close”)
Database migration guide: a new document explains how to switch from SQLite to MySQL / SQL Server / PostgreSQL or other FreeSql-supported databases
Serilog file logging guide: a new document shows how to add file logging with Serilog while keeping the framework’s database logger (writeToProviders: true)
🐛 Bug Fixes
File upload prompt fixed: any upload exception (disk/permission/image/database, etc.) is now converted into a clear “Upload failed” message, and the upload dialog auto-closes after 60 seconds so the “Uploading…” prompt can never get stuck
Chat “The operation was canceled” error fixed: fixed the cancellation/disposal errors thrown by the SignalR connection and online-presence maintenance in the chat extension
Chat/notification self-connect fixed under strict auth: notifications and chat previously self-connected using the public URL, which failed (all connections down, chat stuck offline, page reload loops) under strong auth (HttpOnly cookie), reverse proxies or LAN-IP access; they now connect to the Kestrel listening address, and a failed connection only degrades those features instead of breaking the page or circuit
Null-safe menu path filtering: admin menu filtering now guards against a null PathLower to avoid layout crashes
Generated page row layout fixed: generated edit fields are now wrapped in a row form-inline container even when there are few fields
MailKit upgraded: the Mail extension explicitly references MailKit 4.17.0 to avoid transitive dependency conflicts with NETCore.MailKit
📝 [v2.2.17] - 2026.08.30
✨ New Features
Save button loading in edit dialogs: clicking either “Save” or “Save and Close” in add/edit dialogs now puts both buttons into a loading state and disables them, automatically recovering on errors (including exceptions) and preventing duplicate submissions
Table shows loading while querying: AdminTable no longer flashes “No data” while a query is in flight; a loading indicator is shown until the query completes, then data or the empty state is displayed
Much faster export: AdminTable export now projects only the table's visible columns (dynamic DTO projection) instead of SELECT *, skips Include/IncludeMany navigation loading during export (new IsExport flag on the query event), and writes Excel directly via MiniExcel with per-column lookup caching; for remote databases, exporting thousands of rows drops from minutes to seconds and the file contains only clean, visible columns
Instant export feedback: a non-blocking “Exporting…” badge appears in the table toolbar immediately on click (server-side state driven, pure CSS) and auto-dismisses when done or on error
Data Protection key persistence: new DataProtectionKeyPath / DataProtectionApplicationName options persist the auth-cookie encryption keys to a fixed directory with a stable application name, so users stay logged in across IIS app pool restarts
🐛 Bug Fixes
Solution build fixed: EasyAdminBlazor.AliyunSms now targets net10.0 instead of net9.0, which broke the whole solution build; all project versions are unified
Message center connection leak fixed: the Widget component now overrides DisposeAsync(bool) to release its SignalR connection when the component is disposed
AdminTable permission wrapping fixed: internal default save/delete handlers are no longer double-wrapped on re-render (the old method-group comparison always returned false)
Enable/disable toggles made reliable: user, menu and org toggles now await the database write and roll back the UI state with an error toast on failure
Redis message persistence hardened: entries that fail JSON deserialization are no longer passed to the database writer
WeChat Pay certificate loading modernized: X509CertificateLoader replaces the obsolete X509Certificate2 constructors; a missing platform certificate now logs a clear error
“Cannot match Roles” user role search fixed: ignored search columns are now extracted before the component’s first render, preventing a race where navigation-collection filters (e.g. Roles) reached FreeSql; pruned dynamic filters no longer leave empty wrapper nodes behind
Unused Session middleware removed from the demo: the app never uses ASP.NET Session, so the “Error unprotecting the session cookie” warnings are gone
Tests modernized: log queue tests await asynchronously instead of blocking
📝 [v2.2.16] - 2026.08.22
✨ New Features
Corporate site Tailwind template: new easyadmin-frontend skill (corporate template in a clean Tailwind CSS style); frontend pages for Home / Services / Products / News / Contact, news with category filtering and comments (captcha + moderation), navigation includes the admin entry; new Service / Product / Feedback entities with admin CRUD pages; seed data for services/products and demo menus (Services / Products / Feedback), blog menu renamed to News Management / News Categories (zh/en); legacy Themes feature removed
EasyAdminBlazor.Mail now supports Tencent Cloud SES: calls the Tencent Cloud API directly (TC3-HMAC-SHA256 signature), no SDK required
Mail provider switching: new Mail:Provider config (Smtp / SendCloud / TencentCloud), defaults to Auto (SendCloud → Tencent Cloud → SMTP); callers can switch without code changes
Template emails: in Tencent Cloud mode, SendTemplateEmailAsync takes the numeric template ID and template parameters are serialized to TemplateData JSON automatically
Admin config: the system config page adds SendCloud / Tencent Cloud parameter groups, each with its own test-email button; when Mail:Provider is explicitly set, only the matching provider's settings are shown (all groups are shown in Auto mode)
Per-call provider: SendEmailAsync gains an optional provider string parameter ("Smtp" / "SendCloud" / "TencentCloud"); providers are resolved by the Mail extension so Core stays provider-agnostic and new providers don't require a Core release
Data-driven config page: the test-email feature is driven by contributor declarations (IsEmailProvider / EmailRequiredKeys); adding a new email provider only requires a new contributor class in the Mail extension, with no /Admin/Config changes
Data permission auto-enabled: AdminTable / AdminMultiSelect / AdminSelectTable automatically enable data permissions when the entity implements IDataPermission + IEntityCreated, with no per-page configuration required
Official site link in the public footer: a link to the official site is added at the bottom of the public layout
AI assistant configuration guide: new doc/AI助手配置.md (with an English version) covering the easyadmin-conventions / easyadmin-frontend skills and corporate site templates for ChatGPT / Cursor / Claude
🐛 Bug Fixes
Tencent Cloud business errors no longer mistaken for success: Tencent Cloud API 3.0 returns HTTP 200 for business errors, which are carried in the Response.Error body; these are now detected and reported correctly
Send failure reasons written to error log: failures from SendCloud / Tencent Cloud are logged at Error level (the database error log only records Error and above)
Official domain typo fixed: easyadmim corrected to easyadmin (admin layout, home page, README, docs)
Tenant primary key conflict fixed: removed the legacy SysTenant.Id AOP branch that ToLower()ed the long key and skipped snowflake ID generation, fixing conflicts when adding a second tenant
Corporate site menu seeding made idempotent: migrated legacy /Admin/Service paths and unified Path to Admin/* to avoid duplicate menu insertion; home-page doc parsing regex and csproj cleanup
AuthPath stale state fixed: AuthPathSuccess is now updated in sync when AuthPath carries button parameters, so the page body no longer shows stale state
Redis subscription exception guard: RedisService.Subscribe no longer breaks the subscription when deserialization fails
Nullable and warning cleanup: HexToBytes return type corrected to byte[]?; nullable and unassigned warnings in Chat.razor / FileUploadController / Generator.Model eliminated; removed a leftover Console.WriteLine in MainLayout
Changelog display fixed: the changelog now renders correctly on the admin home page
🔧 Refactoring / Other
Solution slimmed down: removed EasyAdminBlazor.Api.Host and EasyAdminBlazor.Application projects; blog entities Article / Classify / Comment moved into EasyAdminBlazor.Test/Entities/Blog (namespace unchanged); solution and csproj references updated; skills and docs updated with layered-architecture notes (shared layer + multi-host entry model), local path references removed
Version unification: EasyAdminBlazor / EasyAdminBlazor.Core / EasyAdminBlazor.Mail package versions unified to 2.2.16
📝 [v2.2.15] - 2026.08.15
✨ New Feature: Admin Theme Color Switching
Theme color switcher: a palette dropdown in the header with 5 presets (Default Blue, Fresh Green, Elegant Purple, Vibrant Orange, China Red); switching takes effect immediately and persists locally
Default theme color option: EasyAdminBlazorOptions.DefaultThemeColor configures the system default color, applied when the user has not manually chosen
Full component theming: primary buttons, tab active states, menus, calendars, form focus, dropdown selection, TinyMCE editor, link colors etc. follow the theme color in both light and dark modes
Login page theme sync: the login page applies the saved theme color or configured default color, consistent with the admin console
🐛 Bug Fixes
User batch operation logic fixed: no longer executes when role/department/password is not selected; empty-selection guard added
Delete reference protection: roles referenced by users, menus referenced by roles, and orgs with children/users are blocked from deletion to avoid orphaned data
Role save guard: saving a role without any menu permission now shows a prompt instead of saving an empty permission set
Menu "show buttons" state fixed: checkbox state now matches the actual filter logic
Dictionary import parent linkage fixed: dictionary items no longer all attach to the last category when importing multiple categories
Config page fixes: test email validation now only checks SMTP settings; SMS test now reports success/failure/exceptions
Task scheduler fixes: next-run time is filled server-side to avoid index-out-of-range; editing an existing task preserves its round count
Profile sync after save: nickname/email/mobile update immediately in the header without refresh
File upload empty-list guard: no longer reports "0 files uploaded successfully" when nothing was selected
Select component cache fixed: cache key changed from expression reference hash to expression string, resolving cache-miss-every-time repeated DB queries
Code generator resilience: assembly scanning tolerates ReflectionTypeLoadException so the page always opens
Login page theme applied: the login page now applies the saved theme color or configured default color, matching the admin console
Tab title localization: /Admin tab title supports Chinese/English (控制台/Home)
📝 [v2.2.14] - 2026.08.14
⬆️ Dependency Upgrades
BootstrapBlazor upgraded to 10.9.2: fixed compatibility with the deprecated ColumnVisibleItem API
FreeSql packages unified to 3.5.311 (FreeSql.Extensions.AggregateRoot, FreeSql.Extensions.JsonMap, FreeSql.Provider.Sqlite, FreeSql.Provider.MySqlConnector); FreeSql.Cloud stays at the latest 2.0.1
🔒 Security Fixes
Fixed stored XSS in internal messages: message/notification content is HTML-sanitized server-side before rendering (scripts, event attributes, javascript: URLs removed); chat markdown rendering now HTML-encodes raw input
Fixed data-permission bypass in AdminSelectTable: record backfill by selected Id now applies the same data-permission filter, preventing low-privilege users from reading records out of scope
Fixed ChatHub identity spoofing: SignalR connections are validated against the login cookie, and message senders must match the authenticated user, so a forged userid can no longer impersonate other users
Custom data permissions now fail closed: invalid/unparsable custom org config yields "no visible data" instead of leaking all records
Login page hardening: UAParser singleton reuse, unknown fallback on IP parse failure, in-process lock for login failure counting, removed captcha debug output
🐛 Bug Fixes
Fixed forced password-change dialog crash: DialogService.Show is deferred until after the first render so BootstrapBlazorRoot is registered; the dialog now closes and auth state refreshes after a successful password change
Fixed print module SQL dialect compatibility: pagination uses TOP/LIMIT/FETCH FIRST per database type, queries are parameterized, and errors are no longer silently swallowed
Fixed report designer script loading: replaced eval + synchronous XHR with standard <script> injection (CSP-safe, loaded once); generated HTML field names are escaped
Fixed message deletion overreach: deleting a message only removes the current user's association
Fixed password blank-out on user edit: leaving the password field empty no longer overwrites the original password
Fixed protected rows being deleted in batch delete: administrator/super-admin rows are excluded before deletion
Fixed stale permission cache version: permission cache is invalidated immediately after changes
Fixed draft residue when saving without closing
Fixed unvalidated Cron expressions: new tasks are pre-validated, invalid expressions fall back to a 5-second interval
Fixed chat resource leaks: timer and SignalR connection disposal completed, beforeunload reference released
Fixed HttpClient leak in mail service: switched to IHttpClientFactory-managed connections
Fixed new tenant admin not forced to change password
Fixed null reference when deleting an empty selection in AdminTable
✨ Other Improvements
GetConfig no longer throws and caches results for 1 minute
Explicit data source for the tenant database-type dropdown; removed the unused "HTTP request / clean task data" template from the task scheduler
Added XSS sanitizer regression tests; test count 156 → 160
📝 [v2.2.12] - 2026.06.17
🖨️ NEW: Print Template Designer (Major Feature)
Visual Print Template Designer: New /Admin/PrintTemplate page for designing print templates through a visual interface
AdminTable Integration: If an entity has corresponding print templates, AdminTable automatically shows a "Print" button — select records and print with one click
Print Service Abstraction: New PrintService that can be called from anywhere
🎨 Designer Enhancements
2-Column Layout Button: One-click insertion of two-column layouts in the template designer
i18n Support: Complete Chinese/English translations for the print module (zh.json / en.json)
🐛 Bug Fixes
Fixed issue where the left sidebar menu could not be minimized
[v2.2.11] - 2026.06.13
♻️ Architecture Refactoring
Auth Middleware Split: Refactored EasyAdminAuthStartupFilter (120-line inline middleware) into three independent classes
UAParser Singleton: AuthService now injects UAParser as a singleton via constructor, avoiding regex recompilation on every login
OperationLogAttribute Parameter Truncation: Serialized parameters capped at 2000 characters, auto-truncated to prevent memory exhaustion
DatabaseLoggerBackgroundService Exception Safety: Added general exception handling to prevent the log service from crashing permanently on unexpected errors
🛡️ Security Enhancements
SSRF Bug Fix: FileSecurityValidator.IsSafeRemoteUri incorrectly used HasFlag on UriHostNameType (non-[Flags] enum), causing private IP addresses to bypass security checks
Admin route secret middleware now has more controllable pipeline ordering
🌐 Internationalization
Enum Values Anglicized: All Chinese enum members renamed to English (integer values unchanged, backward compatible)
Fixed draft timer not restarting: In OnSaveWithoutClose, the _restartDraftAfterClose flag was set after the dialog close callback, preventing the auto-save timer from ever restarting. Manual draft save was also unresponsive.
Fixed Navigation Property Nullable Annotations: SysUser.Org, SysOrg.Parent, SysMenu.Parent and other navigation properties properly marked as nullable, eliminating null! abuse
[v2.2.10] - 2026.06.07
🎉 New Features
New Code Generator: Visually generate CRUD pages (only in local development environment)
The old version of the code generator has been deprecated. Please manually add the following URL as a backend menu: Admin/CodeGenerator
AdminTable Draft Features
EnableDraft: Auto-save form data to localStorage
EnableSaveWithoutClose: Keep window open after save
DraftAutoSaveInterval: Custom save interval (default 30 seconds)
Smart Draft Recovery: Only prompt when content changed, auto-clean unchanged drafts
New "Save Without Closing" button
🔧 Improvements
Split AdminContext into multiple partial classes for cleaner code
AdminFileInput component now supports direct path copying
Database logging changed to async queue writes to reduce request blocking
Updated AdminTable, Menu Management, Data Permission, Project Introduction docs
[v2.2.9] - 2026-05-30
Added 🎉
Added remote login logout option
Improved English translations for all modules, localization enabled by default.
Breaking change: SysMenuType and DataPermissionType enum types changed. After upgrading to this version, please manually execute: migrate_enum_v.2.2.7.sql
[v2.2.6] - 2026-05-29
Added 🎉
Added changelog display on the admin dashboard
Updated sample module content
Fixed 🐛
Prevent Chat timer from accessing services after DI disposal causing ObjectDisposedException
Added IsAdmin property to AdminContext
Fixed issue where e.Select.OrderByDescending in OnBeforeQuery was not working
Fixed login not obtaining IP address
Fixed issue where inline Switch could still be toggled without edit permission
Fixed BootstrapBlazor edit dialog submit button stuck in loading state
Fixed login failure when EasyAdminBlazor.Scheduler extension is not installed
Refactored 📦
Refactored default sorting logic
Optimized FreeSql registration in non-multi-tenant mode
Synced NuGet package versions
Documentation 📝
Added extensive documentation: AdminTable, Multi-Tenant, Localization, Data Permission, File Management, etc.
Updated quick start documentation
Added project introduction documentation
v2.2.0 — 2026-05-23
Refactored: FreeRedis provided as an extension (EasyAdminBlazor.Redis)
Refactored: Scheduled tasks provided as an extension (EasyAdminBlazor.Scheduler)
Refactored: Multi-tenancy provided as an extension (EasyAdminBlazor.MultiTenant)
Refactored: Caching provided as an extension (EasyAdminBlazor.FusionCache)
Refactored: FusionCache provided as an extension project
v2.0.x — 2026-05-22 ~ 2026-05-23
Added: WeChat Pay
v2.0.x — 2026-04-09 ~ 2026-04-15
Refactored: Architecture layering, separated data from UI to accommodate multi-layer architecture
Added: Added login city record
Optimized: Fully adapted for mobile devices
Fixed: Removed BootstrapBlazor's WebClient to resolve timeout exceptions
Enhanced: AdminSelectEntity.razor now supports binding to any field
Fixed: Save button could not be clicked again after a save exception
Fixed: Corrected OrgId hardcoding issue, now prioritizes Column(Name=value) for Flags type matching
Enhanced: Table filtering and advanced search now support Flags enums
v2.0.x — 2026-03-01 ~ 2026-03-20
Added: Added permission control for file upload Picker
Added: Access admin backend via security code
Added: Added SendCloud email delivery
Fixed: Resolved task scheduling failure in multi-project scenarios
v2.0.x — 2025-12-31 ~ 2026-02-22
Optimized: Fixed TinyMCE not being able to modify image alt text
Added: Menu show/hide option
v2.0.3 — 2026-01-03
Integrated: Directly integrated FusionCache
Enhanced: Added GetOrSet method to cache
v2.0.1 — 2026-01-01
Enhanced: Admin list added quick toggle for enabled/disabled status
Enhanced: Encryption key is now customizable
Enhanced: Encrypted login Cookie
Enhanced: CAPTCHA and login error count stored in Redis when configured, otherwise in-memory
v2.0.0 — 2025-12-31
Upgraded: Upgraded to .NET 10
Upgraded: Upgraded all NuGet packages to latest versions
v1.4.7 — 2025-06-06 ~ 2025-11-25
Added: Upload parameters are configurable
Enhanced: Changed tree component's sort property to string type
Fixed: Fixed issue with fuzzy search returning no results