Add Claude memory files for all relevant project files (#20959)
* Regenerate delivery api claud memory file for updated file lines and inclusion of Secure Cookie-Based Token Storage * Add delivery api memory file * claude memory file for in memory modelsbuilder project * Claud memory file for Imagesharp project * Claude memory file for legacy image sharp project * Claude memory files for Persistence projects * Remaining claude memory files
This commit is contained in:
@@ -23,7 +23,7 @@ Shared infrastructure for Umbraco CMS REST APIs (Management and Delivery).
|
||||
- `Umbraco.Core` - Domain models and service contracts
|
||||
- `Umbraco.Web.Common` - Web functionality
|
||||
|
||||
### Project Structure (45 files)
|
||||
### Project Structure (46 files)
|
||||
|
||||
```
|
||||
Umbraco.Cms.Api.Common/
|
||||
@@ -55,20 +55,7 @@ Umbraco.Cms.Api.Common/
|
||||
|
||||
## 2. Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
dotnet build src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj
|
||||
|
||||
# Pack for NuGet
|
||||
dotnet pack src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj -c Release
|
||||
|
||||
# Run tests (integration tests in consuming APIs)
|
||||
dotnet test tests/Umbraco.Tests.Integration/
|
||||
|
||||
# Check for outdated/vulnerable packages
|
||||
dotnet list src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj package --outdated
|
||||
dotnet list src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj package --vulnerable
|
||||
```
|
||||
See "Quick Reference" section at bottom for common commands.
|
||||
|
||||
---
|
||||
|
||||
@@ -89,20 +76,22 @@ public class SchemaIdHandler : ISchemaIdHandler
|
||||
|
||||
**Why**: Management and Delivery APIs can customize schema/operation ID generation.
|
||||
|
||||
### Schema ID Sanitization (OpenApi/SchemaIdHandler.cs:32)
|
||||
### Schema ID Sanitization (OpenApi/SchemaIdHandler.cs:24-29, 32)
|
||||
|
||||
```csharp
|
||||
// Remove invalid characters to prevent OpenAPI generation errors
|
||||
return Regex.Replace(name, @"[^\w]", string.Empty);
|
||||
|
||||
// Add "Model" suffix to avoid TypeScript name clashes (line 24)
|
||||
// Add "Model" suffix to avoid TypeScript name clashes (lines 24-29)
|
||||
if (name.EndsWith("Model") == false)
|
||||
{
|
||||
// because some models names clash with common classes in TypeScript (i.e. Document),
|
||||
// we need to add a "Model" postfix to all models
|
||||
name = $"{name}Model";
|
||||
}
|
||||
|
||||
// Remove invalid characters to prevent OpenAPI generation errors (line 32)
|
||||
return Regex.Replace(name, @"[^\w]", string.Empty);
|
||||
```
|
||||
|
||||
### Polymorphic Deserialization (Serialization/UmbracoJsonTypeInfoResolver.cs:31-34)
|
||||
### Polymorphic Deserialization (Serialization/UmbracoJsonTypeInfoResolver.cs:29-35)
|
||||
|
||||
```csharp
|
||||
// IMPORTANT: do NOT return an empty enumerable here. it will cause nullability to fail on reference
|
||||
@@ -144,8 +133,10 @@ dotnet test tests/Umbraco.Tests.Integration/
|
||||
|
||||
### Key Configuration (DependencyInjection/UmbracoBuilderAuthExtensions.cs)
|
||||
|
||||
**Reference Tokens over JWT** (line 73-74):
|
||||
**Reference Tokens over JWT** (line 76-80):
|
||||
```csharp
|
||||
// Enable reference tokens
|
||||
// - see https://documentation.openiddict.com/configuration/token-storage.html
|
||||
options
|
||||
.UseReferenceAccessTokens()
|
||||
.UseReferenceRefreshTokens();
|
||||
@@ -153,23 +144,44 @@ options
|
||||
|
||||
**Why**: More secure (revocable), better for load balancing, uses ASP.NET Core Data Protection.
|
||||
|
||||
**Token Lifetime** (line 84-85):
|
||||
**Token Lifetime** (line 88-91):
|
||||
```csharp
|
||||
// Access token: 25% of refresh token lifetime
|
||||
// Make the access token lifetime 25% of the refresh token lifetime
|
||||
options.SetAccessTokenLifetime(new TimeSpan(timeOut.Ticks / 4));
|
||||
options.SetRefreshTokenLifetime(timeOut);
|
||||
```
|
||||
|
||||
**PKCE Required** (line 54-56):
|
||||
**PKCE Required** (line 59-63):
|
||||
```csharp
|
||||
// Enable authorization code flow with PKCE
|
||||
options
|
||||
.AllowAuthorizationCodeFlow()
|
||||
.RequireProofKeyForCodeExchange();
|
||||
.RequireProofKeyForCodeExchange()
|
||||
.AllowRefreshTokenFlow();
|
||||
```
|
||||
|
||||
**Endpoints**:
|
||||
- Backoffice: `/umbraco/management/api/v1/security/*`
|
||||
- Member: `/umbraco/member/api/v1/security/*`
|
||||
**Endpoints**: Backoffice `/umbraco/management/api/v1/security/*`, Member `/umbraco/member/api/v1/security/*`
|
||||
|
||||
### Secure Cookie-Based Token Storage (v17+)
|
||||
|
||||
**Implementation** (DependencyInjection/HideBackOfficeTokensHandler.cs):
|
||||
|
||||
Back-office tokens are hidden from client-side JavaScript via HTTP-only cookies:
|
||||
|
||||
```csharp
|
||||
private const string AccessTokenCookieKey = "__Host-umbAccessToken";
|
||||
private const string RefreshTokenCookieKey = "__Host-umbRefreshToken";
|
||||
|
||||
// Tokens are encrypted via Data Protection and stored in cookies
|
||||
SetCookie(httpContext, AccessTokenCookieKey, context.Response.AccessToken);
|
||||
context.Response.AccessToken = "[redacted]"; // Client sees redacted value
|
||||
```
|
||||
|
||||
**Key Security Features** (lines 143-165): `HttpOnly`, `IsEssential`, `Path="/"`, `Secure` (HTTPS), `__Host-` prefix
|
||||
|
||||
**Configuration**: `BackOfficeTokenCookieSettings.Enabled` (default: true in v17+)
|
||||
|
||||
**Implications**: Client-side cannot access tokens; encrypted with Data Protection; load balancing needs shared key ring; API requests need `credentials: include`
|
||||
|
||||
---
|
||||
|
||||
@@ -183,10 +195,9 @@ options
|
||||
```csharp
|
||||
catch (NotSupportedException exception)
|
||||
{
|
||||
// This happens when trying to deserialize to an interface,
|
||||
// without sending the $type as part of the request
|
||||
context.ModelState.TryAddModelException(string.Empty,
|
||||
new InputFormatterException(exception.Message, exception));
|
||||
// This happens when trying to deserialize to an interface, without sending the $type as part of the request
|
||||
context.ModelState.TryAddModelException(string.Empty, new InputFormatterException(exception.Message, exception));
|
||||
return await InputFormatterResult.FailureAsync();
|
||||
}
|
||||
```
|
||||
|
||||
@@ -196,23 +207,23 @@ catch (NotSupportedException exception)
|
||||
|
||||
**Issue**: Type names like `Document` clash with TypeScript built-ins.
|
||||
|
||||
**Solution** (OpenApi/SchemaIdHandler.cs:24-29):
|
||||
```csharp
|
||||
if (name.EndsWith("Model") == false)
|
||||
{
|
||||
// Add "Model" postfix to all models
|
||||
name = $"{name}Model";
|
||||
}
|
||||
```
|
||||
**Solution**: Add "Model" suffix (OpenApi/SchemaIdHandler.cs:24-29)
|
||||
|
||||
### Generic Type Handling
|
||||
|
||||
**Issue**: `PagedViewModel<T>` needs flattened schema name.
|
||||
|
||||
**Solution** (OpenApi/SchemaIdHandler.cs:41-49):
|
||||
**Solution** (OpenApi/SchemaIdHandler.cs:41-50):
|
||||
```csharp
|
||||
// Turns "PagedViewModel<RelationItemViewModel>" into "PagedRelationItemModel"
|
||||
private string HandleGenerics(string name, Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
return name;
|
||||
|
||||
// use attribute custom name or append the generic type names
|
||||
// turns "PagedViewModel<RelationItemViewModel>" into "PagedRelationItem"
|
||||
return $"{name}{string.Join(string.Empty, type.GenericTypeArguments.Select(SanitizedTypeName))}";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -258,16 +269,6 @@ return BadRequest(problemDetails);
|
||||
|
||||
## 8. Project-Specific Notes
|
||||
|
||||
### Why Reference Tokens Instead of JWT?
|
||||
|
||||
**Decision**: Use `UseReferenceAccessTokens()` and ASP.NET Core Data Protection.
|
||||
|
||||
**Tradeoffs**:
|
||||
- ✅ **Pros**: Revocable, simpler key management, better security
|
||||
- ❌ **Cons**: Requires database lookup (slower than JWT), needs shared Data Protection key ring
|
||||
|
||||
**Load Balancing Requirement**: All servers must share the same Data Protection key ring and application name.
|
||||
|
||||
### Why Virtual Handlers?
|
||||
|
||||
**Decision**: Make `SchemaIdHandler`, `OperationIdHandler`, etc. virtual.
|
||||
@@ -278,12 +279,7 @@ return BadRequest(problemDetails);
|
||||
|
||||
### Performance: Subtype Caching
|
||||
|
||||
**Implementation** (Serialization/UmbracoJsonTypeInfoResolver.cs:14):
|
||||
```csharp
|
||||
private readonly ConcurrentDictionary<Type, ISet<Type>> _subTypesCache = new();
|
||||
```
|
||||
|
||||
**Why**: Reflection is expensive. Cache discovered subtypes to avoid repeated `ITypeFinder.FindClassesOfType()` calls.
|
||||
**Why**: Cache discovered subtypes (UmbracoJsonTypeInfoResolver.cs:14) to avoid expensive reflection calls
|
||||
|
||||
### Known Limitations
|
||||
|
||||
@@ -318,23 +314,11 @@ private readonly ConcurrentDictionary<Type, ISet<Type>> _subTypesCache = new();
|
||||
|
||||
### Configuration
|
||||
|
||||
**HTTPS** (Configuration/ConfigureOpenIddict.cs:14):
|
||||
```csharp
|
||||
// Disable transport security requirement for local development
|
||||
options.DisableTransportSecurityRequirement = _globalSettings.Value.UseHttps is false;
|
||||
```
|
||||
**HTTPS**: `DisableTransportSecurityRequirement` for local dev only (ConfigureOpenIddict.cs:14). **Warning**: Never disable in production.
|
||||
|
||||
**⚠️ Warning**: Never disable HTTPS in production.
|
||||
### Usage Pattern
|
||||
|
||||
### Usage by Consuming APIs
|
||||
|
||||
**Registration Pattern**:
|
||||
```csharp
|
||||
// In Umbraco.Cms.Api.Management or Umbraco.Cms.Api.Delivery
|
||||
builder
|
||||
.AddUmbracoApiOpenApiUI() // Swagger + custom handlers
|
||||
.AddUmbracoOpenIddict(); // OAuth 2.0 authentication
|
||||
```
|
||||
Consuming APIs call `builder.AddUmbracoApiOpenApiUI().AddUmbracoOpenIddict()`
|
||||
|
||||
---
|
||||
|
||||
@@ -343,7 +327,7 @@ builder
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
# Build project
|
||||
dotnet build src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj
|
||||
|
||||
# Pack for NuGet
|
||||
@@ -351,6 +335,10 @@ dotnet pack src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj -c Release
|
||||
|
||||
# Test via integration tests
|
||||
dotnet test tests/Umbraco.Tests.Integration/
|
||||
|
||||
# Check packages
|
||||
dotnet list src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj package --outdated
|
||||
dotnet list src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj package --vulnerable
|
||||
```
|
||||
|
||||
### Key Classes
|
||||
@@ -361,13 +349,14 @@ dotnet test tests/Umbraco.Tests.Integration/
|
||||
| `SchemaIdHandler` | Generate OpenAPI schema IDs | OpenApi/SchemaIdHandler.cs |
|
||||
| `UmbracoJsonTypeInfoResolver` | Polymorphic JSON serialization | Serialization/UmbracoJsonTypeInfoResolver.cs |
|
||||
| `UmbracoBuilderAuthExtensions` | Configure OpenIddict | DependencyInjection/UmbracoBuilderAuthExtensions.cs |
|
||||
| `HideBackOfficeTokensHandler` | Secure cookie-based token storage | DependencyInjection/HideBackOfficeTokensHandler.cs |
|
||||
| `PagedViewModel<T>` | Generic pagination model | ViewModels/Pagination/PagedViewModel.cs |
|
||||
|
||||
### Important Files
|
||||
|
||||
- `Umbraco.Cms.Api.Common.csproj` - Project dependencies
|
||||
- `DependencyInjection/UmbracoBuilderApiExtensions.cs` - OpenAPI registration (line 12-30)
|
||||
- `DependencyInjection/UmbracoBuilderAuthExtensions.cs` - OpenIddict setup (line 19-144)
|
||||
- `DependencyInjection/UmbracoBuilderApiExtensions.cs` - OpenAPI registration (line 12-31)
|
||||
- `DependencyInjection/UmbracoBuilderAuthExtensions.cs` - OpenIddict setup (line 20-183)
|
||||
- `Security/Paths.cs` - API endpoint path constants
|
||||
|
||||
### Getting Help
|
||||
|
||||
382
src/Umbraco.Cms.Api.Delivery/CLAUDE.md
Normal file
382
src/Umbraco.Cms.Api.Delivery/CLAUDE.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Umbraco.Cms.Api.Delivery
|
||||
|
||||
Headless content delivery REST API for Umbraco CMS. Enables frontend applications to fetch published content, media, and member-protected resources.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
**Type**: Class Library (NuGet Package)
|
||||
**Target Framework**: .NET 10.0
|
||||
**Purpose**: Content Delivery API for headless CMS scenarios
|
||||
|
||||
### Key Technologies
|
||||
|
||||
- **ASP.NET Core** - Web framework
|
||||
- **OpenIddict** - Member authentication (OAuth 2.0)
|
||||
- **Asp.Versioning** - API versioning (V1, V2)
|
||||
- **Output Caching** - Configurable response caching
|
||||
- **Examine/Lucene** - Content querying
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `Umbraco.Cms.Api.Common` - Shared API infrastructure (OpenAPI, auth)
|
||||
- `Umbraco.Web.Common` - Web functionality
|
||||
|
||||
### Project Structure (86 files)
|
||||
|
||||
```
|
||||
Umbraco.Cms.Api.Delivery/
|
||||
├── Controllers/
|
||||
│ ├── Content/ # Content endpoints (by ID, route, query)
|
||||
│ ├── Media/ # Media endpoints (by ID, path, query)
|
||||
│ └── Security/ # Member auth (authorize, token, signout)
|
||||
├── Querying/
|
||||
│ ├── Filters/ # ContentType, Name, CreateDate, UpdateDate
|
||||
│ ├── Selectors/ # Ancestors, Children, Descendants
|
||||
│ └── Sorts/ # Name, CreateDate, UpdateDate, Level, SortOrder
|
||||
├── Indexing/ # Lucene index field handlers
|
||||
├── Services/ # Business logic and query building
|
||||
├── Caching/ # Output cache policies
|
||||
├── Rendering/ # Output expansion strategies
|
||||
├── Configuration/ # Swagger configuration
|
||||
└── Filters/ # Action filters (access, validation)
|
||||
```
|
||||
|
||||
### Design Patterns
|
||||
|
||||
1. **Strategy Pattern** - Query handlers (`ISelectorHandler`, `IFilterHandler`, `ISortHandler`)
|
||||
2. **Factory Pattern** - `ApiContentQueryFactory` builds Examine queries
|
||||
3. **Template Method** - `ContentApiControllerBase` for shared controller logic
|
||||
4. **Options Pattern** - `DeliveryApiSettings` for all configuration
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
See "Quick Reference" section at bottom for common commands.
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Patterns
|
||||
|
||||
### API Versioning (V1 vs V2)
|
||||
|
||||
**V1** (legacy) and **V2** (current) coexist. Key difference is output expansion:
|
||||
|
||||
```csharp
|
||||
// DependencyInjection/UmbracoBuilderExtensions.cs:49-52
|
||||
// V1 uses RequestContextOutputExpansionStrategy
|
||||
// V2+ uses RequestContextOutputExpansionStrategyV2
|
||||
return apiVersion.MajorVersion == 1
|
||||
? provider.GetRequiredService<RequestContextOutputExpansionStrategy>()
|
||||
: provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
|
||||
```
|
||||
|
||||
**Why V2**: Improved `expand` and `fields` query parameter parsing (tree-based).
|
||||
|
||||
### Query System Architecture
|
||||
|
||||
Content querying flows through handlers registered in DI:
|
||||
|
||||
1. **Selectors** (`fetch` parameter): `ancestors:id`, `children:id`, `descendants:id`
|
||||
2. **Filters** (`filter[]` parameter): `contentType:alias`, `name:value`, `createDate>2024-01-01`
|
||||
3. **Sorts** (`sort[]` parameter): `name:asc`, `createDate:desc`, `level:asc`
|
||||
|
||||
```csharp
|
||||
// Services/ApiContentQueryService.cs:91-96
|
||||
ISelectorHandler? selectorHandler = _selectorHandlers.FirstOrDefault(h => h.CanHandle(fetch));
|
||||
return selectorHandler?.BuildSelectorOption(fetch);
|
||||
```
|
||||
|
||||
### Path Decoding Workaround
|
||||
|
||||
ASP.NET Core doesn't decode forward slashes in route parameters:
|
||||
|
||||
```csharp
|
||||
// Controllers/DeliveryApiControllerBase.cs:21-31
|
||||
// OpenAPI clients URL-encode paths, but ASP.NET Core doesn't decode "/"
|
||||
// See https://github.com/dotnet/aspnetcore/issues/11544
|
||||
if (path.Contains("%2F", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
path = WebUtility.UrlDecode(path);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Testing
|
||||
|
||||
**Location**: No direct tests - tested via integration tests in test projects
|
||||
|
||||
```bash
|
||||
dotnet test tests/Umbraco.Tests.Integration/ --filter "FullyQualifiedName~Delivery"
|
||||
```
|
||||
|
||||
**Internals exposed to** (csproj lines 28-36):
|
||||
- `Umbraco.Tests.UnitTests`
|
||||
- `Umbraco.Tests.Integration`
|
||||
- `DynamicProxyGenAssembly2` (for mocking)
|
||||
|
||||
**Focus areas**:
|
||||
- Query parsing (selectors, filters, sorts)
|
||||
- Member authentication flows
|
||||
- Output caching behavior
|
||||
- Protected content access
|
||||
|
||||
---
|
||||
|
||||
## 5. Security & Access Control
|
||||
|
||||
### Three Access Modes
|
||||
|
||||
```csharp
|
||||
// Services/ApiAccessService.cs:21-27
|
||||
public bool HasPublicAccess() => _deliveryApiSettings.PublicAccess || HasValidApiKey();
|
||||
public bool HasPreviewAccess() => HasValidApiKey();
|
||||
public bool HasMediaAccess() => _deliveryApiSettings is { PublicAccess: true, Media.PublicAccess: true } || HasValidApiKey();
|
||||
```
|
||||
|
||||
**Access levels**:
|
||||
1. **Public** - No authentication required (if enabled)
|
||||
2. **API Key** - Via `Api-Key` header
|
||||
3. **Preview** - Always requires API key
|
||||
|
||||
### Member Authentication
|
||||
|
||||
OpenIddict-based OAuth 2.0 for member-protected content:
|
||||
|
||||
**Flows supported** (Controllers/Security/MemberController.cs):
|
||||
- Authorization Code + PKCE (line 53)
|
||||
- Client Credentials (line 112)
|
||||
- Refresh Token (line 98)
|
||||
|
||||
**Endpoints**:
|
||||
- `GET /umbraco/delivery/api/v1/security/member/authorize`
|
||||
- `POST /umbraco/delivery/api/v1/security/member/token`
|
||||
- `GET /umbraco/delivery/api/v1/security/member/signout`
|
||||
|
||||
**Scopes**: Only `openid` and `offline_access` allowed for members (line 220-222)
|
||||
|
||||
### Protected Content
|
||||
|
||||
Member access checked via `ProtectedAccess` model:
|
||||
|
||||
```csharp
|
||||
// Controllers/Content/QueryContentApiController.cs:56-57
|
||||
ProtectedAccess protectedAccess = await _requestMemberAccessService.MemberAccessAsync();
|
||||
Attempt<PagedModel<Guid>, ApiContentQueryOperationStatus> queryAttempt =
|
||||
_apiContentQueryService.ExecuteQuery(fetch, filter, sort, protectedAccess, skip, take);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Output Caching
|
||||
|
||||
### Cache Policy Configuration
|
||||
|
||||
```csharp
|
||||
// DependencyInjection/UmbracoBuilderExtensions.cs:120-136
|
||||
// Content and Media have separate cache durations
|
||||
options.AddPolicy(
|
||||
Constants.DeliveryApi.OutputCache.ContentCachePolicy,
|
||||
new DeliveryApiOutputCachePolicy(
|
||||
outputCacheSettings.ContentDuration,
|
||||
new StringValues([AcceptLanguage, AcceptSegment, StartItem])));
|
||||
```
|
||||
|
||||
**Cache invalidation conditions** (Caching/DeliveryApiOutputCachePolicy.cs:31):
|
||||
```csharp
|
||||
// Never cache preview or non-public access
|
||||
context.EnableOutputCaching = requestPreviewService.IsPreview() is false
|
||||
&& apiAccessService.HasPublicAccess();
|
||||
```
|
||||
|
||||
**Vary by headers**: `Accept-Language`, `Accept-Segment`, `Start-Item`
|
||||
|
||||
---
|
||||
|
||||
## 7. Edge Cases & Known Issues
|
||||
|
||||
### Technical Debt (TODOs in codebase)
|
||||
|
||||
1. **V1 Removal Pending** (4 locations):
|
||||
- `DependencyInjection/UmbracoBuilderExtensions.cs:98` - FIXME: remove matcher policy
|
||||
- `Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs:11` - FIXME: remove class
|
||||
- `Filters/SwaggerDocumentationFilterBase.cs:79,83` - FIXME: remove V1 swagger docs
|
||||
|
||||
2. **Obsolete Reference Warnings** (csproj:9-13):
|
||||
- `ASP0019` - IHeaderDictionary.Append usage
|
||||
- `CS0618/CS0612` - Obsolete member references
|
||||
|
||||
### Empty Query Results
|
||||
|
||||
Query service returns empty results (not errors) for invalid options:
|
||||
|
||||
```csharp
|
||||
// Services/ApiContentQueryService.cs:54-78
|
||||
// Invalid selector/filter/sort returns fail status with empty result
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.SelectorOptionNotFound, emptyResult);
|
||||
```
|
||||
|
||||
### Start Item Fallback
|
||||
|
||||
When no `fetch` parameter provided, uses start item or all content:
|
||||
|
||||
```csharp
|
||||
// Services/ApiContentQueryService.cs:99-112
|
||||
if (_requestStartItemProviderAccessor.TryGetValue(out IRequestStartItemProvider? requestStartItemProvider))
|
||||
{
|
||||
IPublishedContent? startItem = requestStartItemProvider.GetStartItem();
|
||||
// Use descendants of start item
|
||||
}
|
||||
return _apiContentQueryProvider.AllContentSelectorOption(); // Fallback to all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Project-Specific Notes
|
||||
|
||||
### V1 vs V2 Differences
|
||||
|
||||
| Feature | V1 | V2 |
|
||||
|---------|----|----|
|
||||
| Output expansion | Basic | Tree-based parsing |
|
||||
| `expand` parameter | Flat list | Nested syntax |
|
||||
| `fields` parameter | Limited | Full property selection |
|
||||
| Default expansion strategy | `RequestContextOutputExpansionStrategy` | `RequestContextOutputExpansionStrategyV2` |
|
||||
|
||||
**Migration note**: V1 is deprecated; plan removal when V17+ drops V1 support.
|
||||
|
||||
### JSON Configuration
|
||||
|
||||
Delivery API has its own JSON options (distinct from Management API):
|
||||
|
||||
```csharp
|
||||
// DependencyInjection/UmbracoBuilderExtensions.cs:82-88
|
||||
.AddJsonOptions(Constants.JsonOptionsNames.DeliveryApi, options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
options.JsonSerializerOptions.TypeInfoResolver = new DeliveryApiJsonTypeResolver();
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
});
|
||||
```
|
||||
|
||||
### Member Token Revocation
|
||||
|
||||
Tokens automatically revoked on member changes:
|
||||
|
||||
```csharp
|
||||
// DependencyInjection/UmbracoBuilderExtensions.cs:93-96
|
||||
builder.AddNotificationAsyncHandler<MemberSavedNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<MemberDeletedNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<AssignedMemberRolesNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<RemovedMemberRolesNotification, RevokeMemberAuthenticationTokensNotificationHandler>();
|
||||
```
|
||||
|
||||
### External Dependencies
|
||||
|
||||
**Examine/Lucene** (via Core):
|
||||
- Powers content querying
|
||||
- Selector/Filter/Sort handlers build Lucene queries
|
||||
|
||||
**OpenIddict** (via Api.Common):
|
||||
- Member OAuth 2.0 authentication
|
||||
- Reference tokens (not JWT)
|
||||
|
||||
### Configuration (appsettings.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"DeliveryApi": {
|
||||
"Enabled": true,
|
||||
"PublicAccess": true,
|
||||
"ApiKey": "your-api-key",
|
||||
"Media": {
|
||||
"Enabled": true,
|
||||
"PublicAccess": true
|
||||
},
|
||||
"MemberAuthorization": {
|
||||
"AuthorizationCodeFlow": { "Enabled": true },
|
||||
"ClientCredentialsFlow": { "Enabled": false }
|
||||
},
|
||||
"OutputCache": {
|
||||
"Enabled": true,
|
||||
"ContentDuration": "00:01:00",
|
||||
"MediaDuration": "00:01:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Endpoints Summary
|
||||
|
||||
**Content** (`/umbraco/delivery/api/v2/content`):
|
||||
- `GET /item/{id}` - Single content by GUID
|
||||
- `GET /item/{path}` - Single content by route
|
||||
- `GET /items` - Multiple by IDs
|
||||
- `GET /` - Query with fetch/filter/sort
|
||||
|
||||
**Media** (`/umbraco/delivery/api/v2/media`):
|
||||
- `GET /item/{id}` - Single media by GUID
|
||||
- `GET /item/{path}` - Single media by path
|
||||
- `GET /items` - Multiple by IDs
|
||||
- `GET /` - Query media
|
||||
|
||||
**Security** (`/umbraco/delivery/api/v1/security/member`):
|
||||
- `GET /authorize` - Start OAuth flow
|
||||
- `POST /token` - Exchange code for token
|
||||
- `GET /signout` - Revoke session
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Build project
|
||||
dotnet build src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj
|
||||
|
||||
# Pack for NuGet
|
||||
dotnet pack src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj -c Release
|
||||
|
||||
# Run integration tests
|
||||
dotnet test tests/Umbraco.Tests.Integration/ --filter "FullyQualifiedName~Delivery"
|
||||
|
||||
# Check packages
|
||||
dotnet list src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj package --outdated
|
||||
```
|
||||
|
||||
### Key Classes
|
||||
|
||||
| Class | Purpose | File |
|
||||
|-------|---------|------|
|
||||
| `DeliveryApiControllerBase` | Base controller with path decoding | Controllers/DeliveryApiControllerBase.cs |
|
||||
| `ApiContentQueryService` | Query orchestration | Services/ApiContentQueryService.cs |
|
||||
| `ApiAccessService` | Access control logic | Services/ApiAccessService.cs |
|
||||
| `DeliveryApiOutputCachePolicy` | Cache policy implementation | Caching/DeliveryApiOutputCachePolicy.cs |
|
||||
| `MemberController` | OAuth endpoints | Controllers/Security/MemberController.cs |
|
||||
| `RequestContextOutputExpansionStrategyV2` | V2 output expansion | Rendering/RequestContextOutputExpansionStrategyV2.cs |
|
||||
|
||||
### Important Files
|
||||
|
||||
- `Umbraco.Cms.Api.Delivery.csproj` - Project dependencies
|
||||
- `DependencyInjection/UmbracoBuilderExtensions.cs` - DI registration (lines 33-141)
|
||||
- `Configuration/DeliveryApiConfiguration.cs` - API constants
|
||||
- `Services/ApiContentQueryService.cs` - Query execution
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Root documentation**: `/CLAUDE.md` - Repository overview
|
||||
- **API Common patterns**: `/src/Umbraco.Cms.Api.Common/CLAUDE.md`
|
||||
- **Official docs**: https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api
|
||||
- **Media docs**: https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api/media-delivery-api
|
||||
|
||||
---
|
||||
|
||||
**This library exposes Umbraco content and media via REST for headless scenarios. Focus on query handlers, access control, and member authentication when working here.**
|
||||
258
src/Umbraco.Cms.DevelopmentMode.Backoffice/CLAUDE.md
Normal file
258
src/Umbraco.Cms.DevelopmentMode.Backoffice/CLAUDE.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# Umbraco.Cms.DevelopmentMode.Backoffice
|
||||
|
||||
Development-time library enabling **InMemoryAuto** ModelsBuilder mode with runtime Razor view compilation. Allows content type changes to instantly regenerate strongly-typed models without application restart.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
**Type**: Class Library (NuGet Package)
|
||||
**Target Framework**: .NET 10.0
|
||||
**Purpose**: Enable hot-reload of ModelsBuilder models during development
|
||||
|
||||
### Key Technologies
|
||||
|
||||
- **Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation** - Runtime Razor view compilation
|
||||
- **Microsoft.CodeAnalysis.CSharp** (Roslyn) - Runtime C# compilation
|
||||
- **AssemblyLoadContext** - Collectible assembly loading/unloading
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `Umbraco.Web.Common` - Umbraco web infrastructure
|
||||
|
||||
### Project Structure (18 source files)
|
||||
|
||||
```
|
||||
Umbraco.Cms.DevelopmentMode.Backoffice/
|
||||
├── DependencyInjection/
|
||||
│ ├── BackofficeDevelopmentComposer.cs # Auto-registration via IComposer
|
||||
│ └── UmbracoBuilderExtensions.cs # DI setup (CRITICAL: contains 80-line design overview)
|
||||
└── InMemoryAuto/
|
||||
├── InMemoryModelFactory.cs # Core factory - generates/loads models (876 lines)
|
||||
├── InMemoryAssemblyLoadContextManager.cs # Manages collectible AssemblyLoadContext
|
||||
├── RoslynCompiler.cs # Compiles generated C# to DLL
|
||||
├── CollectibleRuntimeViewCompiler.cs # Custom IViewCompiler for Razor (489 lines)
|
||||
├── UmbracoViewCompilerProvider.cs # Provides CollectibleRuntimeViewCompiler
|
||||
├── RuntimeCompilationCacheBuster.cs # Clears Razor caches on model rebuild
|
||||
├── UmbracoRazorReferenceManager.cs # Manages Roslyn MetadataReferences
|
||||
├── CompilationOptionsProvider.cs # Mirrors host app compilation settings
|
||||
├── UmbracoAssemblyLoadContext.cs # Collectible AssemblyLoadContext wrapper
|
||||
├── ChecksumValidator.cs # Validates precompiled view checksums
|
||||
├── CompilationExceptionFactory.cs # Creates detailed compilation errors
|
||||
├── UmbracoCompilationException.cs # Custom exception with CompilationFailures
|
||||
├── ModelsBuilderAssemblyAttribute.cs # Marks InMemory assemblies
|
||||
├── ModelsBuilderBindingErrorHandler.cs # Handles model binding version mismatches
|
||||
├── InMemoryModelsBuilderModeValidator.cs # Validates runtime mode configuration
|
||||
└── ModelsModeConstants.cs # "InMemoryAuto" constant
|
||||
```
|
||||
|
||||
### Design Patterns
|
||||
|
||||
1. **Clone-and-Own Pattern** - ASP.NET Core's RuntimeViewCompiler and related classes are cloned because internal APIs can't be extended
|
||||
2. **Collectible AssemblyLoadContext** - Enables assembly unloading for hot-reload
|
||||
3. **FileSystemWatcher** - Monitors `~/umbraco/Data/TEMP/InMemoryAuto/` for changes
|
||||
4. **Lazy Initialization** - References resolved on first use, not startup
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Patterns
|
||||
|
||||
### Why Clone-and-Own (CRITICAL CONTEXT)
|
||||
|
||||
The 80-line comment in `DependencyInjection/UmbracoBuilderExtensions.cs:13-80` explains why this project exists. Key points:
|
||||
|
||||
1. **Problem**: ASP.NET Core's `RuntimeViewCompiler` loads assemblies into the default `AssemblyLoadContext`, which can't reference collectible contexts (breaking change in .NET 7)
|
||||
2. **Failed Solutions**:
|
||||
- Reflection to clear caches (unstable, internal APIs)
|
||||
- Service wrapping via DI (still loads into wrong context)
|
||||
3. **Solution**: Clone `RuntimeViewCompiler`, `RazorReferenceManager`, `ChecksumValidator`, and related classes, modifying them to:
|
||||
- Load compiled views into the same collectible `AssemblyLoadContext` as models
|
||||
- Explicitly add InMemoryAuto models assembly reference during compilation
|
||||
|
||||
### InMemoryModelFactory Lifecycle
|
||||
|
||||
```
|
||||
Content Type Changed → Reset() called → _pendingRebuild = true
|
||||
↓
|
||||
Next View Request → EnsureModels() → GetModelsAssembly(forceRebuild: true)
|
||||
↓
|
||||
GenerateModelsCode() → RoslynCompiler.CompileToFile()
|
||||
↓
|
||||
ReloadAssembly() → InMemoryAssemblyLoadContextManager.RenewAssemblyLoadContext()
|
||||
↓
|
||||
RuntimeCompilationCacheBuster.BustCache() → Views recompile with new models
|
||||
```
|
||||
|
||||
### Assembly Caching Strategy
|
||||
|
||||
Models are cached to disk for faster boot (`InMemoryModelFactory.cs:400-585`):
|
||||
|
||||
1. **Hash Check**: `TypeModelHasher.Hash(typeModels)` creates hash of content type definitions
|
||||
2. **Cache Files** (in `~/umbraco/Data/TEMP/InMemoryAuto/`):
|
||||
- `models.hash` - Current content type hash
|
||||
- `models.generated.cs` - Generated source
|
||||
- `all.generated.cs` - Combined source with assembly attributes
|
||||
- `all.dll.path` - Path to compiled DLL
|
||||
- `Compiled/generated.cs{hash}.dll` - Compiled assembly
|
||||
|
||||
3. **Boot Sequence**:
|
||||
- Check if `models.hash` matches current hash
|
||||
- If match, load existing DLL
|
||||
- If mismatch or missing, recompile
|
||||
|
||||
### Collectible AssemblyLoadContext
|
||||
|
||||
```csharp
|
||||
// InMemoryAssemblyLoadContextManager.cs:29-37
|
||||
internal void RenewAssemblyLoadContext()
|
||||
{
|
||||
_currentAssemblyLoadContext?.Unload(); // Unload previous
|
||||
_currentAssemblyLoadContext = new UmbracoAssemblyLoadContext(); // Create new collectible
|
||||
}
|
||||
```
|
||||
|
||||
**Key constraint**: No external references to the `AssemblyLoadContext` allowed - prevents unloading.
|
||||
|
||||
---
|
||||
|
||||
## 3. Error Handling
|
||||
|
||||
### Compilation Failures
|
||||
|
||||
`CompilationExceptionFactory.cs` creates `UmbracoCompilationException` with:
|
||||
- Source file content
|
||||
- Generated code
|
||||
- Diagnostic messages with line/column positions
|
||||
|
||||
Errors logged in `CollectibleRuntimeViewCompiler.cs:383-396` and `InMemoryModelFactory.cs:341-354`.
|
||||
|
||||
### Model Binding Errors
|
||||
|
||||
`ModelsBuilderBindingErrorHandler.cs` handles version mismatches between:
|
||||
- View's model type (from compiled view assembly)
|
||||
- Content's model type (from InMemoryAuto assembly)
|
||||
|
||||
Reports detailed error messages including assembly versions.
|
||||
|
||||
---
|
||||
|
||||
## 4. Security
|
||||
|
||||
**Runtime Mode Validation**: `InMemoryModelsBuilderModeValidator.cs` prevents `InMemoryAuto` mode outside `BackofficeDevelopment` runtime mode.
|
||||
|
||||
**Temp File Location**: Models compiled to `~/umbraco/Data/TEMP/InMemoryAuto/` - ensure this directory isn't web-accessible.
|
||||
|
||||
---
|
||||
|
||||
## 5. Edge Cases & Known Issues
|
||||
|
||||
### Technical Debt (TODOs)
|
||||
|
||||
1. **Circular Reference** - `InMemoryModelFactory.cs:46`:
|
||||
```csharp
|
||||
private readonly Lazy<UmbracoServices> _umbracoServices; // TODO: this is because of circular refs :(
|
||||
```
|
||||
|
||||
2. **DynamicMethod Performance** - `InMemoryModelFactory.cs:698-701`:
|
||||
```csharp
|
||||
// TODO: use Core's ReflectionUtilities.EmitCtor !!
|
||||
// Yes .. DynamicMethod is uber slow
|
||||
```
|
||||
|
||||
### Race Conditions
|
||||
|
||||
`InMemoryModelFactory.cs:800-809` - FileSystemWatcher can cause race conditions on slow cloud filesystems. Own file changes are always ignored.
|
||||
|
||||
### Unused Assembly Cleanup
|
||||
|
||||
`InMemoryModelFactory.cs:587-612` - `TryDeleteUnusedAssemblies` may fail with `UnauthorizedAccessException` if files are locked. Cleanup retried on next rebuild.
|
||||
|
||||
### Reflection for Cache Clearing
|
||||
|
||||
`RuntimeCompilationCacheBuster.cs:50-51` uses reflection to call internal `RazorViewEngine.ClearCache()`:
|
||||
```csharp
|
||||
Action<RazorViewEngine>? clearCacheMethod = ReflectionUtilities.EmitMethod<Action<RazorViewEngine>>("ClearCache");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Configuration
|
||||
|
||||
**Enable InMemoryAuto mode** (appsettings.json):
|
||||
```json
|
||||
{
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"Runtime": {
|
||||
"Mode": "BackofficeDevelopment"
|
||||
},
|
||||
"ModelsBuilder": {
|
||||
"ModelsMode": "InMemoryAuto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- `RuntimeMode` must be `BackofficeDevelopment`
|
||||
- `ModelsMode` must be `InMemoryAuto`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
dotnet build src/Umbraco.Cms.DevelopmentMode.Backoffice/Umbraco.Cms.DevelopmentMode.Backoffice.csproj
|
||||
|
||||
# Pack for NuGet
|
||||
dotnet pack src/Umbraco.Cms.DevelopmentMode.Backoffice/Umbraco.Cms.DevelopmentMode.Backoffice.csproj -c Release
|
||||
|
||||
# Run integration tests
|
||||
dotnet test tests/Umbraco.Tests.Integration/ --filter "FullyQualifiedName~ModelsBuilder"
|
||||
```
|
||||
|
||||
**Note**: This library has no direct tests - tested via integration tests in `Umbraco.Tests.Integration`.
|
||||
|
||||
**Focus areas when modifying**:
|
||||
- Assembly loading/unloading cycles
|
||||
- Razor view compilation with models
|
||||
- Cache invalidation timing
|
||||
- Model binding error scenarios
|
||||
|
||||
### Essential Classes
|
||||
|
||||
| Class | Purpose | File |
|
||||
|-------|---------|------|
|
||||
| `InMemoryModelFactory` | Core model generation/loading | `InMemoryAuto/InMemoryModelFactory.cs` |
|
||||
| `CollectibleRuntimeViewCompiler` | Custom Razor compiler | `InMemoryAuto/CollectibleRuntimeViewCompiler.cs` |
|
||||
| `InMemoryAssemblyLoadContextManager` | Collectible assembly management | `InMemoryAuto/InMemoryAssemblyLoadContextManager.cs` |
|
||||
| `RuntimeCompilationCacheBuster` | Cache invalidation | `InMemoryAuto/RuntimeCompilationCacheBuster.cs` |
|
||||
| `UmbracoBuilderExtensions` | DI setup + design rationale | `DependencyInjection/UmbracoBuilderExtensions.cs` |
|
||||
|
||||
### Important Files
|
||||
|
||||
- **Design Overview**: `DependencyInjection/UmbracoBuilderExtensions.cs:13-80` - READ THIS FIRST
|
||||
- **Project File**: `Umbraco.Cms.DevelopmentMode.Backoffice.csproj`
|
||||
- **Model Factory**: `InMemoryAuto/InMemoryModelFactory.cs` - Most complex class
|
||||
|
||||
### Cloned Microsoft Code
|
||||
|
||||
These files are clones of ASP.NET Core internals - check upstream for updates:
|
||||
- `ChecksumValidator.cs` - https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Razor.RuntimeCompilation/src/ChecksumValidator.cs
|
||||
- `UmbracoRazorReferenceManager.cs` - https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs
|
||||
- `CompilationOptionsProvider.cs` - Partial clone of CSharpCompiler
|
||||
- `CompilationExceptionFactory.cs` - Partial clone of CompilationFailedExceptionFactory
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Root Documentation**: `/CLAUDE.md`
|
||||
- **Core Patterns**: `/src/Umbraco.Core/CLAUDE.md`
|
||||
- **Official Docs**: https://docs.umbraco.com/umbraco-cms/reference/configuration/modelsbuildersettings
|
||||
|
||||
---
|
||||
|
||||
**This library enables hot-reload of content models during development. The core complexity is working around ASP.NET Core's internal Razor compilation APIs. Always read the design overview in `UmbracoBuilderExtensions.cs:13-80` before making changes.**
|
||||
234
src/Umbraco.Cms.Imaging.ImageSharp/CLAUDE.md
Normal file
234
src/Umbraco.Cms.Imaging.ImageSharp/CLAUDE.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Umbraco.Cms.Imaging.ImageSharp
|
||||
|
||||
Image processing library using **ImageSharp 3.x** and **ImageSharp.Web** for on-the-fly image manipulation, resizing, cropping, and caching.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
**Type**: Class Library (NuGet Package)
|
||||
**Target Framework**: .NET 10.0
|
||||
**Purpose**: Provide image manipulation via query string parameters
|
||||
|
||||
### Key Technologies
|
||||
|
||||
- **SixLabors.ImageSharp** - Image processing library
|
||||
- **SixLabors.ImageSharp.Web** - ASP.NET Core middleware for query string-based image manipulation
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `Umbraco.Web.Common` - Web infrastructure
|
||||
|
||||
### Project Structure (7 source files)
|
||||
|
||||
```
|
||||
Umbraco.Cms.Imaging.ImageSharp/
|
||||
├── ImageSharpComposer.cs # Auto-registration via IComposer
|
||||
├── UmbracoBuilderExtensions.cs # DI setup and middleware configuration
|
||||
├── ConfigureImageSharpMiddlewareOptions.cs # Middleware options (caching, HMAC, size limits)
|
||||
├── ConfigurePhysicalFileSystemCacheOptions.cs # File cache location
|
||||
├── ImageProcessors/
|
||||
│ └── CropWebProcessor.cs # Custom crop processor with EXIF awareness
|
||||
└── Media/
|
||||
├── ImageSharpDimensionExtractor.cs # Extract image dimensions (EXIF-aware)
|
||||
└── ImageSharpImageUrlGenerator.cs # Generate query string URLs for processing
|
||||
```
|
||||
|
||||
### Relationship to ImageSharp2
|
||||
|
||||
Two imaging packages exist:
|
||||
- **Umbraco.Cms.Imaging.ImageSharp** (this package) - Uses ImageSharp 3.x (default)
|
||||
- **Umbraco.Cms.Imaging.ImageSharp2** - Uses ImageSharp 2.x for backwards compatibility
|
||||
|
||||
**Key difference**: ImageSharp 3.x WebP encoder defaults to Lossless (10x larger files), so this package explicitly sets `WebpFileFormatType.Lossy` at `ConfigureImageSharpMiddlewareOptions.cs:108-115`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Patterns
|
||||
|
||||
### Query String Image Processing
|
||||
|
||||
Images are processed via URL query parameters handled by ImageSharp.Web middleware:
|
||||
|
||||
| Parameter | Purpose | Example |
|
||||
|-----------|---------|---------|
|
||||
| `width` / `height` | Resize dimensions | `?width=800&height=600` |
|
||||
| `mode` | Crop mode (pad, crop, stretch, etc.) | `?mode=crop` |
|
||||
| `anchor` | Crop anchor position | `?anchor=center` |
|
||||
| `cc` | Crop coordinates (custom) | `?cc=0.1,0.1,0.1,0.1` |
|
||||
| `rxy` | Focal point | `?rxy=0.5,0.3` |
|
||||
| `format` | Output format | `?format=webp` |
|
||||
| `quality` | Compression quality | `?quality=80` |
|
||||
|
||||
### Pipeline Integration
|
||||
|
||||
ImageSharp middleware runs **before** static files in `UmbracoBuilderExtensions.cs:44-50`:
|
||||
```csharp
|
||||
options.AddFilter(new UmbracoPipelineFilter(nameof(ImageSharpComposer))
|
||||
{
|
||||
PrePipeline = prePipeline => prePipeline.UseImageSharp()
|
||||
});
|
||||
```
|
||||
|
||||
This ensures query strings are processed before serving static files.
|
||||
|
||||
### EXIF Orientation Handling
|
||||
|
||||
Both `ImageSharpDimensionExtractor` and `CropWebProcessor` account for EXIF rotation:
|
||||
|
||||
```csharp
|
||||
// ImageSharpDimensionExtractor.cs:42-44 - Swap width/height for rotated images
|
||||
size = IsExifOrientationRotated(imageInfo)
|
||||
? new Size(imageInfo.Height, imageInfo.Width)
|
||||
: new Size(imageInfo.Width, imageInfo.Height);
|
||||
```
|
||||
|
||||
```csharp
|
||||
// CropWebProcessor.cs:64-65 - Transform crop coordinates for EXIF orientation
|
||||
Vector2 xy1 = ExifOrientationUtilities.Transform(new Vector2(left, top), Vector2.Zero, Vector2.One, orientation);
|
||||
```
|
||||
|
||||
### HMAC Request Authorization
|
||||
|
||||
When `HMACSecretKey` is configured, URLs are signed to prevent abuse (`ImageSharpImageUrlGenerator.cs:121-131`):
|
||||
```csharp
|
||||
if (_options.HMACSecretKey.Length != 0 && _requestAuthorizationUtilities is not null)
|
||||
{
|
||||
var token = _requestAuthorizationUtilities.ComputeHMAC(uri, CommandHandling.Sanitize);
|
||||
queryString.Add(RequestAuthorizationUtilities.TokenCommand, token);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Configuration
|
||||
|
||||
### ImagingSettings (appsettings.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"Imaging": {
|
||||
"HMACSecretKey": "",
|
||||
"Cache": {
|
||||
"BrowserMaxAge": "7.00:00:00",
|
||||
"CacheMaxAge": "365.00:00:00",
|
||||
"CacheHashLength": 12,
|
||||
"CacheFolder": "~/umbraco/Data/TEMP/MediaCache",
|
||||
"CacheFolderDepth": 8
|
||||
},
|
||||
"Resize": {
|
||||
"MaxWidth": 5000,
|
||||
"MaxHeight": 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security: Size Limits Without HMAC
|
||||
|
||||
When HMAC is not configured, `ConfigureImageSharpMiddlewareOptions.cs:46-83` enforces max dimensions:
|
||||
- Width/height requests exceeding `MaxWidth`/`MaxHeight` are **stripped** from the query
|
||||
- This prevents DoS via excessive image generation
|
||||
|
||||
When HMAC **is** configured, size validation is skipped (trusted requests).
|
||||
|
||||
### Cache Busting
|
||||
|
||||
Query parameters `rnd` or `v` trigger immutable cache headers (`ConfigureImageSharpMiddlewareOptions.cs:86-106`):
|
||||
- Disables `MustRevalidate`
|
||||
- Adds `immutable` directive
|
||||
|
||||
---
|
||||
|
||||
## 4. Core Interfaces Implemented
|
||||
|
||||
| Interface | Implementation | Purpose |
|
||||
|-----------|----------------|---------|
|
||||
| `IImageDimensionExtractor` | `ImageSharpDimensionExtractor` | Extract width/height from streams |
|
||||
| `IImageUrlGenerator` | `ImageSharpImageUrlGenerator` | Generate manipulation URLs |
|
||||
| `IImageWebProcessor` | `CropWebProcessor` | Custom crop with `cc` parameter |
|
||||
|
||||
---
|
||||
|
||||
## 5. Edge Cases
|
||||
|
||||
### WebP Encoding Change (ImageSharp 3.x)
|
||||
|
||||
`ConfigureImageSharpMiddlewareOptions.cs:108-115` - ImageSharp 3.x defaults WebP to Lossless for PNGs, creating ~10x larger files. This is overridden:
|
||||
```csharp
|
||||
options.Configuration.ImageFormatsManager.SetEncoder(
|
||||
WebpFormat.Instance,
|
||||
new WebpEncoder { FileFormat = WebpFileFormatType.Lossy });
|
||||
```
|
||||
|
||||
### Crop Coordinates Format
|
||||
|
||||
`CropWebProcessor.cs:50-56` - The `cc` parameter expects 4 values as distances from edges:
|
||||
- Format: `left,top,right,bottom` (0-1 range, percentages)
|
||||
- Right/bottom values are **distance from** those edges, not coordinates
|
||||
- Zero values (`0,0,0,0`) are ignored (no crop)
|
||||
|
||||
### Supported File Types
|
||||
|
||||
Dynamically determined from ImageSharp configuration (`ImageSharpDimensionExtractor.cs:24`):
|
||||
```csharp
|
||||
SupportedImageFileTypes = configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray();
|
||||
```
|
||||
|
||||
Default includes: jpg, jpeg, png, gif, bmp, webp, tiff, etc.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
dotnet build src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
|
||||
|
||||
# Run tests
|
||||
dotnet test tests/Umbraco.Tests.UnitTests/ --filter "FullyQualifiedName~ImageSharp"
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `UmbracoBuilderExtensions.cs` | DI registration and pipeline setup |
|
||||
| `ConfigureImageSharpMiddlewareOptions.cs` | Middleware config (caching, HMAC, size limits) |
|
||||
| `CropWebProcessor.cs` | Custom `cc` crop parameter |
|
||||
| `ImageSharpImageUrlGenerator.cs` | URL generation with HMAC signing |
|
||||
|
||||
### URL Examples
|
||||
|
||||
```
|
||||
# Basic resize
|
||||
/media/image.jpg?width=800
|
||||
|
||||
# Crop to aspect ratio with focal point
|
||||
/media/image.jpg?width=800&height=600&mode=crop&rxy=0.5,0.3
|
||||
|
||||
# Custom crop coordinates (10% from each edge)
|
||||
/media/image.jpg?cc=0.1,0.1,0.1,0.1
|
||||
|
||||
# Format conversion with quality
|
||||
/media/image.jpg?format=webp&quality=80
|
||||
|
||||
# Cache busted (immutable headers)
|
||||
/media/image.jpg?width=800&v=abc123
|
||||
```
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Root Documentation**: `/CLAUDE.md`
|
||||
- **ImageSharp Docs**: https://docs.sixlabors.com/
|
||||
- **Umbraco Imaging**: https://docs.umbraco.com/umbraco-cms/reference/configuration/imagingsettings
|
||||
|
||||
---
|
||||
|
||||
**This library provides query string-based image processing. Key concerns are EXIF orientation handling, WebP encoding defaults, and HMAC security for public-facing sites.**
|
||||
139
src/Umbraco.Cms.Imaging.ImageSharp2/CLAUDE.md
Normal file
139
src/Umbraco.Cms.Imaging.ImageSharp2/CLAUDE.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Umbraco.Cms.Imaging.ImageSharp2
|
||||
|
||||
Image processing library using **ImageSharp 2.x** for backwards compatibility with existing deployments. Use this package only when migrating from older Umbraco versions that depend on ImageSharp 2.x behavior.
|
||||
|
||||
**Namespace Note**: Uses `Umbraco.Cms.Imaging.ImageSharp` (same as v3 package) for drop-in replacement - no code changes needed when switching.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
**Type**: Class Library (NuGet Package)
|
||||
**Target Framework**: .NET 10.0
|
||||
**Purpose**: ImageSharp 2.x compatibility layer
|
||||
|
||||
### Package Versions (Pinned)
|
||||
|
||||
```xml
|
||||
<!-- From csproj lines 7-8 -->
|
||||
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="[2.1.11, 3)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" VersionOverride="[2.0.2, 3)" />
|
||||
```
|
||||
|
||||
Version constraint `[2.1.11, 3)` means: minimum 2.1.11, below 3.0.
|
||||
|
||||
### Project Structure (7 source files)
|
||||
|
||||
```
|
||||
Umbraco.Cms.Imaging.ImageSharp2/
|
||||
├── ImageSharpComposer.cs # Auto-registration via IComposer
|
||||
├── UmbracoBuilderExtensions.cs # DI setup and middleware configuration
|
||||
├── ConfigureImageSharpMiddlewareOptions.cs # Middleware options (caching, size limits)
|
||||
├── ConfigurePhysicalFileSystemCacheOptions.cs # File cache location
|
||||
├── ImageProcessors/
|
||||
│ └── CropWebProcessor.cs # Custom crop processor with EXIF awareness
|
||||
└── Media/
|
||||
├── ImageSharpDimensionExtractor.cs # Extract image dimensions (EXIF-aware)
|
||||
└── ImageSharpImageUrlGenerator.cs # Generate query string URLs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Differences from ImageSharp (3.x)
|
||||
|
||||
| Feature | ImageSharp2 (this) | ImageSharp (3.x) |
|
||||
|---------|-------------------|------------------|
|
||||
| **Package version** | 2.1.11 - 2.x | 3.x+ |
|
||||
| **HMAC signing** | Not supported | Supported |
|
||||
| **WebP default** | Lossy (native) | Lossless (overridden to Lossy) |
|
||||
| **Cache buster param** | `rnd` only | `rnd` or `v` |
|
||||
| **API differences** | `Image.Identify(config, stream)` | `Image.Identify(options, stream)` |
|
||||
| **Size property** | `image.Image.Size()` method | `image.Image.Size` property |
|
||||
|
||||
### API Differences in Code
|
||||
|
||||
**ImageSharpDimensionExtractor** (`Media/ImageSharpDimensionExtractor.cs:31`):
|
||||
```csharp
|
||||
// v2: Direct method call
|
||||
IImageInfo imageInfo = Image.Identify(_configuration, stream);
|
||||
|
||||
// v3: Uses DecoderOptions
|
||||
ImageInfo imageInfo = Image.Identify(options, stream);
|
||||
```
|
||||
|
||||
**CropWebProcessor** (`ImageProcessors/CropWebProcessor.cs:67`):
|
||||
```csharp
|
||||
// v2: Size is a method
|
||||
Size size = image.Image.Size();
|
||||
|
||||
// v3: Size is a property
|
||||
Size size = image.Image.Size;
|
||||
```
|
||||
|
||||
### Missing Features (vs ImageSharp 3.x)
|
||||
|
||||
1. **No HMAC request authorization** - `HMACSecretKey` setting is ignored
|
||||
2. **No `v` cache buster** - Only `rnd` parameter triggers immutable headers
|
||||
3. **No WebP encoder override** - Uses default Lossy encoding (no configuration needed)
|
||||
|
||||
---
|
||||
|
||||
## 3. When to Use This Package
|
||||
|
||||
**Use ImageSharp2 when:**
|
||||
- Migrating from Umbraco versions that used ImageSharp 2.x
|
||||
- Third-party packages have hard dependency on ImageSharp 2.x
|
||||
- Need exact byte-for-byte output compatibility with existing cached images
|
||||
|
||||
**Use ImageSharp (3.x) when:**
|
||||
- New installations
|
||||
- Need HMAC URL signing for security
|
||||
- Want latest performance improvements
|
||||
|
||||
---
|
||||
|
||||
## 4. Configuration
|
||||
|
||||
Same as ImageSharp 3.x. See `/src/Umbraco.Cms.Imaging.ImageSharp/CLAUDE.md` → Section 3 for full configuration details.
|
||||
|
||||
**Key difference**: `HMACSecretKey` setting exists but is **ignored** in this package (no HMAC support in v2).
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
dotnet build src/Umbraco.Cms.Imaging.ImageSharp2/Umbraco.Cms.Imaging.ImageSharp2.csproj
|
||||
|
||||
# Run tests
|
||||
dotnet test tests/Umbraco.Tests.UnitTests/ --filter "FullyQualifiedName~ImageSharp"
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Umbraco.Cms.Imaging.ImageSharp2.csproj` | Version constraints (lines 7-8) |
|
||||
| `ConfigureImageSharpMiddlewareOptions.cs` | Size limit enforcement |
|
||||
| `Media/ImageSharpImageUrlGenerator.cs` | URL generation (no HMAC) |
|
||||
|
||||
### Switching Between Packages
|
||||
|
||||
To switch from ImageSharp2 to ImageSharp (3.x):
|
||||
1. Remove `Umbraco.Cms.Imaging.ImageSharp2` package reference
|
||||
2. Add `Umbraco.Cms.Imaging.ImageSharp` package reference
|
||||
3. Clear media cache folder (`~/umbraco/Data/TEMP/MediaCache`)
|
||||
4. No code changes needed (same namespace)
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **ImageSharp 3.x Documentation**: `/src/Umbraco.Cms.Imaging.ImageSharp/CLAUDE.md`
|
||||
- **Root Documentation**: `/CLAUDE.md`
|
||||
- **SixLabors ImageSharp 2.x Docs**: https://docs.sixlabors.com/
|
||||
|
||||
---
|
||||
|
||||
**This is a backwards-compatibility package. For new projects, use `Umbraco.Cms.Imaging.ImageSharp` (3.x) instead.**
|
||||
132
src/Umbraco.Cms.Persistence.EFCore.SqlServer/CLAUDE.md
Normal file
132
src/Umbraco.Cms.Persistence.EFCore.SqlServer/CLAUDE.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Umbraco.Cms.Persistence.EFCore.SqlServer
|
||||
|
||||
SQL Server-specific EF Core provider for Umbraco CMS. Contains SQL Server migrations and provider setup for the EF Core persistence layer.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Dependencies**: Umbraco.Cms.Persistence.EFCore
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This is a thin provider project that implements SQL Server-specific functionality for the EF Core persistence layer:
|
||||
|
||||
1. **Migration Provider** - Executes SQL Server-specific migrations
|
||||
2. **Migration Provider Setup** - Configures DbContext to use SQL Server
|
||||
3. **Migrations** - SQL Server-specific migration files for OpenIddict tables
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Cms.Persistence.EFCore.SqlServer/
|
||||
├── Migrations/
|
||||
│ ├── 20230622184303_InitialCreate.cs # No-op (NPoco creates tables)
|
||||
│ ├── 20230807654321_AddOpenIddict.cs # OpenIddict tables
|
||||
│ ├── 20240403140654_UpdateOpenIddictToV5.cs # OpenIddict v5 schema changes
|
||||
│ ├── 20251006140751_UpdateOpenIddictToV7.cs # Token Type column expansion
|
||||
│ └── UmbracoDbContextModelSnapshot.cs # Current model state
|
||||
├── EFCoreSqlServerComposer.cs # DI registration
|
||||
├── SqlServerMigrationProvider.cs # IMigrationProvider impl
|
||||
└── SqlServerMigrationProviderSetup.cs # IMigrationProviderSetup impl
|
||||
```
|
||||
|
||||
### Relationship with Parent Project
|
||||
|
||||
This project extends `Umbraco.Cms.Persistence.EFCore`:
|
||||
|
||||
- Implements `IMigrationProvider` interface defined in parent
|
||||
- Implements `IMigrationProviderSetup` interface defined in parent
|
||||
- Uses `UmbracoDbContext` from parent project
|
||||
- Provider name: `Microsoft.Data.SqlClient` (from `Constants.ProviderNames.SQLServer`)
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
**For EF Core migration commands**, see [parent project](../Umbraco.Cms.Persistence.EFCore/CLAUDE.md) (substitute `-p src/Umbraco.Cms.Persistence.EFCore.SqlServer`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### EFCoreSqlServerComposer (line 10-14)
|
||||
|
||||
Registers SQL Server implementations of `IMigrationProvider` and `IMigrationProviderSetup`.
|
||||
|
||||
### SqlServerMigrationProvider
|
||||
|
||||
Executes migrations via `MigrateAsync(EFCoreMigration)` or `MigrateAllAsync()`. Unlike SQLite sibling, no transaction check needed (SQL Server handles concurrent migrations natively).
|
||||
|
||||
### SqlServerMigrationProviderSetup (line 11-14)
|
||||
|
||||
Configures `DbContextOptionsBuilder` with `UseSqlServer` and migrations assembly.
|
||||
|
||||
---
|
||||
|
||||
## 4. Migrations
|
||||
|
||||
### Migration History
|
||||
|
||||
| Migration | Date | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `InitialCreate` | 2023-06-22 | No-op - NPoco creates base tables |
|
||||
| `AddOpenIddict` | 2023-08-07 | Creates OpenIddict tables |
|
||||
| `UpdateOpenIddictToV5` | 2024-04-03 | Renames Type→ClientType, adds ApplicationType/JsonWebKeySet/Settings |
|
||||
| `UpdateOpenIddictToV7` | 2025-10-06 | Expands Token.Type from nvarchar(50) to nvarchar(150) |
|
||||
|
||||
### OpenIddict Tables Created
|
||||
|
||||
Prefixed with `umbraco`: Applications, Authorizations, Scopes, Tokens (see SQLite sibling for details).
|
||||
|
||||
### SQL Server-Specific Differences from SQLite
|
||||
|
||||
1. **nvarchar types** - Uses `nvarchar(n)` and `nvarchar(max)` instead of TEXT
|
||||
2. **v5 migration has actual changes** - Column rename and additions (SQLite handled differently)
|
||||
3. **v7 migration has schema change** - Token.Type column expanded (SQLite didn't need this)
|
||||
|
||||
### Adding New Migrations
|
||||
|
||||
1. Configure SQL Server connection string in `src/Umbraco.Web.UI/appsettings.json`
|
||||
2. Run migration command from repository root (see parent project docs)
|
||||
3. **Critical**: Also add equivalent migration to `Umbraco.Cms.Persistence.EFCore.Sqlite`
|
||||
4. Update `SqlServerMigrationProvider.GetMigrationType()` switch (line 27-35) if adding named migrations
|
||||
|
||||
---
|
||||
|
||||
## 5. Project-Specific Notes
|
||||
|
||||
### Named Migration Mapping
|
||||
|
||||
`SqlServerMigrationProvider.GetMigrationType()` (line 27-35) maps `EFCoreMigration` enum to migration types. Update both parent enum and this switch when adding named migrations.
|
||||
|
||||
### Transaction Handling
|
||||
|
||||
SQL Server handles concurrent migrations natively - no transaction check needed (unlike SQLite).
|
||||
|
||||
### Auto-Generated Files
|
||||
|
||||
`UmbracoDbContextModelSnapshot.cs` is regenerated by EF Core - do not edit manually.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `SqlServerMigrationProvider.cs` | Migration execution |
|
||||
| `SqlServerMigrationProviderSetup.cs` | DbContext configuration |
|
||||
| `EFCoreSqlServerComposer.cs` | DI registration |
|
||||
| `Migrations/*.cs` | Migration files |
|
||||
|
||||
### Provider Name
|
||||
`Constants.ProviderNames.SQLServer` = `"Microsoft.Data.SqlClient"`
|
||||
|
||||
### Related Projects
|
||||
- **Parent**: `Umbraco.Cms.Persistence.EFCore` - Interfaces and base DbContext
|
||||
- **Sibling**: `Umbraco.Cms.Persistence.EFCore.Sqlite` - SQLite equivalent
|
||||
134
src/Umbraco.Cms.Persistence.EFCore.Sqlite/CLAUDE.md
Normal file
134
src/Umbraco.Cms.Persistence.EFCore.Sqlite/CLAUDE.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Umbraco.Cms.Persistence.EFCore.Sqlite
|
||||
|
||||
SQLite-specific EF Core provider for Umbraco CMS. Contains SQLite migrations and provider setup for the EF Core persistence layer.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Dependencies**: Umbraco.Cms.Persistence.EFCore
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This is a thin provider project that implements SQLite-specific functionality for the EF Core persistence layer:
|
||||
|
||||
1. **Migration Provider** - Executes SQLite-specific migrations
|
||||
2. **Migration Provider Setup** - Configures DbContext to use SQLite
|
||||
3. **Migrations** - SQLite-specific migration files for OpenIddict tables
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Cms.Persistence.EFCore.Sqlite/
|
||||
├── Migrations/
|
||||
│ ├── 20230622183638_InitialCreate.cs # No-op (NPoco creates tables)
|
||||
│ ├── 20230807123456_AddOpenIddict.cs # OpenIddict tables
|
||||
│ ├── 20240403141051_UpdateOpenIddictToV5.cs # OpenIddict v5 schema
|
||||
│ ├── 20251006140958_UpdateOpenIddictToV7.cs # OpenIddict v7 (no-op for SQLite)
|
||||
│ └── UmbracoDbContextModelSnapshot.cs # Current model state
|
||||
├── EFCoreSqliteComposer.cs # DI registration
|
||||
├── SqliteMigrationProvider.cs # IMigrationProvider impl
|
||||
└── SqliteMigrationProviderSetup.cs # IMigrationProviderSetup impl
|
||||
```
|
||||
|
||||
### Relationship with Parent Project
|
||||
|
||||
This project extends `Umbraco.Cms.Persistence.EFCore`:
|
||||
|
||||
- Implements `IMigrationProvider` interface defined in parent
|
||||
- Implements `IMigrationProviderSetup` interface defined in parent
|
||||
- Uses `UmbracoDbContext` from parent project
|
||||
- Provider name: `Microsoft.Data.Sqlite` (from `Constants.ProviderNames.SQLLite`)
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
**For EF Core migration commands**, see [parent project](../Umbraco.Cms.Persistence.EFCore/CLAUDE.md) (substitute `-p src/Umbraco.Cms.Persistence.EFCore.Sqlite`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### EFCoreSqliteComposer (line 10-14)
|
||||
|
||||
Registers `IMigrationProvider` and `IMigrationProviderSetup` for SQLite.
|
||||
|
||||
### SqliteMigrationProvider
|
||||
|
||||
- `MigrateAsync(EFCoreMigration)` - Runs specific named migration
|
||||
- `MigrateAllAsync()` - Runs all pending migrations
|
||||
- **Critical**: Cannot run `MigrateAllAsync` when transaction is active (line 26-29)
|
||||
|
||||
### SqliteMigrationProviderSetup (line 11-14)
|
||||
|
||||
Configures `DbContextOptionsBuilder` with `UseSqlite` and migrations assembly.
|
||||
|
||||
---
|
||||
|
||||
## 4. Migrations
|
||||
|
||||
### Migration History
|
||||
|
||||
| Migration | Date | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `InitialCreate` | 2023-06-22 | No-op - NPoco creates base tables |
|
||||
| `AddOpenIddict` | 2023-08-07 | Creates OpenIddict tables (Applications, Tokens, Authorizations, Scopes) |
|
||||
| `UpdateOpenIddictToV5` | 2024-04-03 | Schema updates for OpenIddict v5 |
|
||||
| `UpdateOpenIddictToV7` | 2025-10-06 | No-op for SQLite (no schema changes needed) |
|
||||
|
||||
### OpenIddict Tables Created
|
||||
|
||||
All tables prefixed with `umbraco`:
|
||||
- `umbracoOpenIddictApplications` - OAuth client applications
|
||||
- `umbracoOpenIddictAuthorizations` - User authorizations
|
||||
- `umbracoOpenIddictScopes` - OAuth scopes
|
||||
- `umbracoOpenIddictTokens` - Access/refresh tokens
|
||||
|
||||
### SQLite-Specific Notes
|
||||
|
||||
- **TEXT column type** - SQLite uses TEXT for all strings (no varchar)
|
||||
- **InitialCreate is no-op** - NPoco creates base tables, EF Core manages OpenIddict only
|
||||
- **v7 migration is no-op** - SQLite didn't require schema changes that SQL Server needed
|
||||
|
||||
### Adding New Migrations
|
||||
|
||||
1. Configure SQLite connection string in `src/Umbraco.Web.UI/appsettings.json`
|
||||
2. Run migration command from repository root (see parent project docs)
|
||||
3. **Critical**: Also add equivalent migration to `Umbraco.Cms.Persistence.EFCore.SqlServer`
|
||||
4. Update `SqliteMigrationProvider.GetMigrationType()` switch (line 34-42) if adding named migrations
|
||||
|
||||
---
|
||||
|
||||
## 5. Project-Specific Notes
|
||||
|
||||
### Named Migration Mapping
|
||||
|
||||
`SqliteMigrationProvider.GetMigrationType()` (line 34-42) maps `EFCoreMigration` enum to migration types. When adding named migrations, update both the parent project's enum and this switch.
|
||||
|
||||
### Auto-Generated Files
|
||||
|
||||
`UmbracoDbContextModelSnapshot.cs` is regenerated by EF Core - do not edit manually.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `SqliteMigrationProvider.cs` | Migration execution |
|
||||
| `SqliteMigrationProviderSetup.cs` | DbContext configuration |
|
||||
| `EFCoreSqliteComposer.cs` | DI registration |
|
||||
| `Migrations/*.cs` | Migration files |
|
||||
|
||||
### Provider Name
|
||||
`Constants.ProviderNames.SQLLite` = `"Microsoft.Data.Sqlite"`
|
||||
|
||||
### Related Projects
|
||||
- **Parent**: `Umbraco.Cms.Persistence.EFCore` - Interfaces and base DbContext
|
||||
- **Sibling**: `Umbraco.Cms.Persistence.EFCore.SqlServer` - SQL Server equivalent
|
||||
255
src/Umbraco.Cms.Persistence.EFCore/CLAUDE.md
Normal file
255
src/Umbraco.Cms.Persistence.EFCore/CLAUDE.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Umbraco.Cms.Persistence.EFCore
|
||||
|
||||
Entity Framework Core persistence layer for Umbraco CMS. This project provides EF Core integration including scoping, distributed locking, and migration infrastructure.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Dependencies**: Umbraco.Core, Umbraco.Infrastructure
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project bridges EF Core with Umbraco's existing persistence infrastructure. It provides:
|
||||
|
||||
1. **UmbracoDbContext** - Base DbContext with automatic table prefix (`umbraco`) and provider-based configuration
|
||||
2. **EF Core Scoping** - Integration with Umbraco's Unit of Work pattern (scopes)
|
||||
3. **Distributed Locking** - SQL Server and SQLite locking mechanisms for EF Core contexts
|
||||
4. **Migration Infrastructure** - Provider-agnostic migration execution
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Cms.Persistence.EFCore/
|
||||
├── Composition/
|
||||
│ └── UmbracoEFCoreComposer.cs # DI registration, OpenIddict setup
|
||||
├── Extensions/
|
||||
│ ├── DbContextExtensions.cs # ExecuteScalarAsync, MigrateDatabaseAsync
|
||||
│ └── UmbracoEFCoreServiceCollectionExtensions.cs # AddUmbracoDbContext<T>
|
||||
├── Locking/
|
||||
│ ├── SqlServerEFCoreDistributedLockingMechanism.cs
|
||||
│ └── SqliteEFCoreDistributedLockingMechanism.cs
|
||||
├── Migrations/
|
||||
│ ├── IMigrationProvider.cs # Provider-specific migration execution
|
||||
│ └── IMigrationProviderSetup.cs # DbContext options setup per provider
|
||||
├── Scoping/
|
||||
│ ├── AmbientEFCoreScopeStack.cs # AsyncLocal scope stack
|
||||
│ ├── EFCoreScope.cs # Main scope implementation
|
||||
│ ├── EFCoreDetachableScope.cs # Detachable scope for Deploy
|
||||
│ ├── EFCoreScopeProvider.cs # Scope factory
|
||||
│ ├── EFCoreScopeAccessor.cs # Ambient scope accessor
|
||||
│ └── I*.cs # Interfaces
|
||||
├── Constants-ProviderNames.cs # SQLite/SQLServer provider constants
|
||||
├── EfCoreMigrationExecutor.cs # Migration orchestrator
|
||||
├── StringExtensions.cs # Provider name comparison
|
||||
└── UmbracoDbContext.cs # Base DbContext
|
||||
```
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **Generic DbContext Support** - All scoping/locking is generic (`<TDbContext>`) allowing custom contexts
|
||||
2. **Parallel NPoco Coexistence** - Designed to work alongside existing NPoco persistence layer
|
||||
3. **Provider Abstraction** - `IMigrationProvider` and `IMigrationProviderSetup` enable database-agnostic migrations
|
||||
4. **OpenIddict Integration** - UmbracoDbContext automatically registers OpenIddict entity sets
|
||||
|
||||
### Database Providers
|
||||
|
||||
Provider-specific implementations live in separate projects:
|
||||
- `Umbraco.Cms.Persistence.EFCore.SqlServer` - SQL Server provider and migrations
|
||||
- `Umbraco.Cms.Persistence.EFCore.Sqlite` - SQLite provider and migrations
|
||||
|
||||
Provider names (from `Constants.ProviderNames`):
|
||||
- `Microsoft.Data.SqlClient` - SQL Server
|
||||
- `Microsoft.Data.Sqlite` - SQLite
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and standard build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
### EF Core Migrations
|
||||
|
||||
**Important**: Run from repository root with valid connection string in `src/Umbraco.Web.UI/appsettings.json`.
|
||||
|
||||
```bash
|
||||
# Add migration (SQL Server)
|
||||
dotnet ef migrations add %MigrationName% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -c UmbracoDbContext
|
||||
|
||||
# Add migration (SQLite)
|
||||
dotnet ef migrations add %MigrationName% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -c UmbracoDbContext
|
||||
|
||||
# Remove last migration (SQL Server)
|
||||
dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer
|
||||
|
||||
# Remove last migration (SQLite)
|
||||
dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite
|
||||
|
||||
# Generate migration script
|
||||
dotnet ef migrations script -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Patterns
|
||||
|
||||
### Adding a Custom DbContext
|
||||
|
||||
Use `AddUmbracoDbContext<T>` to register with full scope integration:
|
||||
|
||||
```csharp
|
||||
services.AddUmbracoDbContext<MyCustomDbContext>((provider, options, connectionString, providerName) =>
|
||||
{
|
||||
options.UseOpenIddict(); // Optional
|
||||
});
|
||||
```
|
||||
|
||||
Registers: `IDbContextFactory<T>`, scope providers, accessors, and distributed locking
|
||||
|
||||
### Using EF Core Scopes
|
||||
|
||||
```csharp
|
||||
using IEfCoreScope<UmbracoDbContext> scope = _scopeProvider.CreateScope();
|
||||
|
||||
await scope.ExecuteWithContextAsync<MyEntity>(async dbContext =>
|
||||
{
|
||||
return await dbContext.MyEntities.FirstOrDefaultAsync(x => x.Id == id);
|
||||
});
|
||||
|
||||
scope.Complete(); // Required to commit
|
||||
```
|
||||
|
||||
### UmbracoDbContext Table Naming
|
||||
|
||||
All tables auto-prefixed with `umbraco` (see `UmbracoDbContext.cs:85-88`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Scoping System
|
||||
|
||||
### Scope Hierarchy
|
||||
|
||||
```
|
||||
EFCoreScopeProvider<T>
|
||||
├── Creates EFCoreScope<T> (normal scope)
|
||||
└── Creates EFCoreDetachableScope<T> (for Deploy scenarios)
|
||||
|
||||
EFCoreScope<T>
|
||||
├── Manages DbContext lifecycle
|
||||
├── Handles transactions (BeginTransaction/Commit/Rollback)
|
||||
├── Integrates with parent NPoco IScope when nested
|
||||
└── Manages distributed locks via Locks property
|
||||
```
|
||||
|
||||
### Ambient Scope Stack
|
||||
|
||||
Uses `AsyncLocal<ConcurrentStack<T>>` for thread-safe tracking. Scopes must be disposed in LIFO order (child before parent).
|
||||
|
||||
### Transaction Integration
|
||||
|
||||
When an EF Core scope is created inside an NPoco scope:
|
||||
1. The EF Core scope reuses the parent's `DbTransaction`
|
||||
2. Transaction commit/rollback is delegated to the parent scope
|
||||
3. DbContext connects to the same connection as the parent
|
||||
|
||||
See `EFCoreScope.cs:158-180` for transaction initialization logic.
|
||||
|
||||
---
|
||||
|
||||
## 5. Distributed Locking
|
||||
|
||||
### SQL Server Locking
|
||||
|
||||
Table-level locks with `REPEATABLEREAD` hint. Timeout via `SET LOCK_TIMEOUT`. Requires `ReadCommitted` or higher.
|
||||
|
||||
### SQLite Locking
|
||||
|
||||
Database-level locking (SQLite limitation). Read locks use snapshot isolation (WAL mode). Write locks are exclusive. Handles `SQLITE_BUSY/LOCKED` errors.
|
||||
|
||||
### Lock Requirements
|
||||
|
||||
Requires active scope, transaction, and minimum `ReadCommitted` isolation.
|
||||
|
||||
---
|
||||
|
||||
## 6. Migration System
|
||||
|
||||
### How Migrations Execute
|
||||
|
||||
1. `UmbracoEFCoreComposer` registers `EfCoreMigrationExecutor`
|
||||
2. Notification handler triggers on database creation
|
||||
3. `EfCoreMigrationExecutor` finds correct `IMigrationProvider` by provider name
|
||||
4. Provider executes via `dbContext.Database.Migrate()`
|
||||
|
||||
### Adding New Migrations
|
||||
|
||||
Create equivalent migrations in both SqlServer and Sqlite provider projects using commands from section 2.
|
||||
|
||||
---
|
||||
|
||||
## 7. Project-Specific Notes
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
**Warning Suppressions** (`.csproj:9-14`): `IDE0270` (null checks), `CS0108` (hiding members), `CS1998` (async/await).
|
||||
|
||||
**Detachable Scope** (`EFCoreDetachableScope.cs:93-94`): TODO for Deploy integration, limited test coverage.
|
||||
|
||||
### Circular Dependency Handling
|
||||
|
||||
`SqlServerEFCoreDistributedLockingMechanism` uses `Lazy<IEFCoreScopeAccessor<T>>` to break circular dependency.
|
||||
|
||||
### StaticServiceProvider Usage
|
||||
|
||||
Fallback for design-time EF tooling (migrations) and startup edge cases when DI unavailable.
|
||||
|
||||
### OpenIddict Integration
|
||||
|
||||
`UmbracoEFCoreComposer` configures OpenIddict via `options.UseOpenIddict()` (see `Composition/UmbracoEFCoreComposer.cs:22-36`).
|
||||
|
||||
### Vulnerable Dependency Override
|
||||
|
||||
Top-level dependency on `Microsoft.Extensions.Caching.Memory` overrides vulnerable EF Core transitive dependency (`.csproj:18-19`).
|
||||
|
||||
### InternalsVisibleTo
|
||||
|
||||
`Umbraco.Tests.Integration` has access to internal types.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `UmbracoDbContext.cs` | Base DbContext with table prefixing |
|
||||
| `EFCoreScope.cs` | Main scope implementation |
|
||||
| `EFCoreScopeProvider.cs` | Scope factory |
|
||||
| `UmbracoEFCoreServiceCollectionExtensions.cs` | `AddUmbracoDbContext<T>` extension |
|
||||
| `UmbracoEFCoreComposer.cs` | DI registration |
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
| Interface | Purpose |
|
||||
|-----------|---------|
|
||||
| `IEfCoreScope<TDbContext>` | Scope contract with `ExecuteWithContextAsync` |
|
||||
| `IEFCoreScopeProvider<TDbContext>` | Creates/manages scopes |
|
||||
| `IEFCoreScopeAccessor<TDbContext>` | Access ambient scope |
|
||||
| `IMigrationProvider` | Provider-specific migration execution |
|
||||
| `IMigrationProviderSetup` | DbContext options setup per provider |
|
||||
|
||||
### Provider Constants
|
||||
|
||||
```csharp
|
||||
Constants.ProviderNames.SQLServer = "Microsoft.Data.SqlClient"
|
||||
Constants.ProviderNames.SQLLite = "Microsoft.Data.Sqlite"
|
||||
```
|
||||
|
||||
### Related Projects
|
||||
|
||||
- `Umbraco.Cms.Persistence.EFCore.SqlServer` - SQL Server implementation
|
||||
- `Umbraco.Cms.Persistence.EFCore.Sqlite` - SQLite implementation
|
||||
- `Umbraco.Infrastructure` - Contains NPoco scoping that this integrates with
|
||||
235
src/Umbraco.Cms.Persistence.SqlServer/CLAUDE.md
Normal file
235
src/Umbraco.Cms.Persistence.SqlServer/CLAUDE.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Umbraco.Cms.Persistence.SqlServer
|
||||
|
||||
SQL Server database provider for Umbraco CMS using NPoco ORM. Provides SQL Server-specific SQL syntax, bulk copy operations, distributed locking, and Azure SQL transient fault handling.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Dependencies**: Umbraco.Infrastructure, NPoco.SqlServer, Microsoft.Data.SqlClient
|
||||
|
||||
**Note**: This is the **legacy NPoco-based** SQL Server provider. For EF Core SQL Server support, see `Umbraco.Cms.Persistence.EFCore.SqlServer`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project provides complete SQL Server support for Umbraco's NPoco-based persistence layer:
|
||||
|
||||
1. **SQL Syntax Provider** - SQL Server-specific SQL generation and schema operations
|
||||
2. **Bulk Insert** - True `SqlBulkCopy` for high-performance batch inserts
|
||||
3. **Distributed Locking** - Row-level locking with `REPEATABLEREAD` hints
|
||||
4. **Fault Handling** - Azure SQL and network transient error retry policies
|
||||
5. **LocalDB Support** - SQL Server LocalDB for development
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Cms.Persistence.SqlServer/
|
||||
├── Dtos/
|
||||
│ ├── ColumnInSchemaDto.cs # Schema query results
|
||||
│ ├── ConstraintPerColumnDto.cs
|
||||
│ ├── ConstraintPerTableDto.cs
|
||||
│ ├── DefaultConstraintPerColumnDto.cs
|
||||
│ └── DefinedIndexDto.cs
|
||||
├── FaultHandling/
|
||||
│ ├── RetryPolicyFactory.cs # Creates retry policies
|
||||
│ └── Strategies/
|
||||
│ ├── NetworkConnectivityErrorDetectionStrategy.cs
|
||||
│ ├── SqlAzureTransientErrorDetectionStrategy.cs # Azure SQL error codes
|
||||
│ └── ThrottlingCondition.cs # Azure throttling decode
|
||||
├── Interceptors/
|
||||
│ ├── SqlServerAddMiniProfilerInterceptor.cs
|
||||
│ ├── SqlServerAddRetryPolicyInterceptor.cs
|
||||
│ └── SqlServerConnectionInterceptor.cs # Base interceptor
|
||||
├── Services/
|
||||
│ ├── BulkDataReader.cs # Base bulk reader
|
||||
│ ├── MicrosoftSqlSyntaxProviderBase.cs # Shared SQL syntax
|
||||
│ ├── PocoDataDataReader.cs # NPoco bulk reader
|
||||
│ ├── SqlServerBulkSqlInsertProvider.cs # SqlBulkCopy wrapper
|
||||
│ ├── SqlServerDatabaseCreator.cs
|
||||
│ ├── SqlServerDistributedLockingMechanism.cs
|
||||
│ ├── SqlServerSpecificMapperFactory.cs
|
||||
│ ├── SqlServerSyntaxProvider.cs # Main syntax provider
|
||||
│ ├── SqlAzureDatabaseProviderMetadata.cs
|
||||
│ ├── SqlLocalDbDatabaseProviderMetadata.cs
|
||||
│ └── SqlServerDatabaseProviderMetadata.cs
|
||||
├── Constants.cs # Provider name
|
||||
├── LocalDb.cs # LocalDB management (~1,100 lines)
|
||||
├── NPocoSqlServerDatabaseExtensions.cs # NPoco bulk extensions config
|
||||
├── SqlServerComposer.cs # DI auto-registration
|
||||
└── UmbracoBuilderExtensions.cs # AddUmbracoSqlServerSupport()
|
||||
```
|
||||
|
||||
### Auto-Registration
|
||||
|
||||
When this assembly is referenced, `SqlServerComposer` automatically registers all SQL Server services via `AddUmbracoSqlServerSupport()`.
|
||||
|
||||
### Provider Name Migration
|
||||
|
||||
Auto-migrates legacy `System.Data.SqlClient` to `Microsoft.Data.SqlClient`:
|
||||
|
||||
**Line 53-59**: `UmbracoBuilderExtensions.cs` updates connection string provider name
|
||||
```csharp
|
||||
if (options.ProviderName == "System.Data.SqlClient")
|
||||
{
|
||||
options.ProviderName = Constants.ProviderName; // Microsoft.Data.SqlClient
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### SqlServerDistributedLockingMechanism (Services/SqlServerDistributedLockingMechanism.cs)
|
||||
|
||||
Row-level locking using `REPEATABLEREAD` table hints:
|
||||
|
||||
- **Read locks** (line 147): `SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id`
|
||||
- **Write locks** (line 182-183): `UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = ... WHERE id=@id`
|
||||
- **Timeout**: Uses `SET LOCK_TIMEOUT {milliseconds}` (lines 149, 185)
|
||||
- **Error handling**: Catches SQL error 1222 (lock timeout) → throws `DistributedReadLockTimeoutException` or `DistributedWriteLockTimeoutException`
|
||||
- **Minimum isolation**: Requires `ReadCommitted` or higher (lines 141-145, 176-180)
|
||||
|
||||
### SqlServerBulkSqlInsertProvider (Services/SqlServerBulkSqlInsertProvider.cs)
|
||||
|
||||
True bulk insert using `SqlBulkCopy`:
|
||||
|
||||
```csharp
|
||||
// Line 61-68: SqlBulkCopy configuration
|
||||
new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction)
|
||||
{
|
||||
BulkCopyTimeout = 0, // No timeout (uses connection timeout)
|
||||
DestinationTableName = tableName,
|
||||
BatchSize = 4096, // Consistent with NPoco
|
||||
}
|
||||
```
|
||||
|
||||
**Key features**:
|
||||
- Streams data via `PocoDataDataReader<T>` (line 69) - avoids in-memory DataTable
|
||||
- Explicit column mappings by name (lines 74-77) - prevents column order mismatches
|
||||
- Returns actual count inserted (line 80) - NPoco's `InsertBulk` doesn't provide this
|
||||
|
||||
### SqlAzureTransientErrorDetectionStrategy (FaultHandling/Strategies/)
|
||||
|
||||
Detects transient Azure SQL errors for retry:
|
||||
|
||||
| Error Code | Description |
|
||||
|------------|-------------|
|
||||
| 40501 | Service busy (throttling) |
|
||||
| 10928/10929 | Resource limits reached |
|
||||
| 10053/10054 | Transport-level errors |
|
||||
| 10060 | Connection timeout |
|
||||
| 40197/40540/40143 | Service processing errors |
|
||||
| 40613 | Database not available |
|
||||
| 233 | Connection initialization error |
|
||||
| 64 | Login process error |
|
||||
|
||||
### SqlServerSyntaxProvider (Services/SqlServerSyntaxProvider.cs)
|
||||
|
||||
SQL Server version detection and syntax generation:
|
||||
|
||||
- **Engine editions**: Desktop, Standard, Enterprise, Express, Azure (lines 23-31)
|
||||
- **SQL Server versions**: V7 through V2019, plus Azure (lines 33-47)
|
||||
- **Default isolation**: `ReadCommitted` (line 69)
|
||||
- **Azure detection**: `ServerVersion?.IsAzure` determines `DbProvider` (line 67)
|
||||
- **Version detection**: Populated via `GetDbProviderManifest()` by querying SQL Server metadata
|
||||
|
||||
---
|
||||
|
||||
## 4. LocalDB Support
|
||||
|
||||
`LocalDb.cs` (~1,100 lines) provides comprehensive SQL Server LocalDB management:
|
||||
|
||||
- Database creation/deletion
|
||||
- Instance management
|
||||
- File path handling
|
||||
- Connection string generation
|
||||
|
||||
**Known issues** (from TODO comments):
|
||||
- Line 358: Stale database handling not implemented
|
||||
- Line 359: File name assumptions may not always be correct
|
||||
|
||||
---
|
||||
|
||||
## 5. Differences from SQLite Provider
|
||||
|
||||
| Feature | SQL Server | SQLite |
|
||||
|---------|-----------|--------|
|
||||
| **Bulk Insert** | `SqlBulkCopy` (true bulk) | Transaction + individual inserts |
|
||||
| **Locking** | Row-level with `REPEATABLEREAD` hints | Database-level (WAL mode) |
|
||||
| **Types** | Native (NVARCHAR, DECIMAL, UNIQUEIDENTIFIER) | TEXT for most types |
|
||||
| **Transient Retry** | Azure SQL error codes + network errors | BUSY/LOCKED retry only |
|
||||
| **LocalDB** | Full support (~1,100 lines) | N/A |
|
||||
|
||||
---
|
||||
|
||||
## 6. Project-Specific Notes
|
||||
|
||||
### InternalsVisibleTo
|
||||
|
||||
Test projects have access to internal types:
|
||||
```xml
|
||||
<InternalsVisibleTo>Umbraco.Tests.Integration</InternalsVisibleTo>
|
||||
<InternalsVisibleTo>Umbraco.Tests.UnitTests</InternalsVisibleTo>
|
||||
```
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **Warning Suppressions** (`.csproj:8-22`): Multiple analyzer warnings suppressed
|
||||
- StyleCop: SA1405, SA1121, SA1117
|
||||
- IDE: 1006 (naming), 0270/0057/0054/0048 (simplification)
|
||||
- CS0618 (obsolete usage), CS1574 (XML comments)
|
||||
|
||||
2. **BulkInsert TODO** (`SqlServerBulkSqlInsertProvider.cs:44-46`): Custom `SqlBulkCopy` implementation used because NPoco's `InsertBulk` doesn't return record count. Performance comparison vs NPoco's DataTable approach pending.
|
||||
|
||||
3. **LocalDB TODOs** (`LocalDb.cs:358-359`): Stale database cleanup and file name assumption handling incomplete.
|
||||
|
||||
### NPoco Bulk Extensions
|
||||
|
||||
`NPocoSqlServerDatabaseExtensions.ConfigureNPocoBulkExtensions()` is called during registration (line 50) to configure NPoco's SQL Server bulk operations.
|
||||
|
||||
### Three Provider Metadata Types
|
||||
|
||||
Different metadata for different SQL Server variants:
|
||||
- `SqlServerDatabaseProviderMetadata` - Standard SQL Server
|
||||
- `SqlLocalDbDatabaseProviderMetadata` - LocalDB
|
||||
- `SqlAzureDatabaseProviderMetadata` - Azure SQL
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `SqlServerSyntaxProvider.cs` | Core SQL syntax provider |
|
||||
| `SqlServerDistributedLockingMechanism.cs` | Row-level locking |
|
||||
| `SqlServerBulkSqlInsertProvider.cs` | SqlBulkCopy wrapper |
|
||||
| `SqlAzureTransientErrorDetectionStrategy.cs` | Azure retry logic |
|
||||
| `LocalDb.cs` | LocalDB management |
|
||||
|
||||
### Provider Name
|
||||
`Constants.ProviderName` = `"Microsoft.Data.SqlClient"`
|
||||
|
||||
### Registered Services
|
||||
|
||||
All registered via `TryAddEnumerable`:
|
||||
- `ISqlSyntaxProvider` → `SqlServerSyntaxProvider`
|
||||
- `IBulkSqlInsertProvider` → `SqlServerBulkSqlInsertProvider`
|
||||
- `IDatabaseCreator` → `SqlServerDatabaseCreator`
|
||||
- `IProviderSpecificMapperFactory` → `SqlServerSpecificMapperFactory`
|
||||
- `IDatabaseProviderMetadata` → Three variants (SqlServer, LocalDb, Azure)
|
||||
- `IDistributedLockingMechanism` → `SqlServerDistributedLockingMechanism`
|
||||
- `IProviderSpecificInterceptor` → MiniProfiler and RetryPolicy interceptors
|
||||
|
||||
### Related Projects
|
||||
- **Sibling**: `Umbraco.Cms.Persistence.Sqlite` - SQLite NPoco provider
|
||||
- **EF Core**: `Umbraco.Cms.Persistence.EFCore.SqlServer` - EF Core SQL Server provider
|
||||
204
src/Umbraco.Cms.Persistence.Sqlite/CLAUDE.md
Normal file
204
src/Umbraco.Cms.Persistence.Sqlite/CLAUDE.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Umbraco.Cms.Persistence.Sqlite
|
||||
|
||||
SQLite database provider for Umbraco CMS using NPoco ORM. Provides SQLite-specific SQL syntax, type mappers, distributed locking, and connection interceptors.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Dependencies**: Umbraco.Infrastructure, Microsoft.Data.Sqlite
|
||||
|
||||
**Note**: This is the **legacy NPoco-based** SQLite provider. For EF Core SQLite support, see `Umbraco.Cms.Persistence.EFCore.Sqlite`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project provides complete SQLite support for Umbraco's NPoco-based persistence layer:
|
||||
|
||||
1. **SQL Syntax Provider** - SQLite-specific SQL generation and schema operations
|
||||
2. **Type Mappers** - GUID, decimal, date/time mapping for SQLite's type system
|
||||
3. **Distributed Locking** - SQLite-specific locking using WAL mode
|
||||
4. **Connection Interceptors** - Deferred transactions, MiniProfiler, retry policies
|
||||
5. **Bulk Insert** - SQLite-optimized record insertion
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Cms.Persistence.Sqlite/
|
||||
├── Interceptors/
|
||||
│ ├── SqliteAddMiniProfilerInterceptor.cs # MiniProfiler integration
|
||||
│ ├── SqliteAddPreferDeferredInterceptor.cs # Deferred transaction wrapper
|
||||
│ ├── SqliteAddRetryPolicyInterceptor.cs # Transient error retry
|
||||
│ └── SqliteConnectionInterceptor.cs # Base interceptor
|
||||
├── Mappers/
|
||||
│ ├── SqliteGuidScalarMapper.cs # GUID as TEXT mapping
|
||||
│ ├── SqlitePocoDateAndTimeOnlyMapper.cs # Date/Time as TEXT
|
||||
│ ├── SqlitePocoDecimalMapper.cs # Decimal as TEXT (lossless)
|
||||
│ └── SqlitePocoGuidMapper.cs # GUID for NPoco
|
||||
├── Services/
|
||||
│ ├── SqliteBulkSqlInsertProvider.cs # Bulk insert via transactions
|
||||
│ ├── SqliteDatabaseCreator.cs # Database initialization
|
||||
│ ├── SqliteDatabaseProviderMetadata.cs # Provider info
|
||||
│ ├── SqliteDistributedLockingMechanism.cs # WAL-based locking
|
||||
│ ├── SqliteExceptionExtensions.cs # Error code helpers
|
||||
│ ├── SqlitePreferDeferredTransactionsConnection.cs # Deferred tx wrapper
|
||||
│ ├── SqliteSpecificMapperFactory.cs # Type mapper factory
|
||||
│ ├── SqliteSyntaxProvider.cs # SQL syntax (481 lines)
|
||||
│ └── SqliteTransientErrorDetectionStrategy.cs # BUSY/LOCKED detection
|
||||
├── Constants.cs # Provider name constant
|
||||
├── SqliteComposer.cs # DI auto-registration
|
||||
└── UmbracoBuilderExtensions.cs # AddUmbracoSqliteSupport()
|
||||
```
|
||||
|
||||
### Auto-Registration
|
||||
|
||||
When this assembly is referenced, `SqliteComposer` automatically registers all SQLite services via the `AddUmbracoSqliteSupport()` extension method.
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### SqliteSyntaxProvider (Services/SqliteSyntaxProvider.cs)
|
||||
|
||||
The core SQL syntax provider implementing `ISqlSyntaxProvider`. Key characteristics:
|
||||
|
||||
- **All strings stored as TEXT** with `COLLATE NOCASE` (case-insensitive)
|
||||
- **GUIDs stored as TEXT** (no native GUID support)
|
||||
- **Decimals stored as TEXT** to avoid REAL precision loss (line 51)
|
||||
- **Default isolation level**: `IsolationLevel.Serializable` (line 66-67)
|
||||
- **No identity insert support** (line 73)
|
||||
- **No clustered index support** (line 76)
|
||||
- **LIMIT instead of TOP** for pagination (line 273-278)
|
||||
- **AUTOINCREMENT required** to prevent magic ID issues with negative IDs (line 224-228)
|
||||
|
||||
### SqliteDistributedLockingMechanism (Services/SqliteDistributedLockingMechanism.cs)
|
||||
|
||||
Database-level locking using SQLite WAL mode:
|
||||
|
||||
- **Read locks**: Snapshot isolation via WAL (mostly no-op, just validates transaction exists)
|
||||
- **Write locks**: Only one writer at a time (entire database locked)
|
||||
- **Timeout handling**: Uses `CommandTimeout` for busy-wait (line 163)
|
||||
- **Error handling**: Catches `SQLITE_BUSY` and `SQLITE_LOCKED` errors
|
||||
|
||||
### SqlitePreferDeferredTransactionsConnection (Services/SqlitePreferDeferredTransactionsConnection.cs)
|
||||
|
||||
Wraps `SqliteConnection` to force deferred transactions:
|
||||
|
||||
```csharp
|
||||
// Line 33-34: The critical behavior
|
||||
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
|
||||
=> _inner.BeginTransaction(isolationLevel, true); // <-- The important bit
|
||||
```
|
||||
|
||||
This prevents immediate write locks when beginning transactions, allowing multiple readers.
|
||||
|
||||
### Connection Interceptors
|
||||
|
||||
- **SqliteAddPreferDeferredInterceptor** - Wraps connections with deferred transaction support
|
||||
- **SqliteAddMiniProfilerInterceptor** - Adds profiling for development
|
||||
- **SqliteAddRetryPolicyInterceptor** - Retries on transient SQLite errors
|
||||
|
||||
---
|
||||
|
||||
## 4. SQLite Type Mapping
|
||||
|
||||
SQLite has limited types. Umbraco maps .NET types as follows:
|
||||
|
||||
| .NET Type | SQLite Type | Notes |
|
||||
|-----------|-------------|-------|
|
||||
| `int`, `long`, `bool` | INTEGER | Native support |
|
||||
| `string` | TEXT COLLATE NOCASE | Case-insensitive |
|
||||
| `Guid` | TEXT | Stored as string representation |
|
||||
| `DateTime`, `DateTimeOffset` | TEXT | ISO format string |
|
||||
| `decimal` | TEXT | Prevents REAL precision loss |
|
||||
| `double`, `float` | REAL | Native support |
|
||||
| `byte[]` | BLOB | Native support |
|
||||
|
||||
---
|
||||
|
||||
## 5. Project-Specific Notes
|
||||
|
||||
### Database Creation Prevention
|
||||
|
||||
`UmbracoBuilderExtensions.cs` (line 49-64) prevents accidental database file creation:
|
||||
|
||||
```csharp
|
||||
// Changes ReadWriteCreate mode to ReadWrite only
|
||||
if (connectionStringBuilder.Mode == SqliteOpenMode.ReadWriteCreate)
|
||||
{
|
||||
connectionStringBuilder.Mode = SqliteOpenMode.ReadWrite;
|
||||
}
|
||||
```
|
||||
|
||||
This ensures the database must exist before Umbraco connects.
|
||||
|
||||
### WAL Mode Locking
|
||||
|
||||
SQLite with WAL (Write-Ahead Logging) journal mode:
|
||||
- Multiple readers can access snapshots concurrently
|
||||
- Only one writer at a time (database-level lock)
|
||||
- Write lock uses `umbracoLock` table with UPDATE to acquire
|
||||
|
||||
### Bulk Insert Implementation
|
||||
|
||||
`SqliteBulkSqlInsertProvider` (line 38-60) doesn't use true bulk copy (SQLite doesn't support it). Instead:
|
||||
1. Wraps inserts in a transaction if not already in one
|
||||
2. Inserts records one-by-one with `database.Insert()`
|
||||
3. Completes transaction
|
||||
|
||||
This is slower than SQL Server's `SqlBulkCopy` but safe for SQLite.
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **Warning Suppression** (`.csproj:8-12`): `CS0114` - hiding inherited members needs fixing
|
||||
2. **TODO in SqliteSyntaxProvider** (line 178): `TryGetDefaultConstraint` not implemented for SQLite
|
||||
|
||||
### Differences from SQL Server Provider
|
||||
|
||||
| Feature | SQLite | SQL Server |
|
||||
|---------|--------|------------|
|
||||
| Bulk Insert | Transaction + individual inserts | SqlBulkCopy |
|
||||
| Locking | Database-level (WAL) | Row-level |
|
||||
| Identity Insert | Not supported | Supported |
|
||||
| Clustered Indexes | Not supported | Supported |
|
||||
| TOP clause | LIMIT | TOP |
|
||||
| Decimal | TEXT (lossless) | DECIMAL |
|
||||
| GUID | TEXT | UNIQUEIDENTIFIER |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `SqliteSyntaxProvider.cs` | Core SQL syntax provider |
|
||||
| `SqliteDistributedLockingMechanism.cs` | Database locking |
|
||||
| `UmbracoBuilderExtensions.cs` | DI registration |
|
||||
| `SqlitePreferDeferredTransactionsConnection.cs` | Deferred tx support |
|
||||
|
||||
### Provider Name
|
||||
`Constants.ProviderName` = `"Microsoft.Data.Sqlite"`
|
||||
|
||||
### Registered Services
|
||||
|
||||
All registered via `TryAddEnumerable` (allowing multiple providers):
|
||||
- `ISqlSyntaxProvider` → `SqliteSyntaxProvider`
|
||||
- `IBulkSqlInsertProvider` → `SqliteBulkSqlInsertProvider`
|
||||
- `IDatabaseCreator` → `SqliteDatabaseCreator`
|
||||
- `IProviderSpecificMapperFactory` → `SqliteSpecificMapperFactory`
|
||||
- `IDatabaseProviderMetadata` → `SqliteDatabaseProviderMetadata`
|
||||
- `IDistributedLockingMechanism` → `SqliteDistributedLockingMechanism`
|
||||
- `IProviderSpecificInterceptor` → Three interceptors
|
||||
|
||||
### Related Projects
|
||||
- **Sibling**: `Umbraco.Cms.Persistence.SqlServer` - SQL Server NPoco provider
|
||||
- **EF Core**: `Umbraco.Cms.Persistence.EFCore.Sqlite` - EF Core SQLite provider
|
||||
260
src/Umbraco.Cms.StaticAssets/CLAUDE.md
Normal file
260
src/Umbraco.Cms.StaticAssets/CLAUDE.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# Umbraco.Cms.StaticAssets
|
||||
|
||||
Static assets and Razor views for Umbraco CMS. This is a Razor Class Library (RCL) that packages all backoffice, login, and website assets for deployment.
|
||||
|
||||
**Project Type**: Razor Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**SDK**: Microsoft.NET.Sdk.Razor
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project packages all static assets required for Umbraco CMS at runtime:
|
||||
|
||||
1. **Razor Views** - Server-rendered pages for backoffice, login, and error pages
|
||||
2. **Static Web Assets** - JavaScript, CSS, fonts, images served at runtime
|
||||
3. **Build Integration** - MSBuild targets that compile TypeScript/frontend projects on demand
|
||||
|
||||
### Key Characteristic
|
||||
|
||||
**No C# Source Code** - This is a pure asset packaging project. All functionality comes from referenced projects (`Umbraco.Cms.Api.Management`, `Umbraco.Web.Website`).
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Cms.StaticAssets/
|
||||
├── umbraco/ # Razor Views (server-rendered)
|
||||
│ ├── UmbracoBackOffice/
|
||||
│ │ └── Index.cshtml # Backoffice SPA shell (69 lines)
|
||||
│ ├── UmbracoLogin/
|
||||
│ │ └── Index.cshtml # Login page (99 lines)
|
||||
│ └── UmbracoWebsite/
|
||||
│ ├── NoNodes.cshtml # "No published content" page
|
||||
│ ├── NotFound.cshtml # 404 error page
|
||||
│ └── Maintenance.cshtml # Maintenance mode page
|
||||
│
|
||||
├── wwwroot/ # Static Web Assets
|
||||
│ ├── App_Plugins/
|
||||
│ │ └── Umbraco.BlockGridEditor.DefaultCustomViews/ # Block grid demo templates
|
||||
│ └── umbraco/
|
||||
│ ├── assets/ # Logos and branding (see README.md)
|
||||
│ ├── backoffice/ # Built backoffice SPA (from Umbraco.Web.UI.Client)
|
||||
│ │ ├── apps/ # Application modules
|
||||
│ │ ├── assets/ # Fonts, language files
|
||||
│ │ ├── css/ # Themes and stylesheets
|
||||
│ │ └── monaco-editor/ # Code editor assets
|
||||
│ ├── login/ # Built login SPA (from Umbraco.Web.UI.Login)
|
||||
│ └── website/ # Frontend website assets (fonts, CSS)
|
||||
│
|
||||
└── Umbraco.Cms.StaticAssets.csproj # Build configuration (149 lines)
|
||||
```
|
||||
|
||||
### Project Dependencies
|
||||
|
||||
```xml
|
||||
<ProjectReference Include="..\Umbraco.Cms.Api.Management\Umbraco.Cms.Api.Management.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### Razor Views
|
||||
|
||||
| View | Purpose | Line Count |
|
||||
|------|---------|------------|
|
||||
| `UmbracoBackOffice/Index.cshtml` | Backoffice SPA entry point with `<umb-app>` web component | 69 |
|
||||
| `UmbracoLogin/Index.cshtml` | Login page with `<umb-auth>` web component | 99 |
|
||||
| `UmbracoWebsite/NoNodes.cshtml` | Welcome page when no content published | 59 |
|
||||
| `UmbracoWebsite/NotFound.cshtml` | 404 error page (debug info in debug mode) | 79 |
|
||||
| `UmbracoWebsite/Maintenance.cshtml` | Maintenance mode during upgrades | 65 |
|
||||
|
||||
### Backoffice Index View (umbraco/UmbracoBackOffice/Index.cshtml)
|
||||
|
||||
Key injected services:
|
||||
- `IBackOfficePathGenerator` - Generates backoffice URL paths
|
||||
- `IPackageManifestService` - Package manifest discovery
|
||||
- `IJsonSerializer` - JSON serialization for import maps
|
||||
- `IProfilerHtml` - MiniProfiler integration
|
||||
|
||||
Key features:
|
||||
- **Import Maps** (line 35): `Html.BackOfficeImportMapScriptAsync()` generates JavaScript module import maps
|
||||
- **Debug Mode** (line 16, 63-66): `?umbDebug=true` query param enables profiler
|
||||
- **NoScript Fallback** (lines 40-60): Displays message if JavaScript disabled
|
||||
|
||||
### Login View (umbraco/UmbracoLogin/Index.cshtml)
|
||||
|
||||
Configures `<umb-auth>` web component with attributes:
|
||||
- `return-url` - Redirect after login
|
||||
- `logo-image` / `background-image` - Branding URLs from `BackOfficeGraphicsController`
|
||||
- `username-is-email` - From `SecuritySettings`
|
||||
- `allow-user-invite` / `allow-password-reset` - Email capability check
|
||||
- `disable-local-login` - External login provider configuration
|
||||
|
||||
---
|
||||
|
||||
## 4. Build System
|
||||
|
||||
### Frontend Build Integration (csproj lines 36-90)
|
||||
|
||||
The `.csproj` contains MSBuild targets that automatically build frontend projects when assets are missing.
|
||||
|
||||
**Backoffice Build** (lines 36-90):
|
||||
```
|
||||
BackofficeProjectDirectory = ../Umbraco.Web.UI.Client/
|
||||
BackofficeAssetsPath = wwwroot/umbraco/backoffice
|
||||
```
|
||||
|
||||
**Login Build** (lines 94-148):
|
||||
```
|
||||
LoginProjectDirectory = ../Umbraco.Web.UI.Login/
|
||||
LoginAssetsPath = wwwroot/umbraco/login
|
||||
```
|
||||
|
||||
### Build Targets
|
||||
|
||||
| Target | Purpose |
|
||||
|--------|---------|
|
||||
| `BuildStaticAssetsPreconditions` | Checks if build needed (Visual Studio only) |
|
||||
| `RestoreBackoffice` | Runs `npm i` if package-lock changed |
|
||||
| `BuildBackoffice` | Runs `npm run build:for:cms` |
|
||||
| `DefineBackofficeAssets` | Registers assets with StaticWebAssets system |
|
||||
| `CleanBackoffice` | Removes built assets on `dotnet clean` |
|
||||
|
||||
### Build Conditions
|
||||
|
||||
- **UmbracoBuild Variable**: When set (CI/CD), skips frontend builds (pre-built assets expected)
|
||||
- **Visual Studio Detection**: `'$(UmbracoBuild)' == ''` indicates VS build
|
||||
- **preserve.backoffice Marker**: Skip clean if `preserve.backoffice` file exists in solution root
|
||||
|
||||
---
|
||||
|
||||
## 5. Static Web Assets
|
||||
|
||||
### Asset Categories
|
||||
|
||||
| Directory | Contents | Served At |
|
||||
|-----------|----------|-----------|
|
||||
| `wwwroot/umbraco/assets/` | Branding logos | Via `BackOfficeGraphicsController` API |
|
||||
| `wwwroot/umbraco/backoffice/` | Built backoffice SPA | `/umbraco/backoffice/*` |
|
||||
| `wwwroot/umbraco/login/` | Built login SPA | `/umbraco/login/*` |
|
||||
| `wwwroot/umbraco/website/` | Website assets (fonts, CSS) | `/umbraco/website/*` |
|
||||
| `wwwroot/App_Plugins/` | Block editor demo views | `/App_Plugins/*` |
|
||||
|
||||
### Logo Assets (wwwroot/umbraco/assets/)
|
||||
|
||||
Documented in `wwwroot/umbraco/assets/README.md` (16 lines):
|
||||
|
||||
| File | Usage | API Endpoint |
|
||||
|------|-------|--------------|
|
||||
| `logo.svg` | Backoffice and public sites | `/umbraco/management/api/v1/security/back-office/graphics/logo` |
|
||||
| `logo_dark.svg` | Login screen (dark mode) | `.../graphics/login-logo-alternative` |
|
||||
| `logo_light.svg` | Login screen (light mode) | `.../graphics/login-logo` |
|
||||
| `logo_blue.svg` | Alternative branding | N/A |
|
||||
|
||||
### Block Grid Demo Views (wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/)
|
||||
|
||||
Pre-built AngularJS templates for block grid editor demos:
|
||||
- `umbBlockGridDemoHeadlineBlock.html`
|
||||
- `umbBlockGridDemoImageBlock.html`
|
||||
- `umbBlockGridDemoRichTextBlock.html`
|
||||
- `umbBlockGridDemoTwoColumnLayoutBlock.html`
|
||||
|
||||
---
|
||||
|
||||
## 6. Project-Specific Notes
|
||||
|
||||
### Static Web Asset Base Path
|
||||
|
||||
```xml
|
||||
<StaticWebAssetBasePath>/</StaticWebAssetBasePath>
|
||||
```
|
||||
|
||||
Assets are served from root path, not under assembly name.
|
||||
|
||||
### Compression Disabled
|
||||
|
||||
```xml
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
```
|
||||
|
||||
Comment notes `MapStaticAssets()` is not used (yet).
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **Warning Suppression** (`.csproj:14-16`): `NU5123` - File paths too long for NuGet package. TODO indicates files should be renamed.
|
||||
|
||||
2. **Excluded Content** (`.csproj:31`): `wwwroot/umbraco/assets/README.md` explicitly excluded from package.
|
||||
|
||||
### Backoffice Localization
|
||||
|
||||
The backoffice includes language files for 25+ languages in `wwwroot/umbraco/backoffice/assets/lang/`:
|
||||
- ar, bs, cs, cy, da, de, en, en-us, es, fr, he, hr, it, ja, ko, nb, nl, pl, pt, pt-br, ro, ru, sv, tr, uk, zh, zh-tw
|
||||
|
||||
### Monaco Editor
|
||||
|
||||
Full Monaco code editor included at `wwwroot/umbraco/backoffice/monaco-editor/` for rich code editing in backoffice.
|
||||
|
||||
### Theming
|
||||
|
||||
CSS themes in `wwwroot/umbraco/backoffice/css/`:
|
||||
- `umb-css.css` - Main styles
|
||||
- `uui-css.css` - UI library styles
|
||||
- `dark.theme.css` - Dark theme
|
||||
- `high-contrast.theme.css` - Accessibility theme
|
||||
- `umbraco-blockgridlayout.css` - Block grid styles
|
||||
- `rte-content.css` - Rich text editor content styles
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `umbraco/UmbracoBackOffice/Index.cshtml` | Backoffice entry point |
|
||||
| `umbraco/UmbracoLogin/Index.cshtml` | Login page |
|
||||
| `wwwroot/umbraco/assets/README.md` | Asset documentation |
|
||||
| `Umbraco.Cms.StaticAssets.csproj` | Build targets for frontend |
|
||||
|
||||
### Related Projects
|
||||
|
||||
| Project | Relationship |
|
||||
|---------|--------------|
|
||||
| `Umbraco.Web.UI.Client` | Source for backoffice assets (npm build) |
|
||||
| `Umbraco.Web.UI.Login` | Source for login assets (npm build) |
|
||||
| `Umbraco.Cms.Api.Management` | Razor view dependencies |
|
||||
| `Umbraco.Web.Website` | Razor view dependencies |
|
||||
|
||||
### Build Commands (Manual)
|
||||
|
||||
```bash
|
||||
# Build backoffice assets (from Umbraco.Web.UI.Client)
|
||||
cd src/Umbraco.Web.UI.Client
|
||||
npm install
|
||||
npm run build:for:cms
|
||||
|
||||
# Build login assets (from Umbraco.Web.UI.Login)
|
||||
cd src/Umbraco.Web.UI.Login
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Preserve Assets During Clean
|
||||
|
||||
Create marker file to prevent asset deletion:
|
||||
```bash
|
||||
touch preserve.backoffice # In solution root
|
||||
touch preserve.login # In solution root
|
||||
```
|
||||
293
src/Umbraco.Examine.Lucene/CLAUDE.md
Normal file
293
src/Umbraco.Examine.Lucene/CLAUDE.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# Umbraco.Examine.Lucene
|
||||
|
||||
Full-text search provider for Umbraco CMS using Examine and Lucene.NET. Provides content, media, member, and Delivery API indexing with configurable directory factories and index diagnostics.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Package ID**: Umbraco.Cms.Examine.Lucene
|
||||
**Namespace**: Umbraco.Cms.Infrastructure.Examine
|
||||
**Dependencies**: Umbraco.Infrastructure, Examine (Lucene.NET wrapper)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project provides Lucene-based full-text search capabilities for Umbraco:
|
||||
|
||||
1. **Index Types** - Content, Media, Member, and Delivery API indexes
|
||||
2. **Directory Factories** - Configurable index storage (filesystem, temp, synced)
|
||||
3. **Index Diagnostics** - Health checks and metadata for backoffice
|
||||
4. **Backoffice Search** - Unified search across content tree with permissions
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Examine.Lucene/
|
||||
├── DependencyInjection/
|
||||
│ ├── ConfigureIndexOptions.cs # Per-index configuration (77 lines)
|
||||
│ └── UmbracoBuilderExtensions.cs # AddExamineIndexes() registration (64 lines)
|
||||
├── Extensions/
|
||||
│ └── ExamineExtensions.cs # Lucene query parsing, health check
|
||||
├── AddExamineComposer.cs # Auto-registration via IComposer
|
||||
├── BackOfficeExamineSearcher.cs # Unified backoffice search (532 lines)
|
||||
├── ConfigurationEnabledDirectoryFactory.cs # Directory factory selector
|
||||
├── DeliveryApiContentIndex.cs # Headless API index with culture support
|
||||
├── LuceneIndexDiagnostics.cs # Base diagnostics implementation
|
||||
├── LuceneIndexDiagnosticsFactory.cs # Diagnostics factory
|
||||
├── LuceneRAMDirectoryFactory.cs # In-memory directory for testing
|
||||
├── NoPrefixSimpleFsLockFactory.cs # Lock factory without prefix
|
||||
├── UmbracoApplicationRoot.cs # Application root path provider
|
||||
├── UmbracoContentIndex.cs # Content/media index (158 lines)
|
||||
├── UmbracoExamineIndex.cs # Base index class (153 lines)
|
||||
├── UmbracoExamineIndexDiagnostics.cs # Extended diagnostics
|
||||
├── UmbracoLockFactory.cs # Lock factory wrapper
|
||||
├── UmbracoMemberIndex.cs # Member index
|
||||
└── UmbracoTempEnvFileSystemDirectoryFactory.cs # Temp directory factory
|
||||
```
|
||||
|
||||
### Index Hierarchy
|
||||
|
||||
```
|
||||
LuceneIndex (Examine)
|
||||
└── UmbracoExamineIndex (base for all Umbraco indexes)
|
||||
├── UmbracoContentIndex (content + media)
|
||||
├── UmbracoMemberIndex (members)
|
||||
└── DeliveryApiContentIndex (headless API)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### Built-in Indexes (Constants.UmbracoIndexes)
|
||||
|
||||
| Index Name | Type | Purpose |
|
||||
|------------|------|---------|
|
||||
| `InternalIndex` | UmbracoContentIndex | Backoffice search (all content) |
|
||||
| `ExternalIndex` | UmbracoContentIndex | Frontend published content |
|
||||
| `MembersIndex` | UmbracoMemberIndex | Member search |
|
||||
| `DeliveryApiContentIndex` | DeliveryApiContentIndex | Headless API content |
|
||||
|
||||
**Note:** See [ConfigureIndexOptions](#configureindexoptions-dependencyinjectionconfigureindexoptionscs) for per-index analyzer and validator configuration.
|
||||
|
||||
### UmbracoExamineIndex (UmbracoExamineIndex.cs)
|
||||
|
||||
Base class for all Umbraco indexes. Key features:
|
||||
|
||||
**Runtime State Check** (lines 84-95):
|
||||
```csharp
|
||||
protected bool CanInitialize()
|
||||
{
|
||||
var canInit = _runtimeState.Level == RuntimeLevel.Run;
|
||||
// Logs warning once if runtime not ready
|
||||
}
|
||||
```
|
||||
Prevents indexing during install/upgrade.
|
||||
|
||||
**Special Field Transformations** (lines 130-152):
|
||||
- `__Path` field - Enables descendant queries via path matching
|
||||
- `__Icon` field - Preserves icon for display
|
||||
|
||||
**Raw Field Storage** (lines 101-118):
|
||||
Fields prefixed with `__Raw_` are stored as `StoredField` (not analyzed), used for returning exact values.
|
||||
|
||||
### UmbracoContentIndex (UmbracoContentIndex.cs)
|
||||
|
||||
Handles content and media indexing with validation and cascade deletes.
|
||||
|
||||
**Cascade Delete** (lines 128-157):
|
||||
When content deleted, automatically finds and removes all descendants:
|
||||
```csharp
|
||||
var descendantPath = $@"\-1\,*{nodeId}\,*";
|
||||
var rawQuery = $"{UmbracoExamineFieldNames.IndexPathFieldName}:{descendantPath}";
|
||||
```
|
||||
|
||||
**Validation Groups** (lines 66-116):
|
||||
- `Valid` - Index normally
|
||||
- `Failed` - Skip (invalid data)
|
||||
- `Filtered` - Delete from index (moved to recycle bin)
|
||||
|
||||
### DeliveryApiContentIndex (DeliveryApiContentIndex.cs)
|
||||
|
||||
Specialized index for Delivery API with culture support.
|
||||
|
||||
**Key Differences** (lines 20-34):
|
||||
- `ApplySpecialValueTransformations = false` - No path/icon transformations
|
||||
- `PublishedValuesOnly = false` - Handles via populator
|
||||
- `EnableDefaultEventHandler = false` - Custom event handling
|
||||
|
||||
**Composite IDs** (lines 118-128):
|
||||
Index IDs can be composite: `"1234|da-DK"` (content ID + culture) or simple: `"1234"`.
|
||||
|
||||
### BackOfficeExamineSearcher (BackOfficeExamineSearcher.cs)
|
||||
|
||||
Unified search for backoffice tree with user permissions.
|
||||
|
||||
**Search Features**:
|
||||
- Node name boosting (10x for exact match, line 316-327)
|
||||
- Wildcard support for partial matches (line 350-376)
|
||||
- User start node filtering (lines 378-456)
|
||||
- Recycle bin filtering (lines 185-191)
|
||||
- Multi-language variant search (all `nodeName_{lang}` fields)
|
||||
|
||||
**Entity Type Routing** (lines 89-155): Member→MembersIndex, Media→InternalIndex, Document→InternalIndex
|
||||
|
||||
---
|
||||
|
||||
## 4. Directory Factories
|
||||
|
||||
### Configuration Options (IndexCreatorSettings.LuceneDirectoryFactory)
|
||||
|
||||
- `Default` - FileSystemDirectoryFactory (standard filesystem storage)
|
||||
- `TempFileSystemDirectoryFactory` - UmbracoTempEnvFileSystemDirectoryFactory (store in `%TEMP%/ExamineIndexes`)
|
||||
- `SyncedTempFileSystemDirectoryFactory` - SyncedFileSystemDirectoryFactory (temp with sync to persistent)
|
||||
|
||||
### UmbracoTempEnvFileSystemDirectoryFactory (lines 20-34)
|
||||
|
||||
Creates unique temp path using site name + application identifier hash:
|
||||
```csharp
|
||||
var hashString = hostingEnvironment.SiteName + "::" + applicationIdentifier.GetApplicationUniqueIdentifier();
|
||||
var cachePath = Path.Combine(Path.GetTempPath(), "ExamineIndexes", appDomainHash);
|
||||
```
|
||||
|
||||
**Purpose**: Prevents index collisions when same app moves between workers in load-balanced scenarios.
|
||||
|
||||
### ConfigurationEnabledDirectoryFactory (lines 34-62)
|
||||
|
||||
Selector that creates appropriate factory based on `IndexCreatorSettings.LuceneDirectoryFactory` config value.
|
||||
|
||||
---
|
||||
|
||||
## 5. Index Configuration
|
||||
|
||||
### ConfigureIndexOptions (DependencyInjection/ConfigureIndexOptions.cs)
|
||||
|
||||
Configures per-index options via `IConfigureNamedOptions<LuceneDirectoryIndexOptions>`.
|
||||
|
||||
**Per-Index Settings** (lines 39-61):
|
||||
|
||||
| Index | Analyzer | Validator |
|
||||
|-------|----------|-----------|
|
||||
| InternalIndex | CultureInvariantWhitespaceAnalyzer | ContentValueSetValidator |
|
||||
| ExternalIndex | StandardAnalyzer | PublishedContentValueSetValidator |
|
||||
| MembersIndex | CultureInvariantWhitespaceAnalyzer | MemberValueSetValidator |
|
||||
| DeliveryApiContentIndex | StandardAnalyzer | None (populator handles) |
|
||||
|
||||
**Global Settings** (lines 63-70):
|
||||
- `UnlockIndex = true` - Always unlock on startup
|
||||
- Snapshot deletion policy when using SyncedTempFileSystemDirectoryFactory
|
||||
|
||||
---
|
||||
|
||||
## 6. Index Diagnostics
|
||||
|
||||
### LuceneIndexDiagnostics (LuceneIndexDiagnostics.cs)
|
||||
|
||||
Provides health checks and metadata for backoffice examine dashboard.
|
||||
|
||||
**Health Check** (lines 41-45):
|
||||
```csharp
|
||||
public Attempt<string?> IsHealthy()
|
||||
{
|
||||
var isHealthy = Index.IsHealthy(out Exception? indexError);
|
||||
return isHealthy ? Attempt<string?>.Succeed() : Attempt.Fail(indexError?.Message);
|
||||
}
|
||||
```
|
||||
|
||||
**Metadata** (lines 51-92):
|
||||
- CommitCount, DefaultAnalyzer, LuceneDirectory type
|
||||
- LuceneIndexFolder (relative path)
|
||||
- DirectoryFactory and IndexDeletionPolicy types
|
||||
|
||||
### UmbracoExamineIndexDiagnostics (UmbracoExamineIndexDiagnostics.cs)
|
||||
|
||||
Extends base with Umbraco-specific metadata (lines 23-48):
|
||||
- EnableDefaultEventHandler
|
||||
- PublishedValuesOnly
|
||||
- Validator settings (IncludeItemTypes, ExcludeItemTypes, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 7. Project-Specific Notes
|
||||
|
||||
### Auto-Registration
|
||||
|
||||
`AddExamineComposer` (lines 10-14) automatically registers all Examine services:
|
||||
```csharp
|
||||
builder
|
||||
.AddExamine()
|
||||
.AddExamineIndexes();
|
||||
```
|
||||
|
||||
### Service Registrations (UmbracoBuilderExtensions.cs)
|
||||
|
||||
Key services registered (lines 18-62): `IBackOfficeExamineSearcher`, `IIndexDiagnosticsFactory`, `IApplicationRoot`, `ILockFactory`, plus all 4 indexes with `ConfigurationEnabledDirectoryFactory`.
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **Warning Suppression** (`.csproj:10-14`): `CS0618` - Uses obsolete members in Examine/Lucene.NET
|
||||
|
||||
2. **TODO: Raw Query Support** (`BackOfficeExamineSearcher.cs:71-76`):
|
||||
```csharp
|
||||
// TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string
|
||||
// manipulation for things like start paths, member types, etc...
|
||||
```
|
||||
|
||||
3. **TODO: Query Parsing** (`ExamineExtensions.cs:21-22`):
|
||||
```csharp
|
||||
// TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet
|
||||
```
|
||||
|
||||
### Index Field Names
|
||||
|
||||
Key fields from `UmbracoExamineFieldNames` (defined in Umbraco.Core):
|
||||
- `__Path` - Content path for descendant queries
|
||||
- `__Icon` - Content icon
|
||||
- `__Raw_*` - Raw stored values (not analyzed)
|
||||
- `__Key` - Content GUID
|
||||
- `__NodeTypeAlias` - Content type alias
|
||||
- `__IndexType` - `content`, `media`, or `member`
|
||||
|
||||
### Lucene Query Escaping
|
||||
|
||||
`BackOfficeExamineSearcher.BuildQuery()` (lines 193-311) handles:
|
||||
- Special characters `*`, `-`, `_` removal/replacement
|
||||
- Quoted phrase search (`"exact match"`)
|
||||
- Query escaping via `QueryParserBase.Escape()`
|
||||
- Path escaping (replace `-` with `\-`, `,` with `\,`)
|
||||
|
||||
### User Start Node Filtering
|
||||
|
||||
`BackOfficeExamineSearcher.AppendPath()` (lines 378-456) ensures users only see content they have access to:
|
||||
1. Gets user's start nodes from `IBackOfficeSecurityAccessor`
|
||||
2. Filters search results to paths under start nodes
|
||||
3. Returns empty results if searchFrom outside user's access
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Index Names** (Constants.UmbracoIndexes):
|
||||
- `InternalIndexName` = "InternalIndex"
|
||||
- `ExternalIndexName` = "ExternalIndex"
|
||||
- `MembersIndexName` = "MembersIndex"
|
||||
- `DeliveryApiContentIndexName` = "DeliveryApiContentIndex"
|
||||
|
||||
**Key Interfaces**:
|
||||
- `IUmbracoIndex` - Marker for Umbraco indexes
|
||||
- `IUmbracoContentIndex` - Content index marker
|
||||
- `IBackOfficeExamineSearcher` - Backoffice search service
|
||||
- `IIndexDiagnostics` - Health/metadata for dashboard
|
||||
|
||||
**Dependencies**:
|
||||
- `Umbraco.Infrastructure` - Core services, index populators
|
||||
- `Umbraco.Core` - Field names, constants, interfaces
|
||||
- `Examine` (NuGet) - Lucene.NET abstraction
|
||||
362
src/Umbraco.PublishedCache.HybridCache/CLAUDE.md
Normal file
362
src/Umbraco.PublishedCache.HybridCache/CLAUDE.md
Normal file
@@ -0,0 +1,362 @@
|
||||
# Umbraco.PublishedCache.HybridCache
|
||||
|
||||
Published content caching layer for Umbraco CMS using Microsoft's HybridCache (in-memory + optional distributed cache). Provides high-performance content delivery with cache seeding, serialization, and notification-based invalidation.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Package ID**: Umbraco.Cms.PublishedCache.HybridCache
|
||||
**Namespace**: Umbraco.Cms.Infrastructure.HybridCache
|
||||
**Dependencies**: Umbraco.Core, Umbraco.Infrastructure, Microsoft.Extensions.Caching.Hybrid, MessagePack, K4os.Compression.LZ4
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project implements the published content cache using Microsoft's HybridCache pattern:
|
||||
|
||||
1. **Multi-Level Caching** - L1 (in-memory) + L2 (optional distributed cache like Redis)
|
||||
2. **Cache Seeding** - Pre-populates cache on startup with frequently accessed content
|
||||
3. **MessagePack Serialization** - Fast binary serialization with LZ4 compression
|
||||
4. **Notification-Based Invalidation** - Automatic cache updates on content changes
|
||||
5. **Draft/Published Separation** - Separate cache entries for draft and published content
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.PublishedCache.HybridCache/
|
||||
├── DependencyInjection/
|
||||
│ └── UmbracoBuilderExtensions.cs # AddUmbracoHybridCache() registration (90 lines)
|
||||
├── Extensions/
|
||||
│ └── HybridCacheExtensions.cs # ExistsAsync extension method
|
||||
├── Factories/
|
||||
│ ├── CacheNodeFactory.cs # Creates ContentCacheNode from IContent
|
||||
│ ├── ICacheNodeFactory.cs
|
||||
│ ├── IPublishedContentFactory.cs
|
||||
│ └── PublishedContentFactory.cs # Creates IPublishedContent from cache
|
||||
├── NotificationHandlers/
|
||||
│ ├── CacheRefreshingNotificationHandler.cs # Content/media change handler (120 lines)
|
||||
│ ├── HybridCacheStartupNotificationHandler.cs
|
||||
│ └── SeedingNotificationHandler.cs # Startup seeding (39 lines)
|
||||
├── Persistence/
|
||||
│ ├── ContentSourceDto.cs # DTO for database queries
|
||||
│ ├── DatabaseCacheRepository.cs # NPoco database access (891 lines)
|
||||
│ └── IDatabaseCacheRepository.cs
|
||||
├── SeedKeyProviders/
|
||||
│ ├── BreadthFirstKeyProvider.cs # Base breadth-first traversal
|
||||
│ ├── Document/
|
||||
│ │ ├── ContentTypeSeedKeyProvider.cs # Seeds by content type
|
||||
│ │ └── DocumentBreadthFirstKeyProvider.cs # Seeds top-level content (83 lines)
|
||||
│ └── Media/
|
||||
│ └── MediaBreadthFirstKeyProvider.cs
|
||||
├── Serialization/
|
||||
│ ├── ContentCacheDataModel.cs # Serializable cache model
|
||||
│ ├── HybridCacheSerializer.cs # MessagePack serializer (40 lines)
|
||||
│ ├── IContentCacheDataSerializer.cs # Nested data serializer interface
|
||||
│ ├── JsonContentNestedDataSerializer.cs # JSON serializer option
|
||||
│ ├── MsgPackContentNestedDataSerializer.cs # MessagePack serializer option
|
||||
│ └── LazyCompressedString.cs # Lazy LZ4 compression
|
||||
├── Services/
|
||||
│ ├── DocumentCacheService.cs # Content caching service (372 lines)
|
||||
│ ├── MediaCacheService.cs # Media caching service
|
||||
│ ├── MemberCacheService.cs # Member caching service
|
||||
│ └── DomainCacheService.cs # Domain caching service
|
||||
├── CacheManager.cs # ICacheManager facade (27 lines)
|
||||
├── ContentCacheNode.cs # Cache entry model (24 lines)
|
||||
├── ContentData.cs # Content data container
|
||||
├── ContentNode.cs # Content node representation
|
||||
├── DatabaseCacheRebuilder.cs # Full cache rebuild
|
||||
├── DocumentCache.cs # IPublishedContentCache impl (58 lines)
|
||||
├── MediaCache.cs # IPublishedMediaCache impl
|
||||
├── MemberCache.cs # IPublishedMemberCache impl
|
||||
├── DomainCache.cs # IDomainCache impl
|
||||
└── PublishedContent.cs # IPublishedContent impl
|
||||
```
|
||||
|
||||
### Cache Architecture
|
||||
|
||||
```
|
||||
Request → DocumentCache (IPublishedContentCache)
|
||||
↓
|
||||
DocumentCacheService
|
||||
↓
|
||||
┌─────────────────────────┐
|
||||
│ HybridCache │
|
||||
│ ┌───────┐ ┌────────┐ │
|
||||
│ │ L1 │ │ L2 │ │
|
||||
│ │Memory │→ │ Redis │ │
|
||||
│ └───────┘ └────────┘ │
|
||||
└─────────────────────────┘
|
||||
↓ (cache miss)
|
||||
DatabaseCacheRepository
|
||||
↓
|
||||
cmsContentNu table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### HybridCache Configuration (UmbracoBuilderExtensions.cs)
|
||||
|
||||
**Registration** (lines 30-36):
|
||||
```csharp
|
||||
builder.Services.AddHybridCache(options =>
|
||||
{
|
||||
// Default 100MB max payload (configurable)
|
||||
options.MaximumPayloadBytes = 1024 * 1024 * 100;
|
||||
}).AddSerializer<ContentCacheNode, HybridCacheSerializer>();
|
||||
```
|
||||
|
||||
**Key Services Registered** (lines 38-52):
|
||||
- `IPublishedContentCache` → `DocumentCache`
|
||||
- `IPublishedMediaCache` → `MediaCache`
|
||||
- `IPublishedMemberCache` → `MemberCache`
|
||||
- `IDomainCache` → `DomainCache`
|
||||
- `IDocumentCacheService` → `DocumentCacheService`
|
||||
- `IMediaCacheService` → `MediaCacheService`
|
||||
- `ICacheManager` → `CacheManager`
|
||||
|
||||
### DocumentCacheService (Services/DocumentCacheService.cs)
|
||||
|
||||
Core caching logic for content.
|
||||
|
||||
**GetNodeAsync** (lines 110-155):
|
||||
1. Check in-memory `_publishedContentCache` dictionary
|
||||
2. Call `HybridCache.GetOrCreateAsync()` with database fallback
|
||||
3. Verify published ancestor path exists
|
||||
4. Convert `ContentCacheNode` to `IPublishedContent`
|
||||
|
||||
**Cache Key Format** (line 326):
|
||||
```csharp
|
||||
private static string GetCacheKey(Guid key, bool preview)
|
||||
=> preview ? $"{key}+draft" : $"{key}";
|
||||
```
|
||||
|
||||
**Cache Entry Options** (lines 275-287):
|
||||
- Seed entries use `SeedCacheDuration` from `CacheSettings`
|
||||
- Regular entries use `LocalCacheDuration` and `RemoteCacheDuration`
|
||||
- Preview/draft entries always use regular (non-seed) durations
|
||||
|
||||
### ContentCacheNode (ContentCacheNode.cs)
|
||||
|
||||
Immutable cache entry model (marked `[ImmutableObject(true)]` for HybridCache performance):
|
||||
- `Id`, `Key`, `SortOrder`, `CreateDate`, `CreatorId`
|
||||
- `ContentTypeId`, `IsDraft`
|
||||
- `ContentData? Data` - Property values, culture data, URL segments
|
||||
|
||||
### HybridCacheSerializer (Serialization/HybridCacheSerializer.cs)
|
||||
|
||||
MessagePack serialization with LZ4 compression:
|
||||
```csharp
|
||||
_options = defaultOptions
|
||||
.WithCompression(MessagePackCompression.Lz4BlockArray)
|
||||
.WithSecurity(MessagePackSecurity.UntrustedData);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Cache Seeding
|
||||
|
||||
### Seed Key Providers
|
||||
|
||||
| Provider | Purpose | Config Setting |
|
||||
|----------|---------|----------------|
|
||||
| `ContentTypeSeedKeyProvider` | Seeds specific content types | N/A |
|
||||
| `DocumentBreadthFirstKeyProvider` | Seeds top-level content first | `DocumentBreadthFirstSeedCount` |
|
||||
| `MediaBreadthFirstKeyProvider` | Seeds top-level media first | `MediaBreadthFirstSeedCount` |
|
||||
|
||||
### DocumentBreadthFirstKeyProvider (lines 28-82)
|
||||
|
||||
Breadth-first traversal that:
|
||||
1. Gets root content keys
|
||||
2. Filters to only published content (any culture)
|
||||
3. Traverses children breadth-first until seed count reached
|
||||
4. Only includes content with published ancestor path
|
||||
|
||||
### SeedingNotificationHandler (lines 27-38)
|
||||
|
||||
Runs on `UmbracoApplicationStartingNotification`:
|
||||
- Skips during Install or Upgrade (when maintenance page shown)
|
||||
- Calls `DocumentCacheService.SeedAsync()` then `MediaCacheService.SeedAsync()`
|
||||
|
||||
### Seed Process (DocumentCacheService.SeedAsync lines 205-264)
|
||||
|
||||
1. Collect seed keys from all `IDocumentSeedKeyProvider` instances
|
||||
2. Process in batches of `DocumentSeedBatchSize`
|
||||
3. Check if key already in cache via `ExistsAsync()`
|
||||
4. Batch fetch uncached keys from database
|
||||
5. Set in cache with seed entry options
|
||||
|
||||
---
|
||||
|
||||
## 5. Cache Invalidation
|
||||
|
||||
### CacheRefreshingNotificationHandler (120 lines)
|
||||
|
||||
Handles 8 notification types:
|
||||
|
||||
| Notification | Action |
|
||||
|--------------|--------|
|
||||
| `ContentRefreshNotification` | `RefreshContentAsync()` |
|
||||
| `ContentDeletedNotification` | `DeleteItemAsync()` for each |
|
||||
| `MediaRefreshNotification` | `RefreshMediaAsync()` |
|
||||
| `MediaDeletedNotification` | `DeleteItemAsync()` for each |
|
||||
| `ContentTypeRefreshedNotification` | Clear type cache, `Rebuild()` |
|
||||
| `ContentTypeDeletedNotification` | Clear type cache |
|
||||
| `MediaTypeRefreshedNotification` | Clear type cache, `Rebuild()` |
|
||||
| `MediaTypeDeletedNotification` | Clear type cache |
|
||||
|
||||
### RefreshContentAsync (DocumentCacheService.cs lines 300-324)
|
||||
|
||||
1. Creates draft cache node from `IContent`
|
||||
2. Persists to `cmsContentNu` table
|
||||
3. If publishing: creates published node and persists
|
||||
4. If unpublishing: clears published cache entry
|
||||
|
||||
### Cache Tags (line 331)
|
||||
|
||||
All content uses tag `Constants.Cache.Tags.Content` for bulk invalidation:
|
||||
```csharp
|
||||
await _hybridCache.RemoveByTagAsync(Constants.Cache.Tags.Content, cancellationToken);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Database Persistence
|
||||
|
||||
### DatabaseCacheRepository (891 lines)
|
||||
|
||||
NPoco-based repository for `cmsContentNu` table.
|
||||
|
||||
**Key Methods**:
|
||||
- `GetContentSourceAsync(Guid key, bool preview)` - Single content fetch
|
||||
- `GetContentSourcesAsync(IEnumerable<Guid> keys)` - Batch fetch
|
||||
- `RefreshContentAsync(ContentCacheNode, PublishedState)` - Insert/update cache
|
||||
- `Rebuild()` - Full cache rebuild by content type
|
||||
|
||||
**SQL Templates** (lines 575-748):
|
||||
Uses `SqlContext.Templates` for cached SQL generation with optimized joins across:
|
||||
- `umbracoNode`, `umbracoContent`, `cmsDocument`, `umbracoContentVersion`
|
||||
- `cmsDocumentVersion`, `cmsContentNu`
|
||||
|
||||
### Serialization Options (NuCacheSettings.NuCacheSerializerType)
|
||||
|
||||
| Type | Serializer |
|
||||
|------|------------|
|
||||
| `JSON` | JsonContentNestedDataSerializer |
|
||||
| `MessagePack` | MsgPackContentNestedDataSerializer |
|
||||
|
||||
---
|
||||
|
||||
## 7. Project-Specific Notes
|
||||
|
||||
### Experimental API Warning
|
||||
|
||||
HybridCache API is experimental (suppressed with `#pragma warning disable EXTEXP0018`).
|
||||
|
||||
### Draft vs Published Caching
|
||||
|
||||
- **Draft cache key**: `"{key}+draft"`
|
||||
- **Published cache key**: `"{key}"`
|
||||
- Each stored separately to avoid cross-contamination
|
||||
- Draft entries never use seed durations
|
||||
|
||||
### Published Ancestor Path Check (DocumentCacheService.cs lines 131-135)
|
||||
|
||||
Before returning cached content, verifies ancestor path is published via `_publishStatusQueryService.HasPublishedAncestorPath()`. Returns null if parent unpublished.
|
||||
|
||||
### In-Memory Content Cache (DocumentCacheService.cs line 39)
|
||||
|
||||
Secondary `ConcurrentDictionary<string, IPublishedContent>` caches converted objects, since `ContentCacheNode` to `IPublishedContent` conversion is expensive.
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **Warnings Disabled** (`.csproj:10-12`): `TreatWarningsAsErrors=false`
|
||||
2. **Property Value Null Handling** (`PropertyData.cs:25, 37`): Cannot be null, needs fallback decision
|
||||
3. **Routing Cache TODO** (`ContentCacheDataModel.cs:26`): Remove when routing cache implemented
|
||||
4. **SQL Template Limitations** (`DatabaseCacheRepository.cs:612, 688, 737`): Can't use templates with certain joins
|
||||
5. **Serializer Refactor** (`DatabaseCacheRepository.cs:481`): Standardize to ContentTypeId only
|
||||
6. **String Interning** (`MsgPackContentNestedDataSerializer.cs:29`): Intern alias strings during deserialization
|
||||
7. **V18 Interface Cleanup** (`IDatabaseCacheRepository.cs:24, 48`): Remove default implementations
|
||||
|
||||
### Cache Settings (CacheSettings)
|
||||
|
||||
Key configuration options:
|
||||
- `DocumentBreadthFirstSeedCount` - Number of documents to seed
|
||||
- `MediaBreadthFirstSeedCount` - Number of media items to seed
|
||||
- `DocumentSeedBatchSize` - Batch size for seeding
|
||||
- `Entry.Document.SeedCacheDuration` - Duration for seeded entries
|
||||
- `Entry.Document.LocalCacheDuration` - L1 cache duration
|
||||
- `Entry.Document.RemoteCacheDuration` - L2 cache duration
|
||||
|
||||
### InternalsVisibleTo
|
||||
|
||||
Test projects with access to internal types:
|
||||
- `Umbraco.Tests`
|
||||
- `Umbraco.Tests.Common`
|
||||
- `Umbraco.Tests.UnitTests`
|
||||
- `Umbraco.Tests.Integration`
|
||||
- `DynamicProxyGenAssembly2` (for mocking)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Cache Flow
|
||||
|
||||
```
|
||||
GetByIdAsync(id) → GetByKeyAsync(key) → GetNodeAsync(key, preview)
|
||||
→ HybridCache.GetOrCreateAsync()
|
||||
→ DatabaseCacheRepository.GetContentSourceAsync() [on miss]
|
||||
→ PublishedContentFactory.ToIPublishedContent()
|
||||
→ CreateModel(IPublishedModelFactory)
|
||||
```
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
| Interface | Implementation | Purpose |
|
||||
|-----------|----------------|---------|
|
||||
| `IPublishedContentCache` | DocumentCache | Content retrieval |
|
||||
| `IPublishedMediaCache` | MediaCache | Media retrieval |
|
||||
| `IDocumentCacheService` | DocumentCacheService | Cache operations |
|
||||
| `IDatabaseCacheRepository` | DatabaseCacheRepository | Database access |
|
||||
| `ICacheNodeFactory` | CacheNodeFactory | Cache node creation |
|
||||
|
||||
### Configuration Keys
|
||||
|
||||
```json
|
||||
{
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"Cache": {
|
||||
"DocumentBreadthFirstSeedCount": 100,
|
||||
"MediaBreadthFirstSeedCount": 50,
|
||||
"DocumentSeedBatchSize": 100,
|
||||
"Entry": {
|
||||
"Document": {
|
||||
"SeedCacheDuration": "04:00:00",
|
||||
"LocalCacheDuration": "00:05:00",
|
||||
"RemoteCacheDuration": "01:00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Related Projects
|
||||
|
||||
| Project | Relationship |
|
||||
|---------|--------------|
|
||||
| `Umbraco.Core` | Interfaces (IPublishedContentCache, etc.) |
|
||||
| `Umbraco.Infrastructure` | Services, repositories, NPoco |
|
||||
| Microsoft.Extensions.Caching.Hybrid | HybridCache implementation |
|
||||
380
src/Umbraco.Web.Common/CLAUDE.md
Normal file
380
src/Umbraco.Web.Common/CLAUDE.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# Umbraco.Web.Common
|
||||
|
||||
Shared ASP.NET Core web functionality for Umbraco CMS. Provides controllers, middleware, application builder extensions, security/identity, localization, and the UmbracoContext request pipeline.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Package ID**: Umbraco.Cms.Web.Common
|
||||
**Namespace**: Umbraco.Cms.Web.Common
|
||||
**Dependencies**: Umbraco.Examine.Lucene, Umbraco.PublishedCache.HybridCache, MiniProfiler, Serilog, Asp.Versioning
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project provides the web layer foundation for Umbraco CMS:
|
||||
|
||||
1. **DI Registration** - `AddUmbraco()` entry point for service registration
|
||||
2. **Application Pipeline** - `UmbracoApplicationBuilder` for middleware ordering
|
||||
3. **Controllers** - Base classes for frontend and backoffice controllers
|
||||
4. **UmbracoContext** - Request-scoped context with published content access
|
||||
5. **Security** - Member sign-in, identity management, authentication middleware
|
||||
6. **Middleware** - Boot failure handling, preview authentication, request routing
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Web.Common/
|
||||
├── ApplicationBuilder/
|
||||
│ ├── IUmbracoApplicationBuilder.cs # Builder interface
|
||||
│ ├── IUmbracoPipelineFilter.cs # Pipeline customization hooks
|
||||
│ └── UmbracoApplicationBuilder.cs # Middleware orchestration (170 lines)
|
||||
├── AspNetCore/
|
||||
│ ├── AspNetCoreIpResolver.cs # IP address resolution
|
||||
│ ├── AspNetCorePasswordHasher.cs # Identity password hashing
|
||||
│ ├── AspNetCoreRequestAccessor.cs # Request access abstraction
|
||||
│ └── OptionsMonitorAdapter.cs # Config monitoring wrapper
|
||||
├── Controllers/
|
||||
│ ├── IRenderController.cs # Frontend controller marker
|
||||
│ ├── IVirtualPageController.cs # Virtual page support
|
||||
│ ├── PluginController.cs # Plugin controller base (104 lines)
|
||||
│ ├── UmbracoApiController.cs # Legacy API controller (obsolete)
|
||||
│ ├── UmbracoAuthorizedController.cs # Backoffice authorized base
|
||||
│ └── UmbracoController.cs # Base MVC controller (13 lines)
|
||||
├── DependencyInjection/
|
||||
│ └── UmbracoBuilderExtensions.cs # AddUmbraco(), AddUmbracoCore() (338 lines)
|
||||
├── Extensions/
|
||||
│ ├── BlockGridTemplateExtensions.cs # Block grid Razor helpers
|
||||
│ ├── BlockListTemplateExtensions.cs # Block list Razor helpers
|
||||
│ ├── HttpRequestExtensions.cs # Request utility methods
|
||||
│ ├── ImageCropperTemplateExtensions.cs # Image crop Razor helpers
|
||||
│ ├── LinkGeneratorExtensions.cs # URL generation helpers
|
||||
│ ├── WebApplicationExtensions.cs # BootUmbracoAsync() (32 lines)
|
||||
│ └── [30+ extension classes]
|
||||
├── Filters/
|
||||
│ ├── BackOfficeCultureFilter.cs # Culture detection
|
||||
│ ├── DisableBrowserCacheAttribute.cs # Cache control headers
|
||||
│ ├── UmbracoMemberAuthorizeAttribute.cs # Member authorization
|
||||
│ └── ValidateUmbracoFormRouteStringAttribute.cs
|
||||
├── Localization/
|
||||
│ ├── UmbracoBackOfficeIdentityCultureProvider.cs
|
||||
│ └── UmbracoPublishedContentCultureProvider.cs
|
||||
├── Middleware/
|
||||
│ ├── BootFailedMiddleware.cs # Startup failure handling (81 lines)
|
||||
│ └── PreviewAuthenticationMiddleware.cs # Preview mode auth (84 lines)
|
||||
├── Routing/
|
||||
│ ├── IAreaRoutes.cs # Area routing interface
|
||||
│ ├── IRoutableDocumentFilter.cs # Content routing filter
|
||||
│ ├── UmbracoRouteValues.cs # Route data container (60 lines)
|
||||
│ └── UmbracoVirtualPageRoute.cs # Virtual page routing
|
||||
├── Security/
|
||||
│ ├── MemberSignInManager.cs # Member sign-in (350 lines)
|
||||
│ ├── UmbracoSignInManager.cs # Base sign-in manager
|
||||
│ ├── IMemberSignInManager.cs # Sign-in interface
|
||||
│ └── EncryptionHelper.cs # Surface controller encryption
|
||||
├── Templates/
|
||||
│ └── TemplateRenderer.cs # Razor template rendering
|
||||
├── UmbracoContext/
|
||||
│ ├── UmbracoContext.cs # Request context (173 lines)
|
||||
│ └── UmbracoContextFactory.cs # Context creation (75 lines)
|
||||
├── UmbracoHelper.cs # Template helper (427 lines)
|
||||
└── Umbraco.Web.Common.csproj # Project configuration (48 lines)
|
||||
```
|
||||
|
||||
### Request Pipeline Flow
|
||||
|
||||
```
|
||||
WebApplication.BootUmbracoAsync()
|
||||
↓
|
||||
UseUmbraco().WithMiddleware()
|
||||
↓
|
||||
┌──────────────────────────────┐
|
||||
│ UmbracoApplicationBuilder │
|
||||
│ ├─ RunPrePipeline() │
|
||||
│ ├─ BootFailedMiddleware │
|
||||
│ ├─ UseUmbracoCore() │
|
||||
│ ├─ UseStaticFiles() │
|
||||
│ ├─ UseRouting() │
|
||||
│ ├─ UseAuthentication() │
|
||||
│ ├─ UseAuthorization() │
|
||||
│ ├─ UseRequestLocalization() │
|
||||
│ └─ RunPostPipeline() │
|
||||
└──────────────────────────────┘
|
||||
↓
|
||||
WithEndpoints() → Content routing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### DI Entry Point (DependencyInjection/UmbracoBuilderExtensions.cs)
|
||||
|
||||
**AddUmbraco()** (lines 77-123) - Creates `IUmbracoBuilder` and registers core services:
|
||||
```csharp
|
||||
public static IUmbracoBuilder AddUmbraco(
|
||||
this IServiceCollection services,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
IConfiguration config)
|
||||
```
|
||||
|
||||
Key registrations:
|
||||
- Sets `DataDirectory` for database paths (line 97-99)
|
||||
- Creates `HttpContextAccessor` singleton (line 104-105)
|
||||
- Initializes `AppCaches` with request-scoped cache
|
||||
- Creates `TypeLoader` for assembly scanning
|
||||
- Returns `UmbracoBuilder` for fluent configuration
|
||||
|
||||
**AddUmbracoCore()** (lines 131-173) - Registers ASP.NET Core-specific services:
|
||||
- `IHostingEnvironment` → `AspNetCoreHostingEnvironment`
|
||||
- `IBackOfficeInfo` → `AspNetCoreBackOfficeInfo`
|
||||
- `IDbProviderFactoryCreator` with SQL syntax providers
|
||||
- Telemetry providers and application lifetime
|
||||
|
||||
**AddWebComponents()** (lines 222-288) - Registers web-specific services:
|
||||
- Session with `UMB_SESSION` cookie (line 229)
|
||||
- API versioning configuration
|
||||
- Password hasher, cookie manager, IP resolver
|
||||
- `UmbracoHelper`, `UmbracoContextFactory`
|
||||
- All middleware as singletons
|
||||
|
||||
### Application Builder (ApplicationBuilder/UmbracoApplicationBuilder.cs)
|
||||
|
||||
Orchestrates middleware registration in correct order.
|
||||
|
||||
**WithMiddleware()** (lines 49-65):
|
||||
1. `RunPrePipeline()` - Custom filters before Umbraco middleware
|
||||
2. `RegisterDefaultRequiredMiddleware()` - Core middleware stack
|
||||
3. `RunPostPipeline()` - Custom filters after
|
||||
4. User-provided middleware callback
|
||||
|
||||
**RegisterDefaultRequiredMiddleware()** (lines 70-104):
|
||||
```csharp
|
||||
UseUmbracoCoreMiddleware();
|
||||
AppBuilder.UseUmbracoMediaFileProvider();
|
||||
AppBuilder.UseUmbracoBackOfficeRewrites();
|
||||
AppBuilder.UseStaticFiles();
|
||||
AppBuilder.UseUmbracoPluginsStaticFiles();
|
||||
AppBuilder.UseRouting();
|
||||
AppBuilder.UseAuthentication();
|
||||
AppBuilder.UseAuthorization();
|
||||
AppBuilder.UseAntiforgery();
|
||||
AppBuilder.UseRequestLocalization();
|
||||
AppBuilder.UseSession();
|
||||
```
|
||||
|
||||
**Pipeline Filter Hooks** (IUmbracoPipelineFilter):
|
||||
- `OnPrePipeline()` - Before any Umbraco middleware
|
||||
- `OnPreRouting()` - Before UseRouting()
|
||||
- `OnPostRouting()` - After UseRouting()
|
||||
- `OnPostPipeline()` - After all Umbraco middleware
|
||||
- `OnEndpoints()` - Before endpoint mapping
|
||||
|
||||
### UmbracoContext (UmbracoContext/UmbracoContext.cs)
|
||||
|
||||
Request-scoped container for Umbraco request state.
|
||||
|
||||
**Key Properties**:
|
||||
- `Content` → `IPublishedContentCache` (line 102)
|
||||
- `Media` → `IPublishedMediaCache` (line 105)
|
||||
- `Domains` → `IDomainCache` (line 108)
|
||||
- `PublishedRequest` → Routed content for request (line 111)
|
||||
- `InPreviewMode` → Preview cookie detected (lines 140-152)
|
||||
- `OriginalRequestUrl` / `CleanedUmbracoUrl` - Request URLs
|
||||
|
||||
**Preview Detection** (lines 154-166):
|
||||
```csharp
|
||||
private void DetectPreviewMode()
|
||||
{
|
||||
// Check preview cookie, verify not backoffice request
|
||||
var previewToken = _cookieManager.GetCookieValue(Constants.Web.PreviewCookieName);
|
||||
_previewing = _previewToken.IsNullOrWhiteSpace() == false;
|
||||
}
|
||||
```
|
||||
|
||||
### UmbracoHelper (UmbracoHelper.cs)
|
||||
|
||||
Template helper for Razor views (scoped lifetime).
|
||||
|
||||
**Content Retrieval** (lines 192-317):
|
||||
- `Content(id)` - Get by int, Guid, string, or Udi
|
||||
- `Content(ids)` - Batch retrieval
|
||||
- `ContentAtRoot()` - Root content items
|
||||
|
||||
**Media Retrieval** (lines 320-424):
|
||||
- Same pattern as content
|
||||
|
||||
**Dictionary** (lines 102-189):
|
||||
- `GetDictionaryValue(key)` - Localized string lookup
|
||||
- `GetDictionaryValueOrDefault(key, defaultValue)`
|
||||
- `CultureDictionary` - Current culture dictionary
|
||||
|
||||
### Controller Base Classes
|
||||
|
||||
| Controller | Purpose | Features |
|
||||
|------------|---------|----------|
|
||||
| `UmbracoController` | Base MVC controller | Simple base, debug InstanceId |
|
||||
| `UmbracoAuthorizedController` | Backoffice controllers | `[Authorize(BackOfficeAccess)]`, `[DisableBrowserCache]` |
|
||||
| `PluginController` | Plugin/package controllers | UmbracoContext, Services, AppCaches, ProfilingLogger |
|
||||
| `UmbracoApiController` | Legacy API controller | **Obsolete** - Use ASP.NET Core ApiController |
|
||||
| `IRenderController` | Frontend rendering marker | Route hijacking support |
|
||||
|
||||
**PluginController** (lines 18-104):
|
||||
- Provides `UmbracoContext`, `DatabaseFactory`, `Services`, `AppCaches`
|
||||
- Static metadata caching with `ConcurrentDictionary<Type, PluginControllerMetadata>`
|
||||
- Auto-discovers `[PluginController]` and `[IsBackOffice]` attributes
|
||||
|
||||
### Member Sign-In (Security/MemberSignInManager.cs)
|
||||
|
||||
ASP.NET Core Identity sign-in manager for members.
|
||||
|
||||
**Key Features**:
|
||||
- External login with auto-linking (lines 112-142)
|
||||
- Two-factor authentication support (lines 162-172)
|
||||
- Auto-link and create member accounts (lines 180-267)
|
||||
|
||||
**Auto-Link Flow** (lines 180-267):
|
||||
1. Check if auto-link enabled and email available
|
||||
2. Find or create member by email
|
||||
3. Call `OnAutoLinking` callback for customization
|
||||
4. Add external login link
|
||||
5. Sign in or request 2FA
|
||||
|
||||
**Result Types** (lines 320-348):
|
||||
- `ExternalLoginSignInResult.NotAllowed` - Login refused by callback
|
||||
- `AutoLinkSignInResult.FailedNoEmail` - No email from provider
|
||||
- `AutoLinkSignInResult.FailedCreatingUser` - User creation failed
|
||||
- `AutoLinkSignInResult.FailedLinkingUser` - Link creation failed
|
||||
|
||||
### Middleware
|
||||
|
||||
**BootFailedMiddleware** (lines 17-81):
|
||||
- Intercepts requests when `RuntimeLevel == BootFailed`
|
||||
- Debug mode: Rethrows exception for stack trace
|
||||
- Production: Shows `BootFailed.html` error page
|
||||
|
||||
**PreviewAuthenticationMiddleware** (lines 22-84):
|
||||
- Adds backoffice identity to principal for preview requests
|
||||
- Skips client-side requests and backoffice paths
|
||||
- Uses `IPreviewService.TryGetPreviewClaimsIdentityAsync()`
|
||||
|
||||
---
|
||||
|
||||
## 4. Routing
|
||||
|
||||
### UmbracoRouteValues (Routing/UmbracoRouteValues.cs)
|
||||
|
||||
Container for routed request data:
|
||||
- `PublishedRequest` - The resolved content request
|
||||
- `ControllerActionDescriptor` - MVC routing info
|
||||
- `TemplateName` - Resolved template name
|
||||
- `DefaultActionName` = "Index"
|
||||
|
||||
### Route Hijacking
|
||||
|
||||
Implement `IRenderController` to handle specific content types:
|
||||
```csharp
|
||||
public class ProductController : Controller, IRenderController
|
||||
{
|
||||
public IActionResult Index() => View();
|
||||
}
|
||||
```
|
||||
|
||||
### Virtual Pages
|
||||
|
||||
Implement `IVirtualPageController` for URL-to-content mapping without physical content nodes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Project-Specific Notes
|
||||
|
||||
### Warning Suppressions (csproj lines 10-22)
|
||||
|
||||
Multiple analyzer warnings suppressed:
|
||||
- SA1117, SA1401, SA1134 - StyleCop formatting
|
||||
- ASP0019 - Header dictionary usage
|
||||
- CS0618/SYSLIB0051 - Obsolete references
|
||||
- IDE0040/SA1400 - Access modifiers
|
||||
- SA1649 - File name matching
|
||||
|
||||
### InternalsVisibleTo
|
||||
|
||||
```xml
|
||||
<InternalsVisibleTo>Umbraco.Tests.UnitTests</InternalsVisibleTo>
|
||||
```
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **MVC Global State** (UmbracoBuilderExtensions.cs:210-211): `AddControllersWithViews` modifies global app, order matters
|
||||
2. **OptionsMonitor Hack** (AspNetCore/OptionsMonitorAdapter.cs:6): Temporary workaround for TypeLoader during ConfigureServices
|
||||
3. **DisposeResources TODO** (UmbracoContext.cs:168-171): Empty dispose method marked for removal
|
||||
4. **Pipeline Default Implementations** (IUmbracoPipelineFilter.cs:36,45): Default methods to remove in Umbraco 13
|
||||
5. **SignIn Manager Sharing** (MemberSignInManager.cs:319,325): Could share code with backoffice sign-in
|
||||
|
||||
### Session Configuration
|
||||
|
||||
Default session cookie (lines 227-231):
|
||||
```csharp
|
||||
options.Cookie.Name = "UMB_SESSION";
|
||||
options.Cookie.HttpOnly = true;
|
||||
```
|
||||
|
||||
Can be overridden by calling `AddSession` after `AddWebComponents`.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Startup Flow
|
||||
|
||||
```csharp
|
||||
// Program.cs
|
||||
builder.Services.AddUmbraco(webHostEnvironment, config)
|
||||
.AddUmbracoCore()
|
||||
.AddMvcAndRazor()
|
||||
.AddWebComponents()
|
||||
.AddUmbracoProfiler()
|
||||
.Build();
|
||||
|
||||
await app.BootUmbracoAsync();
|
||||
app.UseUmbraco()
|
||||
.WithMiddleware(u => u.UseBackOffice())
|
||||
.WithEndpoints(u => u.UseBackOfficeEndpoints());
|
||||
```
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
| Interface | Implementation | Purpose |
|
||||
|-----------|----------------|---------|
|
||||
| `IUmbracoContext` | UmbracoContext | Request state |
|
||||
| `IUmbracoContextFactory` | UmbracoContextFactory | Context creation |
|
||||
| `IUmbracoContextAccessor` | (in Core) | Context access |
|
||||
| `IMemberSignInManager` | MemberSignInManager | Member auth |
|
||||
| `IUmbracoApplicationBuilder` | UmbracoApplicationBuilder | Pipeline config |
|
||||
|
||||
### Extension Method Namespaces
|
||||
|
||||
Most extensions are in `Umbraco.Extensions` namespace:
|
||||
- `HttpRequestExtensions` - Request helpers
|
||||
- `LinkGeneratorExtensions` - URL generation
|
||||
- `BlockGridTemplateExtensions` - Block grid rendering
|
||||
- `ImageCropperTemplateExtensions` - Image cropping
|
||||
|
||||
### Related Projects
|
||||
|
||||
| Project | Relationship |
|
||||
|---------|--------------|
|
||||
| `Umbraco.Core` | Interface contracts |
|
||||
| `Umbraco.Infrastructure` | Service implementations |
|
||||
| `Umbraco.Examine.Lucene` | Search dependency |
|
||||
| `Umbraco.PublishedCache.HybridCache` | Caching dependency |
|
||||
| `Umbraco.Web.UI` | Main web application (references this) |
|
||||
| `Umbraco.Cms.Api.Common` | API layer (references this) |
|
||||
266
src/Umbraco.Web.UI.Login/CLAUDE.md
Normal file
266
src/Umbraco.Web.UI.Login/CLAUDE.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# Umbraco.Web.UI.Login
|
||||
|
||||
TypeScript/Lit login SPA for Umbraco CMS backoffice authentication. Provides the `<umb-auth>` web component used in the login page, supporting local login, MFA, password reset, and user invitation flows.
|
||||
|
||||
**Project Type**: TypeScript Library (Vite)
|
||||
**Runtime**: Node.js >= 22, npm >= 10.9
|
||||
**Output**: ES Module library → `../Umbraco.Cms.StaticAssets/wwwroot/umbraco/login/`
|
||||
**Dependencies**: @umbraco-cms/backoffice, Lit, Vite
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project builds the login single-page application:
|
||||
|
||||
1. **Web Component** - `<umb-auth>` custom element for authentication
|
||||
2. **Authentication Flows** - Login, MFA, password reset, user invitation
|
||||
3. **Localization** - Multi-language support (en, de, da, nb, nl, sv)
|
||||
4. **API Client** - Generated from OpenAPI specification
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Web.UI.Login/
|
||||
├── src/
|
||||
│ ├── api/ # Generated API client
|
||||
│ │ ├── client/ # HTTP client utilities
|
||||
│ │ ├── core/ # Serializers, types
|
||||
│ │ ├── sdk.gen.ts # Generated SDK
|
||||
│ │ └── types.gen.ts # Generated types
|
||||
│ ├── components/
|
||||
│ │ ├── layouts/ # Layout components
|
||||
│ │ │ ├── auth-layout.element.ts
|
||||
│ │ │ ├── confirmation-layout.element.ts
|
||||
│ │ │ ├── error-layout.element.ts
|
||||
│ │ │ └── new-password-layout.element.ts
|
||||
│ │ ├── pages/ # Page components
|
||||
│ │ │ ├── login.page.element.ts
|
||||
│ │ │ ├── mfa.page.element.ts
|
||||
│ │ │ ├── new-password.page.element.ts
|
||||
│ │ │ ├── reset-password.page.element.ts
|
||||
│ │ │ └── invite.page.element.ts
|
||||
│ │ └── back-to-login-button.element.ts
|
||||
│ ├── contexts/
|
||||
│ │ ├── auth.context.ts # Authentication state (86 lines)
|
||||
│ │ └── auth.repository.ts # API calls (231 lines)
|
||||
│ ├── controllers/
|
||||
│ │ └── slim-backoffice-initializer.ts # Minimal backoffice bootstrap
|
||||
│ ├── localization/
|
||||
│ │ ├── lang/ # Language files (da, de, en, en-us, nb, nl, sv)
|
||||
│ │ └── manifests.ts # Localization registration
|
||||
│ ├── mocks/ # MSW mock handlers for development
|
||||
│ │ ├── handlers/
|
||||
│ │ │ ├── login.handlers.ts
|
||||
│ │ │ └── backoffice.handlers.ts
|
||||
│ │ └── data/login.data.ts
|
||||
│ ├── utils/
|
||||
│ │ ├── is-problem-details.function.ts
|
||||
│ │ └── load-custom-view.function.ts
|
||||
│ ├── auth.element.ts # Main <umb-auth> component (404 lines)
|
||||
│ ├── types.ts # Type definitions
|
||||
│ ├── manifests.ts # Extension manifests
|
||||
│ └── umbraco-package.ts # Package definition
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── index.html # Development entry point
|
||||
├── package.json # npm configuration (29 lines)
|
||||
├── tsconfig.json # TypeScript config (25 lines)
|
||||
├── vite.config.ts # Vite build config (20 lines)
|
||||
├── .nvmrc # Node version (22)
|
||||
└── .prettierrc.json # Prettier config
|
||||
```
|
||||
|
||||
### Build Output
|
||||
|
||||
Built assets are output to `Umbraco.Cms.StaticAssets`:
|
||||
```
|
||||
../Umbraco.Cms.StaticAssets/wwwroot/umbraco/login/
|
||||
├── login.js # Main ES module
|
||||
└── login.js.map # Source map
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
cd src/Umbraco.Web.UI.Login
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run development server with hot reload
|
||||
npm run dev
|
||||
|
||||
# Build for production (outputs to StaticAssets)
|
||||
npm run build
|
||||
|
||||
# Watch mode build
|
||||
npm run watch
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### API Generation
|
||||
|
||||
```bash
|
||||
# Regenerate API client from OpenAPI spec
|
||||
npm run generate:server-api
|
||||
```
|
||||
|
||||
Uses `@hey-api/openapi-ts` to generate TypeScript client from the Management API OpenAPI specification.
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### UmbAuthElement (src/auth.element.ts)
|
||||
|
||||
Main `<umb-auth>` web component (404 lines).
|
||||
|
||||
**Attributes** (lines 172-204):
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `disable-local-login` | boolean | Disables local login, external only |
|
||||
| `background-image` | string | Login page background URL |
|
||||
| `logo-image` | string | Logo URL |
|
||||
| `logo-image-alternative` | string | Alternative logo (dark mode) |
|
||||
| `username-is-email` | boolean | Use email as username |
|
||||
| `allow-password-reset` | boolean | Show password reset link |
|
||||
| `allow-user-invite` | boolean | Enable user invitation flow |
|
||||
| `return-url` | string | Redirect URL after login |
|
||||
|
||||
**Authentication Flows** (lines 379-396):
|
||||
- Default: Login page with username/password
|
||||
- `flow=mfa`: Multi-factor authentication
|
||||
- `flow=reset`: Request password reset
|
||||
- `flow=reset-password`: Set new password
|
||||
- `flow=invite-user`: User invitation acceptance
|
||||
|
||||
**Form Workaround** (lines 274-334):
|
||||
Creates login form in light DOM (not shadow DOM) to support Chrome autofill/autocomplete, which doesn't work properly with shadow DOM inputs.
|
||||
|
||||
### UmbAuthContext (src/contexts/auth.context.ts)
|
||||
|
||||
Authentication state and API methods (86 lines):
|
||||
- `login(data)` - Authenticate user
|
||||
- `resetPassword(username)` - Request password reset
|
||||
- `validatePasswordResetCode(userId, code)` - Validate reset code
|
||||
- `newPassword(password, code, userId)` - Set new password
|
||||
- `validateInviteCode(token, userId)` - Validate invitation
|
||||
- `newInvitedUserPassword(...)` - Set invited user password
|
||||
- `validateMfaCode(code, provider)` - MFA validation
|
||||
|
||||
**Return Path Security** (lines 37-54): Validates return URL to prevent open redirect attacks - only allows same-origin URLs.
|
||||
|
||||
### Localization
|
||||
|
||||
Supported languages: English (en, en-us), Danish (da), German (de), Norwegian Bokmål (nb), Dutch (nl), Swedish (sv).
|
||||
|
||||
---
|
||||
|
||||
## 4. Development with Mocks
|
||||
|
||||
### MSW (Mock Service Worker)
|
||||
|
||||
Development uses MSW for API mocking. Run `npm run dev` to start with mocks enabled.
|
||||
|
||||
**Mock Files**:
|
||||
- `handlers/login.handlers.ts` - Login, MFA, password reset
|
||||
- `handlers/backoffice.handlers.ts` - Backoffice API
|
||||
- `data/login.data.ts` - Test user data
|
||||
- `customViews/` - Example custom login views
|
||||
|
||||
---
|
||||
|
||||
## 5. Project-Specific Notes
|
||||
|
||||
### Shadow DOM Limitation
|
||||
|
||||
Chrome doesn't support autofill in shadow DOM inputs. The login form is created in light DOM via `#initializeForm()` (lines 274-334) and inserted with `insertAdjacentElement()`. See Chromium intent-to-ship discussion linked in code.
|
||||
|
||||
### Slim Backoffice Controller
|
||||
|
||||
`UmbSlimBackofficeController` provides minimal backoffice utilities (extension registry, localization, context API) without loading the full app.
|
||||
|
||||
### Localization Wait Pattern (lines 242-265)
|
||||
|
||||
Form waits for localization availability before rendering. Retries 40 times with 50ms interval (2 second max wait).
|
||||
|
||||
### External Dependencies
|
||||
|
||||
- `@umbraco-cms/backoffice` ^16.2.0 - Umbraco UI components, Lit element base
|
||||
- `vite` ^7.2.0 - Build tooling
|
||||
- `typescript` ^5.9.3 - Type checking
|
||||
- `msw` ^2.11.3 - API mocking
|
||||
- `@hey-api/openapi-ts` ^0.85.0 - API client generation
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **UUI Color Variable** - Multiple files use `--uui-color-text-alt` with TODO to change when UUI adds muted text variable:
|
||||
- `back-to-login-button.element.ts:35`
|
||||
- `confirmation-layout.element.ts:41`
|
||||
- `error-layout.element.ts:42`
|
||||
- `login.page.element.ts:203,226`
|
||||
- `new-password-layout.element.ts:221`
|
||||
- `reset-password.page.element.ts:110`
|
||||
|
||||
2. **API Client Error Types** (`api/client/client.gen.ts:207`): Error handling and types need improvement
|
||||
|
||||
### TypeScript Configuration
|
||||
|
||||
Key settings in `tsconfig.json`:
|
||||
- `target`: ES2022
|
||||
- `experimentalDecorators`: true (for Lit decorators)
|
||||
- `useDefineForClassFields`: false (Lit compatibility)
|
||||
- `moduleResolution`: bundler (Vite)
|
||||
- `strict`: true
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
npm run dev # Start dev server
|
||||
npm run build # Build to StaticAssets
|
||||
npm run watch # Watch mode
|
||||
|
||||
# API
|
||||
npm run generate:server-api # Regenerate API client
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/auth.element.ts` | Main `<umb-auth>` component |
|
||||
| `src/contexts/auth.context.ts` | Auth state management |
|
||||
| `src/contexts/auth.repository.ts` | API calls |
|
||||
| `vite.config.ts` | Build configuration |
|
||||
| `package.json` | Dependencies and scripts |
|
||||
|
||||
### Build Output
|
||||
|
||||
Built files go to:
|
||||
```
|
||||
../Umbraco.Cms.StaticAssets/wwwroot/umbraco/login/
|
||||
```
|
||||
|
||||
### Related Projects
|
||||
|
||||
| Project | Relationship |
|
||||
|---------|--------------|
|
||||
| `Umbraco.Cms.StaticAssets` | Receives built output |
|
||||
| `Umbraco.Web.UI.Client` | Backoffice SPA (similar architecture) |
|
||||
| `@umbraco-cms/backoffice` | NPM dependency for UI components |
|
||||
320
src/Umbraco.Web.UI/CLAUDE.md
Normal file
320
src/Umbraco.Web.UI/CLAUDE.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# Umbraco.Web.UI
|
||||
|
||||
Main ASP.NET Core web application for running Umbraco CMS. This is the development test site that references all Umbraco packages and provides a minimal startup configuration for testing and development.
|
||||
|
||||
**Project Type**: ASP.NET Core Web Application
|
||||
**SDK**: Microsoft.NET.Sdk.Web
|
||||
**Target Framework**: net10.0
|
||||
**IsPackable**: false (not published as NuGet package)
|
||||
**Namespace**: Umbraco.Cms.Web.UI
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This is the **runnable Umbraco instance** used for development and testing. It:
|
||||
|
||||
1. **References All Umbraco Packages** - Via the `Umbraco.Cms` meta-package
|
||||
2. **Provides Minimal Startup** - Simple `Program.cs` with builder pattern
|
||||
3. **Includes Development Tools** - Backoffice development mode support
|
||||
4. **Demonstrates Patterns** - Example composers, views, and block templates
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Web.UI/
|
||||
├── Composers/
|
||||
│ ├── ControllersAsServicesComposer.cs # DI validation composer (66 lines)
|
||||
│ └── UmbracoAppAuthenticatorComposer.cs # 2FA example (commented out)
|
||||
├── Properties/
|
||||
│ └── launchSettings.json # Debug profiles (29 lines)
|
||||
├── Views/
|
||||
│ ├── _ViewImports.cshtml # Global view imports
|
||||
│ ├── page.cshtml # Generic page template
|
||||
│ ├── BlockPage.cshtml # Block page template
|
||||
│ ├── BlockTester.cshtml # Block testing template
|
||||
│ └── Partials/
|
||||
│ ├── blockgrid/ # Block grid partials
|
||||
│ │ ├── area.cshtml
|
||||
│ │ ├── areas.cshtml
|
||||
│ │ ├── items.cshtml
|
||||
│ │ └── default.cshtml
|
||||
│ ├── blocklist/ # Block list partials
|
||||
│ │ ├── Components/textBlock.cshtml
|
||||
│ │ └── default.cshtml
|
||||
│ └── singleblock/ # Single block partials
|
||||
│ └── default.cshtml
|
||||
├── wwwroot/
|
||||
│ └── favicon.ico # Site favicon
|
||||
├── umbraco/ # Runtime data directory
|
||||
│ ├── Data/ # SQLite database, temp files
|
||||
│ │ ├── Umbraco.sqlite.db # Development database
|
||||
│ │ └── TEMP/ # ModelsBuilder, DistCache
|
||||
│ ├── Logs/ # Serilog JSON logs
|
||||
│ └── models/ # Generated content models
|
||||
├── appsettings.json # Production config
|
||||
├── appsettings.Development.json # Development overrides
|
||||
├── appsettings.template.json # Template for new installs
|
||||
├── appsettings.Development.template.json # Dev template
|
||||
├── appsettings-schema.json # JSON schema reference
|
||||
├── appsettings-schema.Umbraco.Cms.json # Full Umbraco schema (71KB)
|
||||
├── umbraco-package-schema.json # Package manifest schema (495KB)
|
||||
├── Program.cs # Application entry point (33 lines)
|
||||
└── Umbraco.Web.UI.csproj # Project file (75 lines)
|
||||
```
|
||||
|
||||
### Project Dependencies
|
||||
|
||||
```xml
|
||||
<ProjectReference Include="..\Umbraco.Cms\Umbraco.Cms.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Cms.DevelopmentMode.Backoffice\..." />
|
||||
```
|
||||
|
||||
- **Umbraco.Cms** - Meta-package referencing all Umbraco packages
|
||||
- **Umbraco.Cms.DevelopmentMode.Backoffice** - Hot reload for backoffice development
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
### Running the Application
|
||||
|
||||
```bash
|
||||
# Run with Kestrel (recommended)
|
||||
dotnet run --project src/Umbraco.Web.UI
|
||||
|
||||
# Run with watch for auto-reload
|
||||
dotnet watch --project src/Umbraco.Web.UI
|
||||
|
||||
# Run from Visual Studio
|
||||
# Profile: "Umbraco.Web.UI" (https://localhost:44339)
|
||||
# Profile: "IIS Express" (http://localhost:11000)
|
||||
```
|
||||
|
||||
### Database
|
||||
|
||||
SQLite is configured by default for development:
|
||||
- Location: `src/Umbraco.Web.UI/umbraco/Data/Umbraco.sqlite.db`
|
||||
- Connection string configured in `appsettings.json`
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### Program.cs (33 lines)
|
||||
|
||||
Minimal Umbraco startup using builder pattern:
|
||||
|
||||
```csharp
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.CreateUmbracoBuilder()
|
||||
.AddBackOffice()
|
||||
.AddWebsite()
|
||||
#if UseDeliveryApi
|
||||
.AddDeliveryApi()
|
||||
#endif
|
||||
.AddComposers()
|
||||
.Build();
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
await app.BootUmbracoAsync();
|
||||
|
||||
app.UseUmbraco()
|
||||
.WithMiddleware(u =>
|
||||
{
|
||||
u.UseBackOffice();
|
||||
u.UseWebsite();
|
||||
})
|
||||
.WithEndpoints(u =>
|
||||
{
|
||||
u.UseBackOfficeEndpoints();
|
||||
u.UseWebsiteEndpoints();
|
||||
});
|
||||
|
||||
await app.RunAsync();
|
||||
```
|
||||
|
||||
**Preprocessor Directives**:
|
||||
- `#if UseDeliveryApi` - Enable/disable Delivery API
|
||||
- `#if UseHttpsRedirect` - Enable/disable HTTPS redirection
|
||||
|
||||
### ControllersAsServicesComposer (lines 36-38)
|
||||
|
||||
Registers all controllers in the DI container for validation purposes:
|
||||
|
||||
```csharp
|
||||
public void Compose(IUmbracoBuilder builder) => builder.Services
|
||||
.AddMvc()
|
||||
.AddControllersAsServicesWithoutChangingActivator();
|
||||
```
|
||||
|
||||
**Purpose**: Detects ambiguous constructors in CMS controllers that would cause issues with `ServiceBasedControllerActivator`. Not shipped in `Umbraco.Templates`.
|
||||
|
||||
### Launch Profiles (Properties/launchSettings.json)
|
||||
|
||||
| Profile | URL | Command |
|
||||
|---------|-----|---------|
|
||||
| Umbraco.Web.UI | https://localhost:44339, http://localhost:11000 | Project |
|
||||
| IIS Express | http://localhost:11000, SSL port 44339 | IISExpress |
|
||||
|
||||
---
|
||||
|
||||
## 4. Configuration
|
||||
|
||||
### appsettings.template.json
|
||||
|
||||
Default configuration for new installations:
|
||||
|
||||
| Setting | Value | Description |
|
||||
|---------|-------|-------------|
|
||||
| `ModelsMode` | `InMemoryAuto` | Auto-generate models in memory |
|
||||
| `DefaultUILanguage` | `en-us` | Backoffice language |
|
||||
| `HideTopLevelNodeFromPath` | `true` | Clean URLs |
|
||||
| `TimeOut` | `00:20:00` | Session timeout |
|
||||
| `UseHttps` | `false` | HTTPS enforcement |
|
||||
| `UsernameIsEmail` | `true` | Email as username |
|
||||
| `UserPassword.RequiredLength` | `10` | Minimum password length |
|
||||
|
||||
### appsettings.Development.template.json
|
||||
|
||||
Development-specific overrides:
|
||||
|
||||
| Setting | Value | Description |
|
||||
|---------|-------|-------------|
|
||||
| `Hosting.Debug` | `true` | Debug mode enabled |
|
||||
| `LuceneDirectoryFactory` | `TempFileSystemDirectoryFactory` | Examine indexes in temp |
|
||||
| Console logging | `Async` sink | Serilog console output |
|
||||
| Examine log levels | `Debug` | Detailed Examine logging |
|
||||
|
||||
### Auto-Copy Build Target (csproj lines 65-73)
|
||||
|
||||
MSBuild targets automatically copy template files if missing (appsettings.json and appsettings.Development.json).
|
||||
|
||||
---
|
||||
|
||||
## 5. ModelsBuilder
|
||||
|
||||
### InMemoryAuto Mode
|
||||
|
||||
The project uses `InMemoryAuto` mode:
|
||||
- Models auto-generated at runtime
|
||||
- Source stored in `umbraco/Data/TEMP/InMemoryAuto/`
|
||||
- Compiled assembly in `Compiled/` subdirectory
|
||||
- `models.hash` tracks content type changes
|
||||
|
||||
### Generated Models Location
|
||||
|
||||
```
|
||||
umbraco/models/*.generated.cs # Source files (for reference)
|
||||
umbraco/Data/TEMP/InMemoryAuto/ # Runtime compilation
|
||||
```
|
||||
|
||||
### Razor Compilation Settings (csproj lines 50-51)
|
||||
|
||||
Razor compilation is disabled for InMemoryAuto mode (`RazorCompileOnBuild=false`, `RazorCompileOnPublish=false`).
|
||||
|
||||
---
|
||||
|
||||
## 6. ICU Globalization
|
||||
|
||||
### App-Local ICU (csproj lines 39-40)
|
||||
|
||||
Uses app-local ICU (`Microsoft.ICU.ICU4C.Runtime` v72.1.0.3) for consistent globalization across platforms.
|
||||
|
||||
**Note**: Ensure ICU version matches between package reference and runtime option. Changes must also be made to `Umbraco.Templates`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Project-Specific Notes
|
||||
|
||||
### Development vs Production
|
||||
|
||||
This project is for **development only**:
|
||||
- `IsPackable=false` - Not published as NuGet
|
||||
- `EnablePackageValidation=false` - No package validation
|
||||
- References `DevelopmentMode.Backoffice` for hot reload
|
||||
|
||||
### Package Version Management
|
||||
|
||||
```xml
|
||||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
```
|
||||
|
||||
Does NOT use central package management. Versions specified directly:
|
||||
- `Microsoft.EntityFrameworkCore.Design` - For EF Core migrations tooling
|
||||
- `Microsoft.Build.Tasks.Core` - Security fix for EFCore.Design dependency
|
||||
- `Microsoft.ICU.ICU4C.Runtime` - Globalization
|
||||
|
||||
### Umbraco Targets Import (csproj lines 19-20)
|
||||
|
||||
Imports shared build configuration from `Umbraco.Cms.Targets` project (props and targets).
|
||||
|
||||
### Excluded Views (csproj lines 55-57)
|
||||
|
||||
Three Umbraco views excluded from content (UmbracoInstall, UmbracoLogin, UmbracoBackOffice) as they come from `Umbraco.Cms.StaticAssets` RCL.
|
||||
|
||||
### Known Technical Debt
|
||||
|
||||
1. **Warning Suppression** (csproj lines 12-16): `SA1119` - Unnecessary parenthesis to fix
|
||||
|
||||
### Runtime Data (umbraco/ directory)
|
||||
|
||||
| Directory | Contents | Git Status |
|
||||
|-----------|----------|------------|
|
||||
| `umbraco/Data/` | SQLite database, MainDom locks | Ignored |
|
||||
| `umbraco/Logs/` | Serilog JSON trace logs | Ignored |
|
||||
| `umbraco/models/` | Generated content models | Ignored |
|
||||
| `umbraco/Data/TEMP/` | ModelsBuilder, DistCache | Ignored |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Run development site
|
||||
dotnet run --project src/Umbraco.Web.UI
|
||||
|
||||
# Clean runtime data (reset database)
|
||||
rm -rf src/Umbraco.Web.UI/umbraco/Data
|
||||
|
||||
# Reset to fresh install
|
||||
rm src/Umbraco.Web.UI/appsettings.json
|
||||
rm src/Umbraco.Web.UI/appsettings.Development.json
|
||||
```
|
||||
|
||||
### Important Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Program.cs` | Application entry point |
|
||||
| `appsettings.template.json` | Default configuration |
|
||||
| `appsettings.Development.template.json` | Development overrides |
|
||||
| `launchSettings.json` | Debug profiles |
|
||||
| `Composers/ControllersAsServicesComposer.cs` | DI validation |
|
||||
|
||||
### URLs
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| Development (Kestrel) | https://localhost:44339 |
|
||||
| Development (HTTP) | http://localhost:11000 |
|
||||
| Backoffice | /umbraco |
|
||||
| Installer | /install (on first run) |
|
||||
|
||||
### Related Projects
|
||||
|
||||
| Project | Relationship |
|
||||
|---------|--------------|
|
||||
| `Umbraco.Cms` | Meta-package (all dependencies) |
|
||||
| `Umbraco.Cms.DevelopmentMode.Backoffice` | Hot reload support |
|
||||
| `Umbraco.Cms.StaticAssets` | Backoffice/login views |
|
||||
| `Umbraco.Cms.Targets` | Build configuration |
|
||||
| `Umbraco.Web.Common` | Web functionality |
|
||||
245
src/Umbraco.Web.Website/CLAUDE.md
Normal file
245
src/Umbraco.Web.Website/CLAUDE.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Umbraco.Web.Website
|
||||
|
||||
Front-end website functionality for Umbraco CMS. Provides Surface controllers, member authentication/registration, content routing, and Razor view engine support for rendering published content.
|
||||
|
||||
**Project Type**: Class Library (NuGet package)
|
||||
**Target Framework**: net10.0
|
||||
**Package ID**: Umbraco.Cms.Web.Website
|
||||
**Namespace**: Umbraco.Cms.Web.Website
|
||||
**Dependencies**: Umbraco.Web.Common
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
### Project Purpose
|
||||
|
||||
This project provides the front-end website layer:
|
||||
|
||||
1. **Surface Controllers** - POST/GET controllers for forms and user interactions
|
||||
2. **Member Authentication** - Login, registration, profile, 2FA, external logins
|
||||
3. **Content Routing** - Dynamic route value transformation for Umbraco content
|
||||
4. **View Engines** - Custom Razor view locations and profiling wrappers
|
||||
5. **Public Access** - Protected content with member authentication
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```
|
||||
Umbraco.Web.Website/
|
||||
├── ActionResults/
|
||||
│ ├── RedirectToUmbracoPageResult.cs # Redirect to content by key (151 lines)
|
||||
│ ├── RedirectToUmbracoUrlResult.cs # Redirect to current URL
|
||||
│ └── UmbracoPageResult.cs # Return current page with ViewData
|
||||
├── Cache/
|
||||
│ └── PartialViewCacheInvalidators/
|
||||
│ └── MemberPartialViewCacheInvalidator.cs
|
||||
├── Collections/
|
||||
│ ├── SurfaceControllerTypeCollection.cs
|
||||
│ └── SurfaceControllerTypeCollectionBuilder.cs
|
||||
├── Controllers/
|
||||
│ ├── SurfaceController.cs # Base front-end controller (113 lines)
|
||||
│ ├── UmbLoginController.cs # Member login (128 lines)
|
||||
│ ├── UmbRegisterController.cs # Member registration
|
||||
│ ├── UmbProfileController.cs # Member profile management
|
||||
│ ├── UmbLoginStatusController.cs # Login status checking
|
||||
│ ├── UmbExternalLoginController.cs # OAuth/external login
|
||||
│ ├── UmbTwoFactorLoginController.cs # 2FA authentication
|
||||
│ ├── RenderNoContentController.cs # No content page
|
||||
│ └── UmbracoRenderingDefaultsOptions.cs # Default rendering options
|
||||
├── DependencyInjection/
|
||||
│ ├── UmbracoBuilderExtensions.cs # AddWebsite() (91 lines)
|
||||
│ └── UmbracoBuilder.MemberIdentity.cs # AddMemberExternalLogins() (22 lines)
|
||||
├── Extensions/
|
||||
│ ├── HtmlHelperRenderExtensions.cs # Razor render helpers
|
||||
│ ├── LinkGeneratorExtensions.cs # URL generation
|
||||
│ ├── TypeLoaderExtensions.cs # Surface controller discovery
|
||||
│ ├── UmbracoApplicationBuilder.Website.cs # UseWebsite() middleware
|
||||
│ └── WebsiteUmbracoBuilderExtensions.cs
|
||||
├── Middleware/
|
||||
│ └── BasicAuthenticationMiddleware.cs # Basic auth support
|
||||
├── Models/
|
||||
│ ├── LoginModel.cs # (in Web.Common)
|
||||
│ ├── RegisterModel.cs # Registration form
|
||||
│ ├── ProfileModel.cs # Profile form
|
||||
│ ├── RegisterModelBuilder.cs # Model builder
|
||||
│ ├── ProfileModelBuilder.cs # Profile builder
|
||||
│ ├── MemberModelBuilderBase.cs # Builder base class
|
||||
│ ├── MemberModelBuilderFactory.cs # Factory for builders
|
||||
│ └── NoNodesViewModel.cs # Empty site view model
|
||||
├── Routing/
|
||||
│ ├── UmbracoRouteValueTransformer.cs # Dynamic route transformer (330 lines)
|
||||
│ ├── UmbracoRouteValuesFactory.cs # Creates UmbracoRouteValues
|
||||
│ ├── ControllerActionSearcher.cs # Finds controller actions
|
||||
│ ├── PublicAccessRequestHandler.cs # Protected content handling
|
||||
│ ├── FrontEndRoutes.cs # Route definitions
|
||||
│ ├── EagerMatcherPolicy.cs # Early route matching
|
||||
│ ├── NotFoundSelectorPolicy.cs # 404 handling
|
||||
│ ├── SurfaceControllerMatcherPolicy.cs # Surface controller matching
|
||||
│ ├── IControllerActionSearcher.cs
|
||||
│ ├── IPublicAccessRequestHandler.cs
|
||||
│ └── IUmbracoRouteValuesFactory.cs
|
||||
├── Security/
|
||||
│ ├── MemberAuthenticationBuilder.cs # Auth configuration
|
||||
│ └── MemberExternalLoginsBuilder.cs # External login providers
|
||||
├── ViewEngines/
|
||||
│ ├── ProfilingViewEngine.cs # MiniProfiler wrapper
|
||||
│ ├── ProfilingViewEngineWrapperMvcViewOptionsSetup.cs
|
||||
│ ├── PluginRazorViewEngineOptionsSetup.cs # Plugin view locations
|
||||
│ └── RenderRazorViewEngineOptionsSetup.cs # Render view locations
|
||||
└── Umbraco.Web.Website.csproj # Project file (34 lines)
|
||||
```
|
||||
|
||||
### Request Flow
|
||||
|
||||
```
|
||||
HTTP Request → UmbracoRouteValueTransformer.TransformAsync()
|
||||
↓
|
||||
Check UmbracoContext exists
|
||||
↓
|
||||
Check routable document filter
|
||||
↓
|
||||
RouteRequestAsync() → IPublishedRouter
|
||||
↓
|
||||
UmbracoRouteValuesFactory.CreateAsync()
|
||||
↓
|
||||
PublicAccessRequestHandler (protected content)
|
||||
↓
|
||||
Check for Surface controller POST (ufprt)
|
||||
↓ (if POST)
|
||||
HandlePostedValues() → Surface Controller
|
||||
↓ (else)
|
||||
Return route values → Render Controller
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Commands
|
||||
|
||||
**For Git workflow and build commands**, see [repository root](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 3. Key Components
|
||||
|
||||
### SurfaceController (Controllers/SurfaceController.cs)
|
||||
|
||||
Base class for front-end form controllers (113 lines).
|
||||
|
||||
**Key Features**:
|
||||
- `[AutoValidateAntiforgeryToken]` applied by default (line 19)
|
||||
- Extends `PluginController` with `IPublishedUrlProvider`
|
||||
- `CurrentPage` property for accessing routed content (lines 40-53)
|
||||
- Redirect helpers for content by key/entity (lines 58-102)
|
||||
- Supports query strings on redirects
|
||||
|
||||
### UmbracoRouteValueTransformer (Routing/UmbracoRouteValueTransformer.cs)
|
||||
|
||||
Dynamic route value transformer for front-end content routing (330 lines).
|
||||
|
||||
**TransformAsync Flow** (lines 120-194):
|
||||
1. Check `UmbracoContext` exists (skip client-side requests)
|
||||
2. Check `IRoutableDocumentFilter.IsDocumentRequest()` (skip static files)
|
||||
3. Check no existing dynamic routing active
|
||||
4. Check `IDocumentUrlService.HasAny()` → `RenderNoContentController` if empty
|
||||
5. `RouteRequestAsync()` → create published request via `IPublishedRouter`
|
||||
6. `UmbracoRouteValuesFactory.CreateAsync()` → resolve controller/action
|
||||
7. `PublicAccessRequestHandler.RewriteForPublishedContentAccessAsync()` → handle protected content
|
||||
8. Store `UmbracoRouteValues` in `HttpContext.Features`
|
||||
9. Check for Surface controller POST via `ufprt` parameter
|
||||
10. Return route values for controller/action
|
||||
|
||||
**Surface Controller POST Handling** (lines 244-309):
|
||||
- Detects `ufprt` (Umbraco Form Post Route Token) in request
|
||||
- Decrypts via `EncryptionHelper.DecryptAndValidateEncryptedRouteString()`
|
||||
- Extracts controller, action, area from encrypted data
|
||||
- Routes to Surface controller action
|
||||
|
||||
**Reserved Keys for ufprt** (lines 321-327):
|
||||
- `c` = Controller
|
||||
- `a` = Action
|
||||
- `ar` = Area
|
||||
|
||||
### AddWebsite() (DependencyInjection/UmbracoBuilderExtensions.cs)
|
||||
|
||||
Main DI registration entry point (91 lines).
|
||||
|
||||
**Key Registrations** (lines 34-90): Surface controller discovery, view engine setup, routing services, matcher policies, member models, distributed cache, ModelsBuilder, and member identity.
|
||||
|
||||
### Member Controllers
|
||||
|
||||
Built-in Surface controllers for member functionality:
|
||||
|
||||
| Controller | Purpose | Key Action |
|
||||
|------------|---------|------------|
|
||||
| `UmbLoginController` | Member login | `HandleLogin(LoginModel)` |
|
||||
| `UmbRegisterController` | Member registration | `HandleRegister(RegisterModel)` |
|
||||
| `UmbProfileController` | Profile management | `HandleUpdate(ProfileModel)` |
|
||||
| `UmbLoginStatusController` | Login status | `HandleLogout()` |
|
||||
| `UmbExternalLoginController` | OAuth login | `ExternalLogin(provider)` |
|
||||
| `UmbTwoFactorLoginController` | 2FA verification | `HandleTwoFactorLogin()` |
|
||||
|
||||
### UmbLoginController (Controllers/UmbLoginController.cs)
|
||||
|
||||
Member login Surface controller (128 lines).
|
||||
|
||||
**HandleLogin** (lines 53-114): Validates credentials, handles 2FA/lockout, redirects on success. Uses encrypted redirect URLs (lines 120-126).
|
||||
|
||||
### RedirectToUmbracoPageResult (ActionResults/RedirectToUmbracoPageResult.cs)
|
||||
|
||||
Action result for redirecting to Umbraco content (151 lines).
|
||||
|
||||
Implements `IKeepTempDataResult` to preserve TempData. Resolves content by `Guid` key or `IPublishedContent`, supports query strings.
|
||||
|
||||
---
|
||||
|
||||
## 4. View Engine Configuration
|
||||
|
||||
**Custom View Locations**:
|
||||
- `RenderRazorViewEngineOptionsSetup` - /Views/{controller}/{action}.cshtml
|
||||
- `PluginRazorViewEngineOptionsSetup` - /App_Plugins/{area}/Views/...
|
||||
- `ProfilingViewEngine` - MiniProfiler wrapper for view timing
|
||||
|
||||
---
|
||||
|
||||
## 5. Project-Specific Notes
|
||||
|
||||
**Warning Suppressions** (csproj lines 10-18): ASP0019, CS0618, SA1401, SA1649, IDE1006
|
||||
|
||||
**InternalsVisibleTo**: Umbraco.Tests.UnitTests, Umbraco.Tests.Integration
|
||||
|
||||
**Known Technical Debt**:
|
||||
1. Load balanced setup needs review (UmbracoBuilderExtensions.cs:47)
|
||||
2. UmbracoContext re-assignment pattern (UmbracoRouteValueTransformer.cs:229-232)
|
||||
3. Obsolete constructor without IDocumentUrlService (removal in Umbraco 18)
|
||||
|
||||
**Surface Controller Form Token (ufprt)**: Encrypted route token prevents tampering. Hidden field includes controller/action/area encrypted via Data Protection, decrypted in `UmbracoRouteValueTransformer.GetFormInfo()`.
|
||||
|
||||
**Public Access**: `PublicAccessRequestHandler` checks member authentication, redirects to login if needed.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Startup**:
|
||||
```csharp
|
||||
builder.CreateUmbracoBuilder().AddWebsite().AddMembersIdentity().Build();
|
||||
app.UseUmbraco().WithMiddleware(u => u.UseWebsite()).WithEndpoints(u => u.UseWebsiteEndpoints());
|
||||
```
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
| Interface | Implementation | Purpose |
|
||||
|-----------|----------------|---------|
|
||||
| `IControllerActionSearcher` | ControllerActionSearcher | Find controller actions |
|
||||
| `IUmbracoRouteValuesFactory` | UmbracoRouteValuesFactory | Create route values |
|
||||
| `IPublicAccessRequestHandler` | PublicAccessRequestHandler | Protected content |
|
||||
| `IRoutableDocumentFilter` | RoutableDocumentFilter | Filter routable requests |
|
||||
|
||||
### Related Projects
|
||||
|
||||
| Project | Relationship |
|
||||
|---------|--------------|
|
||||
| `Umbraco.Web.Common` | Base controllers, UmbracoContext |
|
||||
| `Umbraco.Core` | Interfaces, routing contracts |
|
||||
| `Umbraco.Infrastructure` | Service implementations |
|
||||
| `Umbraco.Web.UI` | Main web application (references this) |
|
||||
Reference in New Issue
Block a user