Skip to main content

GeneralUpdate.Extension

Namespace: GeneralUpdate.Extension | Main Entry Point: GeneralExtensionHost (implements IExtensionHost) | NuGet Package: GeneralUpdate.Extension

1. Component Overview

1.1 Introduction

GeneralUpdate.Extension is an extension management component for .NET applications, designed to give host apps VS Code-like extension ecosystem capabilities: query extensions from a remote service, download extension packages, install or update to local directories, and handle version compatibility, platform matching, dependency resolution, SHA256 verification, failure rollback, and event notifications.

It's suited for scenarios where the main app and optional capabilities are distributed separately — reports, authentication, industry plugins, customer-customized modules, script executors, etc.

Core Capabilities:

CapabilityDescription
Extension QueryPaginated query of available extensions from server API with multi-condition filtering
One-Click UpdateUpdateExtensionAsync chains query→compatibility→dependencies→download→hash verify→install→catalog update
Safe InstallationZip Slip path traversal protection, pre-install backup, auto-rollback to old version on failure
Batch UpdatesUpdateExtensionsAsync processes multiple extensions sequentially, returns per-extension success/failure
Version CompatibilityMinHostVersionHostVersionMaxHostVersion required for installation
Platform Matching[Flags] TargetPlatform bitwise check against current OS
Dependency ResolutionTopological sort dependency tree with circular dependency detection, recursive install of missing deps
Resumable DownloadHTTP Range support for resuming interrupted downloads
Local CatalogPer-extension manifest.json with atomic write (.tmp → rename), persistence and loading
Lifecycle HooksBusiness logic injection before/after install, activate/deactivate, uninstall
Auto-Update PolicySetGlobalAutoUpdate / SetAutoUpdate toggles for global or per-extension auto-update
DI IntegrationExtensionHostBuilder registers default services; all services replaceable via DI

Business Problems Solved:

  • Main app bloat; non-core features should be independently updatable extensions
  • Different customers need different feature combinations; extension ecosystem enables on-demand installation
  • Extensions have inter-dependencies; need automatic dependency management and version compatibility
  • Need a unified extension management framework to reduce redundant development

Use Cases:

  • IDE plugin marketplace
  • Enterprise ERP/CRM industry modules (report templates, auth methods, data exports)
  • Independently distributed customer-customized features
  • Componentized publishing for script executors/tool suites

1.2 Environment & Dependencies

ItemDescription
Version10.5.0-beta.2
Target Frameworknetstandard2.0 (.NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+)
DependenciesMicrosoft.Extensions.DependencyInjection, Microsoft.Extensions.Logging.Abstractions, Microsoft.Extensions.Options, Newtonsoft.Json, System.Net.Http, System.IO.Compression, System.IO.Compression.ZipFile
CompatibilityAll .NET Standard 2.0 platforms

2. Feature List

FeatureDescriptionTypeRequiredNotes
Extension QueryPaginated query of extensions from server APICoreRecommendedMulti-condition filtering supported
Extension DownloadDownload extension ZIP from server with resume supportCoreAutomaticTriggered by DownloadExtensionAsync or one-click update
Extension InstallSafe ZIP extraction with Zip Slip protection and rollbackCoreAutomaticOnly .zip format accepted
One-Click UpdateAuto chain: query→compatibility→deps→download→verify→installCoreRecommendedUpdateExtensionAsync
Batch UpdateSequential multi-extension updateExtendedOptionalUpdateExtensionsAsync
Extension UninstallRemove from catalog and delete extension directoryCoreOptionalUninstallExtensionAsync
Version Compatibility CheckHost version must fall within extension's Min/Max rangeCoreAutomaticChecked automatically during update
Platform MatchingAuto-detect current OS, match extension's supported platformsCoreAutomaticPlatformMatcher via RuntimeInformation
Recursive Dependency InstallRecursively update missing dependenciesCoreAutomaticDependencies must be queryable from same server
Circular Dependency DetectionDetect cycles during topological sortCoreAutomaticDependencyResolver
SHA256 VerificationVerify downloaded file integrityCoreAutomaticWhen server Hash is non-empty
Local Catalog ManagementPer-extension manifest.json with atomic writeCoreAutomaticStored in extension directory
Auto-Update PolicyGlobal/per-extension auto-update togglesExtendedOptionalIn-memory only; no background polling
Lifecycle HooksBusiness logic before/after install/activate/deactivate/uninstallExtendedOptionalImplement IExtensionLifecycleHooks or extend DefaultExtensionLifecycleHooks
DI BuilderExtensionHostBuilder registers and replaces all servicesExtendedOptionalCustom IExtensionServiceFactory supported
Download Queue ManagementConcurrent download control (default 3)ExtendedOptionalDownloadQueueManager

3. API Configuration Reference

3.1 Configuration Properties (Props)

ExtensionHostOptions:

FieldTypeDefaultRequiredValuesDescription
ServerUrlstringYesValid absolute URLExtension service root; client calls {ServerUrl}/Query and {ServerUrl}/Download/{extensionId}
Schemestring""Optional"Bearer" etc.Auth scheme; no auth header when empty
Tokenstring""OptionalAuth token; must both be non-empty with Scheme
HostVersionstringRecommendedSemVer formatHost app version for compatibility
ExtensionsDirectorystringYesValid directory pathDownload, install, and .backup location
CatalogPathstringnullOptionalValid directory pathCatalog scan path; defaults to ExtensionsDirectory

ExtensionMetadata (Local Model):

FieldTypeDefaultRequiredDescription
IdstringYesUnique extension ID; key for dependency, query, update, uninstall
NamestringnullRecommendedStable name for directory and package naming
DisplayNamestringnullOptionalDisplay name
VersionstringnullRecommendedExtension version; suggested 1.2.3 format
FormatstringnullRecommendedPackage format; install requires .zip
HashstringnullRecommendedSHA256; verified during update when non-empty
PublisherstringnullOptionalPublisher
CategoriesstringnullOptionalComma-separated categories
SupportedPlatformsTargetPlatformAllRecommended[Flags]: Windows(1), Linux(2), MacOS(4), All(7)
MinHostVersionstringnullOptionalMinimum host version
MaxHostVersionstringnullOptionalMaximum host version
DependenciesstringnullOptionalComma-separated dependency extension IDs
IsPreReleaseboolfalseOptionalWhether pre-release
CustomPropertiesstringnullOptionalCustom properties as JSON string

ExtensionQueryDTO (Query Filters):

FieldTypeDefaultRequiredDescription
Idstring?nullOptionalExact match by ID
Namestring?nullOptionalPartial match by name
Publisherstring?nullOptionalPartial match by publisher
Categorystring?nullOptionalFilter by category
PlatformTargetPlatform?nullOptionalFilter by target platform
HostVersionstring?nullOptionalFor server-side compatibility check
PageNumberint1OptionalPage number (1-based)
PageSizeint10OptionalPage size

3.2 Instance Methods

IExtensionHost:

MethodParametersReturnsUse CaseNotes
QueryExtensionsAsync(ExtensionQueryDTO)queryTask<HttpResponseDTO<PagedResultDTO<ExtensionDTO>>>Search/browse extensionsResponse data in Body.Items
DownloadExtensionAsync(string, string)extensionId, savePathTask<bool>Download extension separatelySupports HTTP Range resume
UpdateExtensionAsync(string)extensionIdTask<bool>One-click update (recommended)Chains full update pipeline
InstallExtensionAsync(string, bool)extensionPath, rollbackOnFailureTask<bool>Manual local installOnly .zip accepted
UpdateExtensionsAsync(IEnumerable<string>, CancellationToken)extensionIds, ctTask<Dictionary<string, bool>>Batch updateSequential processing in order
UninstallExtensionAsync(string, CancellationToken)extensionId, ctTask<bool>Uninstall extensionRemoves from catalog and deletes directory
ActivateExtensionAsync(string, CancellationToken)extensionId, ctTaskActivate extensionInvokes lifecycle hooks
DeactivateExtensionAsync(string, CancellationToken)extensionId, ctTaskDeactivate extensionInvokes lifecycle hooks
IsExtensionCompatible(ExtensionMetadata)extensionboolCheck compatibilityBased on HostVersion vs Min/MaxHostVersion
SetAutoUpdate(string, bool)extensionId, autoUpdatevoidSet per-extension auto-updateIn-memory only; no background polling
SetGlobalAutoUpdate(bool)enabledvoidSet global defaultIn-memory only

ExtensionHostBuilder:

MethodParametersReturnsUse Case
ConfigureOptions(Action<ExtensionHostOptions>)configureExtensionHostBuilderConfigure via lambda
WithOptions(ExtensionHostOptions)optionsExtensionHostBuilderSet options directly
ConfigureServices(Action<IServiceCollection>)configureExtensionHostBuilderReplace or add DI services
Build()NoneIExtensionHostBuild host instance with auto-registered defaults

3.3 Callback Events

EventCallback ParametersTrigger TimingUsage Notes
ExtensionUpdateStatusChangedExtensionUpdateEventArgsExtensionId, ExtensionName, Status, Progress(0-100), ErrorMessageDuring extension update lifecycleStatus: QueuedUpdatingUpdateSuccessful/UpdateFailed

ExtensionUpdateStatus Enum:

ValueDescription
Queued (0)Queued for update
Updating (1)Downloading / updating
UpdateSuccessful (2)Update succeeded
UpdateFailed (3)Update failed

4. Advanced Examples

4.1 Extension Points Overview

All services are replaceable via ExtensionHostBuilder.ConfigureServices():

Service InterfaceDefault ImplementationDescription
IExtensionHttpClientExtensionHttpClientHTTP communication
IVersionCompatibilityCheckerVersionCompatibilityCheckerVersion compatibility check
IDownloadQueueManagerDownloadQueueManagerDownload queue management
IPlatformMatcherPlatformMatcherPlatform detection
IPlatformServicesRuntimePlatformServicesRuntime platform info
IExtensionMetadataMapperDefaultExtensionMetadataMapperDTO→model mapping
IExtensionCatalogExtensionCatalogLocal extension catalog
IDependencyResolverDependencyResolverDependency resolution
IExtensionLifecycleHooksDefaultExtensionLifecycleHooksLifecycle hooks (all virtual)
IExtensionServiceFactoryExtensionServiceFactoryService factory

4.2 Examples by Scenario

Scenario 1: Custom Lifecycle Hooks

Description: Custom logic before/after install: check license before install, initialize extension database after install.

using GeneralUpdate.Extension.Core;
using GeneralUpdate.Extension.Common.Models;

public sealed class LicensedLifecycleHooks : DefaultExtensionLifecycleHooks
{
public override async Task<bool> OnBeforeInstallAsync(
ExtensionMetadata extension, string? packagePath,
CancellationToken cancellationToken = default)
{
if (!LicenseManager.IsLicensed(extension.Id))
return false; // Block installation
return true;
}

public override async Task OnAfterInstallAsync(
ExtensionMetadata extension, CancellationToken cancellationToken = default)
{
if (extension.CustomProperties != null)
{
var props = Newtonsoft.Json.JsonConvert
.DeserializeObject<Dictionary<string, string>>(extension.CustomProperties);
if (props?.ContainsKey("DbInitScript") == true)
await DatabaseInitializer.RunAsync(props["DbInitScript"], cancellationToken);
}
Console.WriteLine($"Extension '{extension.DisplayName}' installed successfully.");
}
}

var host = new ExtensionHostBuilder()
.WithOptions(options)
.ConfigureServices(services =>
{
services.AddSingleton<IExtensionLifecycleHooks, LicensedLifecycleHooks>();
})
.Build();

Scenario 2: Custom HTTP Client with Shared Connection Pool

Description: Share HttpClient connection pool with main app; switch to POST query.

using GeneralUpdate.Extension.Communication;

var sharedClient = new HttpClient();

var httpClient = new ExtensionHttpClient(
serverUrl: "https://extensions.mycompany.com/Extension",
scheme: "Bearer",
token: "jwt-token",
httpClient: sharedClient,
ownsHttpClient: false)
{
UsePostForQuery = true
};

var host = new ExtensionHostBuilder()
.WithOptions(options)
.ConfigureServices(services =>
{
services.AddSingleton<IExtensionHttpClient>(httpClient);
})
.Build();

Scenario 3: Dependency Resolution + Conditional Batch Update

Description: When user selects an extension to install, auto-resolve dependencies and install them together.

var host = new GeneralExtensionHost(options);
host.ExtensionCatalog.LoadInstalledExtensions();

var response = await host.QueryExtensionsAsync(new ExtensionQueryDTO { Id = "report-extension" });

var ext = response.Body?.Items.FirstOrDefault();
if (ext == null) return;

var resolver = new DependencyResolver(host.ExtensionCatalog);
var deps = resolver.ResolveDependencies(
new ExtensionMetadata { Id = ext.Id, Dependencies = string.Join(",", ext.Dependencies ?? []) });
var missing = resolver.GetMissingDependencies(
new ExtensionMetadata { Id = ext.Id, Dependencies = string.Join(",", ext.Dependencies ?? []) });

var updateOrder = new List<string>();
updateOrder.AddRange(missing);
updateOrder.Add(ext.Id);

var results = await host.UpdateExtensionsAsync(updateOrder);
foreach (var (id, success) in results)
Console.WriteLine($" {id}: {(success ? "OK" : "FAILED")}");

5. Basic Usage Examples

5.1 Quick Start (Minimal Demo)

using GeneralUpdate.Extension.Core;
using GeneralUpdate.Extension.Common.DTOs;
using GeneralUpdate.Extension.Common.Models;

var options = new ExtensionHostOptions
{
ServerUrl = "https://extensions.example.com/Extension",
Scheme = "Bearer",
Token = "your-token",
HostVersion = "1.0.0",
ExtensionsDirectory = "./extensions"
};

var host = new GeneralExtensionHost(options);

host.ExtensionUpdateStatusChanged += (sender, e) =>
Console.WriteLine($"[{e.Status}] {e.ExtensionId}: {e.Progress}% {e.ErrorMessage}");

var response = await host.QueryExtensionsAsync(new ExtensionQueryDTO
{
Platform = TargetPlatform.Windows,
PageNumber = 1,
PageSize = 20
});

if (response.Body != null)
foreach (var ext in response.Body.Items)
Console.WriteLine($"{ext.DisplayName} v{ext.Version}");

var success = await host.UpdateExtensionAsync("report-extension");
Console.WriteLine(success ? "Extension updated." : "Update failed.");

5.2 Basic Parameter Combination

var host = new GeneralExtensionHost(new ExtensionHostOptions
{
ServerUrl = "https://extensions.mycompany.com/Extension",
Scheme = "Bearer",
Token = Environment.GetEnvironmentVariable("EXTENSION_TOKEN") ?? "",
HostVersion = "2.0.0",
ExtensionsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "extensions")
});

host.ExtensionUpdateStatusChanged += (_, e) =>
{
switch (e.Status)
{
case ExtensionUpdateStatus.Queued:
Console.WriteLine($"{e.ExtensionId}: queued"); break;
case ExtensionUpdateStatus.Updating:
Console.WriteLine($"{e.ExtensionId}: downloading... {e.Progress}%"); break;
case ExtensionUpdateStatus.UpdateSuccessful:
Console.WriteLine($"{e.ExtensionName ?? e.ExtensionId}: updated"); break;
case ExtensionUpdateStatus.UpdateFailed:
Console.WriteLine($"{e.ExtensionId}: failed — {e.ErrorMessage}"); break;
}
};

// Install local package
var installed = await host.InstallExtensionAsync("./downloads/report-extension_1.0.0.zip", rollbackOnFailure: true);

// Query installed extensions
host.ExtensionCatalog.LoadInstalledExtensions();
foreach (var ext in host.ExtensionCatalog.GetInstalledExtensions())
Console.WriteLine($"{ext.DisplayName} v{ext.Version} — compatible: {host.IsExtensionCompatible(ext)}");

// Configure auto-update
host.SetGlobalAutoUpdate(true);
host.SetAutoUpdate("large-extension", false);

5.3 Production-Ready Example

Full workflow with exception handling, dependency management, and compatibility checking:

using GeneralUpdate.Extension.Core;
using GeneralUpdate.Extension.Common.DTOs;
using GeneralUpdate.Extension.Common.Enums;
using GeneralUpdate.Extension.Common.Models;

var options = new ExtensionHostOptions
{
ServerUrl = "https://extensions.mycompany.com/Extension",
Scheme = "Bearer",
Token = Configuration.GetExtensionToken(),
HostVersion = AppInfo.CurrentVersion.ToString(),
ExtensionsDirectory = Path.Combine(AppInfo.DataDirectory, "extensions")
};

var host = new ExtensionHostBuilder()
.WithOptions(options)
.ConfigureServices(services =>
services.AddSingleton<IExtensionLifecycleHooks, AuditLifecycleHooks>())
.Build();

host.ExtensionUpdateStatusChanged += (_, e) =>
{
if (e.Status == ExtensionUpdateStatus.UpdateFailed)
Log.Error($"Extension '{e.ExtensionId}' update failed: {e.ErrorMessage}");
else if (e.Status == ExtensionUpdateStatus.UpdateSuccessful)
Log.Info($"Extension '{e.ExtensionName ?? e.ExtensionId}' updated.");
};

host.ExtensionCatalog.LoadInstalledExtensions();
var installed = host.ExtensionCatalog.GetInstalledExtensions();
Console.WriteLine($"Loaded {installed.Count} installed extension(s).");

var response = await host.QueryExtensionsAsync(new ExtensionQueryDTO
{
Platform = TargetPlatform.Windows | TargetPlatform.Linux,
HostVersion = options.HostVersion,
Status = true,
PageNumber = 1,
PageSize = 100
});

if (response?.Body == null) return;

var toUpdate = new List<string>();
foreach (var ext in response.Body.Items)
{
var local = host.ExtensionCatalog.GetInstalledExtensionById(ext.Id);
if (local == null) continue;

var meta = new ExtensionMetadata
{
MinHostVersion = ext.MinHostVersion,
MaxHostVersion = ext.MaxHostVersion
};

if (!host.IsExtensionCompatible(meta))
{
Console.WriteLine($"[INCOMPATIBLE] {ext.DisplayName}");
continue;
}

if (Version.TryParse(ext.Version, out var remoteVer) &&
Version.TryParse(local.Version, out var localVer) &&
remoteVer > localVer && host.IsAutoUpdateEnabled(ext.Id))
{
Console.WriteLine($"[UPDATE] {ext.DisplayName}: {local.Version}{ext.Version}");
toUpdate.Add(ext.Id);
}
}

if (toUpdate.Any())
{
Console.WriteLine($"\nUpdating {toUpdate.Count} extension(s)...");
var results = await host.UpdateExtensionsAsync(toUpdate);
var succeeded = results.Count(r => r.Value);
var failed = results.Count(r => !r.Value);
Console.WriteLine($"Done: {succeeded} succeeded, {failed} failed.");
}
else
{
Console.WriteLine("All extensions up to date.");
}

6. Global Configuration

Server API Contract

Query Endpoint:

GET {ServerUrl}/Query
Content-Type: application/json
Authorization: {Scheme} {Token}

Body: ExtensionQueryDTO (JSON)

Note: Current implementation uses GET + JSON Body, which is non-standard HTTP. If going through proxies/gateways, may need to switch to POST or query string.

Download Endpoint:

GET {ServerUrl}/Download/{extensionId}
Authorization: {Scheme} {Token}
Range: bytes={existingLength}-

Package Structure

Recommended package name: {Name}_{Version}.zip

report-extension_1.0.0.zip
├── manifest.json
├── extension.dll
├── extension.deps.json
├── README.md
├── CHANGELOG.md
└── LICENSE.txt

Auto-Update Policy Priority

Per-extension setting > Global setting > Default (false)

Platform Compatibility Reference

Enum ValueCodeDescription
TargetPlatform.None0Matches no platform
TargetPlatform.Windows1Windows
TargetPlatform.Linux2Linux
TargetPlatform.MacOS4macOS
TargetPlatform.All7All platforms (Windows | Linux | MacOS)