The service layer contains all business logic for the Package Script Writer application. Services are registered with Scoped lifetime in the dependency injection container.
- Service Overview
- ScriptGeneratorService
- MarketplacePackageService
- QueryStringService
- UmbracoVersionService
- Service Registration
| Service | Interface | Purpose | Lines of Code |
|---|---|---|---|
| ScriptGeneratorService | IScriptGeneratorService | Generates installation scripts | 327 |
| MarketplacePackageService | IPackageService | Fetches package data from APIs | 150+ |
| QueryStringService | IQueryStringService | URL query string handling | 100+ |
| UmbracoVersionService | IUmbracoVersionService | Version lifecycle management | 80+ |
All services follow SOLID principles with clear separation of concerns and interface-based contracts.
File: src/PSW/Services/ScriptGeneratorService.cs
Purpose: Generates shell scripts for Umbraco project installation based on user selections.
public class ScriptGeneratorService : IScriptGeneratorService
{
private readonly PSWConfig _pswConfig;
private readonly IUmbracoVersionService _umbracoVersionService;
public ScriptGeneratorService(
IOptions<PSWConfig> pswConfig,
IUmbracoVersionService umbracoVersionService)
{
_pswConfig = pswConfig.Value;
_umbracoVersionService = umbracoVersionService;
}
}Signature:
public string GenerateScript(PackagesViewModel model)Description: Main entry point that orchestrates the entire script generation process.
Flow:
- Generates Umbraco template installation commands
- Creates solution file commands (if enabled)
- Creates project with unattended install options
- Adds project to solution
- Generates Docker Compose commands (if enabled)
- Adds starter kit package
- Adds selected packages
- Generates run command
- Applies output formatting (comments, one-liner)
Returns: Complete installation script as a string
Example Output:
# Ensure we have the version specific Umbraco templates
dotnet new install Umbraco.Templates::14.3.0 --force
# Create solution/project
dotnet new sln --name "MySolution"
dotnet new umbraco --force -n "MyProject" --friendly-name "Admin" --email "admin@example.com" --password "Password123" --development-database-type SQLite
dotnet sln add "MyProject"
#Add Packages
dotnet add "MyProject" package Umbraco.Community.BlockPreview --version 1.6.0
dotnet run --project "MyProject"
#RunningSignature:
public List<string> GenerateUmbracoTemplatesSectionScript(PackagesViewModel model)Description: Generates commands to install Umbraco or community templates.
Logic:
- Handles LTS version replacement (converts "LTS" to actual version number)
- Uses
dotnet new installfor recent versions - Uses
dotnet new -ifor legacy versions (9.x, 10.x) - Supports specific version or latest
Example Output:
# Ensure we have the version specific Umbraco templates
dotnet new install Umbraco.Templates::14.3.0 --forceSignature:
public List<string> GenerateCreateSolutionFileScript(PackagesViewModel model)Description: Generates command to create a solution file.
Conditions: Only generates output if model.CreateSolutionFile is true
Example Output:
# Create solution/project
dotnet new sln --name "MySolution"Signature:
public List<string> GenerateCreateProjectScript(PackagesViewModel model)Description: Generates the project creation command with all configuration options.
Features:
- Supports unattended install with database configuration
- Handles multiple database types (SQLite, LocalDB, SQL Server, SQL Azure, SQLCE)
- Version-specific logic for different Umbraco versions
- Docker support flag (
--add-docker) - Special handling for v10 RC versions
Database Type Handling:
| Database Type | Umbraco < 10 | Umbraco >= 10 |
|---|---|---|
| SQLite | Connection string | --development-database-type SQLite |
| LocalDB | Connection string | --development-database-type LocalDB |
| SQL Server | Connection string | Connection string |
| SQL Azure | Connection string | Connection string |
| SQLCE | Connection string (v9 only) | Not supported |
Example Output:
dotnet new umbraco --force -n "MyProject" --add-docker --friendly-name "Admin User" --email "admin@example.com" --password "SuperSecret123" --development-database-type SQLiteSignature:
public List<string> GenerateAddProjectToSolutionScript(PackagesViewModel model)Description: Generates command to add the project to the solution file.
Conditions: Only generates if both CreateSolutionFile is true and SolutionName is provided
Example Output:
dotnet sln add "MyProject"Signature:
public List<string> GenerateAddStarterKitScript(PackagesViewModel model, bool renderPackageName)Description: Generates command to add a starter kit package.
Parameters:
model: View model with starter kit settingsrenderPackageName: If true, includes project name in command
Example Output:
#Add starter kit
dotnet add "MyProject" package Umbraco.TheStarterKitSignature:
public List<string> GenerateAddPackagesScript(PackagesViewModel model, bool renderPackageName)Description: Generates commands to add all selected NuGet packages.
Package String Format: PackageId|Version,PackageId|Version|--prerelease
Features:
- Parses package string to extract package ID and version
- Skips starter kit if already added as starter kit
- Supports prerelease flag
- Handles version specification
Example Output:
#Add Packages
dotnet add "MyProject" package Umbraco.Community.BlockPreview --version 1.6.0
dotnet add "MyProject" package Diplo.GodMode --version 3.0.3
#Ignored Umbraco.TheStarterKit as it was added as a starter kitSignature:
public List<string> GenerateRunProjectScript(PackagesViewModel model, bool renderPackageName)Description: Generates the final command to run the project.
Example Output:
dotnet run --project "MyProject"
#RunningSignature:
public List<string> GenerateAddDockerComposeScript(PackagesViewModel model)Description: Generates command to add Docker Compose support.
Conditions: Only for Umbraco templates with Docker enabled and appropriate version
Example Output:
#Add Docker Compose
dotnet new umbraco-compose -P "MyProject"File: src/PSW/Services/MarketplacePackageService.cs
Purpose: Fetches package data from Umbraco Marketplace and NuGet.org with caching.
public class MarketplacePackageService : IPackageService
{
private readonly IHttpClientFactory _httpClientFactory;
public MarketplacePackageService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
}Signature:
public async Task<PagedPackages> GetAllPackages(
IMemoryCache memoryCache,
int cacheDurationMinutes)Description: Retrieves all packages from Umbraco Marketplace with caching.
Caching:
- Cache Key:
all-packages - TTL: 60 minutes (configurable)
- Eviction: Absolute expiration
API Endpoint: https://marketplace.umbraco.com/umbraco/api/marketplaceapi/getallpackages
Response Processing:
- Checks in-memory cache first
- If cache miss, calls Marketplace API
- Deserializes JSON response to
PagedPackagesmodel - Stores in cache for future requests
- Returns package list
Example Response Structure:
{
"count": 150,
"next": null,
"packages": [
{
"id": "umbraco-community-blockpreview",
"name": "Umbraco Community Block Preview",
"description": "Preview Umbraco Block content",
"owner": "rickbutterfield",
"icon": "https://...",
"tags": ["backoffice", "editor"]
}
]
}Signature:
public async Task<List<string>> GetNugetPackageVersions(
string packageId,
bool includePrerelease,
IMemoryCache memoryCache,
int cacheDurationMinutes)Description: Fetches available versions for a specific NuGet package.
Caching:
- Cache Key:
package-versions-{packageId} - TTL: 60 minutes (configurable)
API Endpoints:
-
Prerelease Versions:
GET https://api.nuget.org/v3-flatcontainer/{packageId}/index.jsonReturns: All versions including prerelease
-
Stable Versions Only:
GET https://azuresearch-usnc.nuget.org/query?q={packageId}&prerelease=falseReturns: Stable versions only
Response Processing:
- Checks cache for existing versions
- If cache miss, calls appropriate NuGet API
- Parses JSON response to extract version array
- Stores in cache
- Returns list of version strings
Example Response:
{
"versions": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0-beta",
"2.0.0"
]
}File: src/PSW/Services/QueryStringService.cs
Purpose: Handles bidirectional conversion between URL query strings and view models.
Signature:
public PackagesViewModel LoadModelFromQueryString(string queryString)Description: Parses URL query parameters into a PackagesViewModel object.
Supported Parameters:
TemplateName- Template to useTemplateVersion- Specific version or "LTS"ProjectName- Project nameSolutionName- Solution nameCreateSolutionFile- Boolean flagPackages- Comma-separated package listUseUnattendedInstall- Boolean flagDatabaseType- Database type selectionConnectionString- Database connection stringUserFriendlyName- Admin user nameUserEmail- Admin emailUserPassword- Admin passwordIncludeStarterKit- Boolean flagStarterKitPackage- Starter kit package nameCanIncludeDocker- Boolean flagIncludeDockerfile- Boolean flagIncludeDockerCompose- Boolean flagOnelinerOutput- Boolean flagRemoveComments- Boolean flag
Example URL:
?TemplateName=Umbraco.Templates
&TemplateVersion=14.3.0
&ProjectName=MyProject
&Packages=Umbraco.Community.BlockPreview|1.6.0,Diplo.GodMode|3.0.3
&UseUnattendedInstall=true
&DatabaseType=SQLite
Backward Compatibility: Handles older URL formats for compatibility with saved links.
Signature:
public string GenerateQueryString(PackagesViewModel model, string baseUrl)Description: Generates a shareable URL from the current model state.
Features:
- URL-encodes all parameters
- Includes only non-default values
- Generates clean, readable URLs
- Supports all model properties
Returns: Complete URL with query string
Example Output:
https://psw.codeshare.co.uk?TemplateName=Umbraco.Templates&TemplateVersion=14.3.0&ProjectName=MyProject&Packages=Umbraco.Community.BlockPreview%7C1.6.0
File: src/PSW/Services/UmbracoVersionService.cs
Purpose: Manages Umbraco version lifecycle data and determines LTS/STS status.
public class UmbracoVersionService : IUmbracoVersionService
{
// No external dependencies - uses PSWConfig injected via methods
}Signature:
public string GetLatestLTSVersion(PSWConfig config)Description: Determines the latest Long-Term Support (LTS) version from configuration.
Logic:
- Reads
UmbracoVersionsarray fromPSWConfig - Filters for versions where
IsLTS = true - Finds the version with the latest
ReleaseDate - Returns version string (e.g., "14.3.0")
Example:
var latestLTS = _umbracoVersionService.GetLatestLTSVersion(_pswConfig);
// Returns: "14.3.0"Signature:
public VersionStatus GetVersionStatus(string version, PSWConfig config)Description: Determines if a version is active, EOL, or in security-only phase.
Return Values:
VersionStatus.LTS- Long-Term Support versionVersionStatus.STS- Short-Term Support versionVersionStatus.EOL- End of Life (no longer supported)VersionStatus.SecurityOnly- Security updates only
Logic:
- Compares current date with
EndOfLifeDate - Checks
EndOfSecurityDateif applicable - Determines LTS vs STS based on
IsLTSflag
File: src/PSW/Program.cs
Services are registered in the dependency injection container with Scoped lifetime:
// Service registration
builder.Services.AddScoped<IScriptGeneratorService, ScriptGeneratorService>();
builder.Services.AddScoped<IPackageService, MarketplacePackageService>();
builder.Services.AddScoped<IQueryStringService, QueryStringService>();
builder.Services.AddScoped<IUmbracoVersionService, UmbracoVersionService>();Scoped Lifetime: Service instances are created once per request and shared across that request.
Why Scoped?
- Services don't maintain state between requests
- Allows for proper disposal of resources
- Efficient for web request handling
- Services can share dependencies within a request
// HTTP Client Factory
builder.Services.AddHttpClient();
// Memory Cache
builder.Services.AddMemoryCache();public interface IScriptGeneratorService
{
string GenerateScript(PackagesViewModel model);
List<string> GenerateUmbracoTemplatesSectionScript(PackagesViewModel model);
List<string> GenerateCreateSolutionFileScript(PackagesViewModel model);
List<string> GenerateCreateProjectScript(PackagesViewModel model);
List<string> GenerateAddProjectToSolutionScript(PackagesViewModel model);
List<string> GenerateAddStarterKitScript(PackagesViewModel model, bool renderPackageName);
List<string> GenerateAddPackagesScript(PackagesViewModel model, bool renderPackageName);
List<string> GenerateRunProjectScript(PackagesViewModel model, bool renderPackageName);
List<string> GenerateAddDockerComposeScript(PackagesViewModel model);
}public interface IPackageService
{
Task<PagedPackages> GetAllPackages(
IMemoryCache memoryCache,
int cacheDurationMinutes);
Task<List<string>> GetNugetPackageVersions(
string packageId,
bool includePrerelease,
IMemoryCache memoryCache,
int cacheDurationMinutes);
}public interface IQueryStringService
{
PackagesViewModel LoadModelFromQueryString(string queryString);
string GenerateQueryString(PackagesViewModel model, string baseUrl);
}public interface IUmbracoVersionService
{
string GetLatestLTSVersion(PSWConfig config);
VersionStatus GetVersionStatus(string version, PSWConfig config);
}public class ScriptGeneratorServiceTests
{
[Fact]
public void GenerateScript_WithBasicModel_ReturnsValidScript()
{
// Arrange
var config = Options.Create(new PSWConfig());
var versionService = new Mock<IUmbracoVersionService>();
var service = new ScriptGeneratorService(config, versionService.Object);
var model = new PackagesViewModel
{
TemplateName = "Umbraco.Templates",
TemplateVersion = "14.3.0",
ProjectName = "TestProject"
};
// Act
var result = service.GenerateScript(model);
// Assert
Assert.Contains("dotnet new install Umbraco.Templates::14.3.0", result);
Assert.Contains("dotnet new umbraco", result);
}
}Services leverage IMemoryCache for optimal performance:
| Data Type | Cache Duration | Performance Impact |
|---|---|---|
| All Packages | 60 minutes | 100x improvement |
| Package Versions | 60 minutes | 100x improvement |
| Template Versions | 60 minutes | 50x improvement |
- Cache Warming: Pre-load common packages on startup
- Parallel Requests: Fetch multiple package versions concurrently
- Response Compression: Enable gzip compression for API responses
- CDN Integration: Cache static package data on CDN