OElite.Common.Hosting 5.0.9-develop.472

OElite.Common.Hosting

Generic application hosting and lifecycle management for OElite applications.

NuGet Version .NET

Overview

OElite.Common.Hosting provides streamlined application startup and lifecycle management for OElite applications. It eliminates the need for manual service registration, configuration setup, and middleware pipeline configuration by providing automatic discovery and configuration based on application type detection.

This package provides the generic hosting foundation used by all OElite applications, including console apps, background services, and web applications. For web-specific features, use OElite.Common.Hosting.AspNetCore.

Features

🚀 Automatic Service Discovery

  • Service Registration: Auto-discovery of IOEliteService implementations
  • Repository Registration: Auto-discovery of IDataRepository<> implementations
  • Options Registration: Auto-discovery of IOEliteOptions implementations
  • Bootstrap Providers: Auto-discovery of IBootstrapProvider implementations

🔧 Intelligent Configuration Management

  • App Type Detection: Automatic detection of OElite app types (Platform, Kortex, Obelisk, etc.)
  • Configuration Composition: Merges local and Kortex remote configurations
  • DbCentre Resolution: Automatic database center selection based on app configuration
  • Path Resolution: Standardized path management across all app types

🔄 Advanced Lifecycle Management

  • Bootstrap Orchestration: Ordered execution of initialization providers
  • Background Services: Automatic hosted service registration
  • Model Transformation: Auto-discovery and registration of model transformers
  • Graceful Shutdown: Proper resource disposal and cleanup

💾 Integrated Caching Strategy

  • Redis Cache: Distributed caching using OElite.Restme.Redis
  • Memory Cache: In-memory caching with OElite.Restme.Memory
  • Fallback Logic: Automatic fallback to memory cache when Redis unavailable

Quick Start

1. Installation

dotnet add package OElite.Common.Hosting

2. Basic Console Application

using OElite.Common.Hosting.Bootstrap;
using OElite.Common.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public class MyAppConfig : BaseAppConfig
{
    public override string? DbCentreFullClassName =>
        "MyApp.Data.MyDbCentre, MyApp.Data";

    public MyAppConfig(string jsonConfig, ILogger? logger = null)
        : base(OeAppType.GeneralConsoleApp, jsonConfig, logger)
    {
    }
}

public class Program
{
    public static async Task Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: false)
            .AddEnvironmentVariables()
            .Build();

        var jsonConfig = File.ReadAllText("appsettings.json");
        var appConfig = new MyAppConfig(jsonConfig);

        var host = Host.CreateDefaultBuilder(args)
            .ConfigureServices(services =>
            {
                // Add OElite hosting with automatic service discovery
                services.AddOeApp(configuration, appConfig, options =>
                {
                    // Optional: Configure service discovery
                    options.IncludeAssemblyPrefixes.Add("MyApp");
                    options.ExcludeNamespacePrefixes.Add("MyApp.Legacy");
                });
            })
            .Build();

        // Execute bootstrap providers
        await host.Services.ExecuteBootstrapProviderAsync();

        await host.RunAsync();
    }
}

3. Background Service Application

using OElite.Common.Hosting.Lifecycle;

public class DataSyncBackgroundService : OEliteBackgroundService
{
    private readonly ILogger<DataSyncBackgroundService> _logger;
    private readonly IMyDataService _dataService;

    public DataSyncBackgroundService(
        ILogger<DataSyncBackgroundService> logger,
        IMyDataService dataService)
    {
        _logger = logger;
        _dataService = dataService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await _dataService.SynchronizeDataAsync();
                await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Data synchronization failed");
                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
    }
}

// The service will be automatically discovered and registered as a hosted service

Advanced Configuration

Service Discovery Options

services.AddOeApp(configuration, appConfig, options =>
{
    // Assembly filtering
    options.IncludeAssemblyPrefixes.Add("MyApp");
    options.IncludeAssemblyPrefixes.Add("MyCompany.Shared");
    options.ExcludeAssemblyPrefixes.Add("MyApp.ThirdParty");

    // Namespace filtering
    options.IncludeNamespacePrefixes.Add("MyApp.Services");
    options.IncludeNamespacePrefixes.Add("MyApp.Repositories");
    options.ExcludeNamespacePrefixes.Add("MyApp.Legacy");

    // Specific type exclusions
    options.ExcludeServiceTypes.Add(typeof(LegacyService));
    options.ExcludeRepositoryTypes.Add(typeof(DeprecatedRepository));

    // Custom type filters
    options.ServiceTypeFilter = type =>
        !type.Name.Contains("Test") && !type.Name.Contains("Mock");

    options.RepositoryTypeFilter = type =>
        type.Namespace?.Contains("Production") == true;
});

Bootstrap Provider Implementation

using OElite.Common.Hosting.Bootstrap;

public class DatabaseMigrationBootstrap : IBootstrapProvider
{
    public string Name => "Database Migration";
    public int Priority => 10; // Lower numbers run first

    private readonly ILogger<DatabaseMigrationBootstrap> _logger;
    private readonly IMyDbService _dbService;

    public DatabaseMigrationBootstrap(
        ILogger<DatabaseMigrationBootstrap> logger,
        IMyDbService dbService)
    {
        _logger = logger;
        _dbService = dbService;
    }

    public async Task<bool> InitializeAsync()
    {
        try
        {
            _logger.LogInformation("Running database migrations...");
            await _dbService.MigrateAsync();
            _logger.LogInformation("Database migrations completed successfully");
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Database migration failed");
            return false; // Return false to indicate failure
        }
    }
}

// This will be automatically discovered and executed during startup

Model Transformer Registration

using OElite.Common.ModelTransformation;

public class ProductApiTransformer : IModelTransformer
{
    public Type EntityType => typeof(Product);
    public Type CollectionType => typeof(List<Product>);
    public int Priority => 10;

    public bool CanHandle(TransformationContext context)
    {
        return context.TargetFormat == "api";
    }

    public object? TransformObject(object obj, TransformationContext context)
    {
        if (obj is Product product)
        {
            return new ProductApiModel
            {
                Id = product.Id,
                Name = product.Name,
                Price = product.Price
                // Transform to API format
            };
        }
        return null;
    }

    // Implement remaining interface members...
}

// The transformer will be automatically discovered and registered

Application Types

OElite.Common.Hosting supports all standard OElite application types:

public enum OeAppType
{
    Platform,              // Platform core services
    Kortex,               // Kortex proxy and management services
    Obelisk,              // Email server applications
    OeSterling,           // OeSterling business services
    GeneralWebApp,        // Web applications (use with AspNetCore)
    GeneralWebApi,        // API applications (use with AspNetCore)
    GeneralConsoleApp,    // Console applications
    GeneralMobileApp      // Mobile application backends
}

Configuration Structure by App Type

Each app type gets automatic configuration path resolution:

{
  "oelite": {
    "data": {
      "mongodb": {
        "platform": "mongodb://localhost:27017/platform",
        "kortex": "mongodb://localhost:27017/kortex",
        "obelisk": "mongodb://localhost:27017/obelisk",
        "app": "mongodb://localhost:27017/myapp"
      },
      "redis": {
        "platform": "redis://localhost:6379/0",
        "kortex": "redis://localhost:6379/1",
        "obelisk": "redis://localhost:6379/2",
        "app": "redis://localhost:6379/3"
      }
    }
  }
}

Bootstrap System

The bootstrap system executes initialization providers in priority order during application startup.

Bootstrap Provider Lifecycle

  1. Discovery: All IBootstrapProvider implementations are auto-discovered
  2. Ordering: Providers are sorted by priority (lower numbers first)
  3. Execution: Each provider's InitializeAsync() method is called
  4. Error Handling: Failed providers are logged but don't stop the process

Built-in Bootstrap Providers

  • DbBootstrapService: Initializes MongoDB collections and indexes
  • TransformerAutoRegistrationService: Registers discovered model transformers
  • KortexConfigurationService: Loads remote configuration from Kortex

Getting Bootstrap Information

// Get information about registered bootstrap providers
var bootstrapInfo = serviceProvider.GetBootstrapProviders();

foreach (var provider in bootstrapInfo)
{
    Console.WriteLine($"Provider: {provider.Name}, Priority: {provider.Priority}");
}

Path Resolution System

Built-in Path Resolver

using OElite.Common.Hosting.Infrastructure;

// The default path resolver is automatically registered
public class MyService
{
    private readonly IOElitePathResolver _pathResolver;

    public MyService(IOElitePathResolver pathResolver)
    {
        _pathResolver = pathResolver;
    }

    public void DoWork()
    {
        // Get resolved paths
        var dataPath = _pathResolver.ResolvePath(OElitePathType.Data, "products.json");
        var logPath = _pathResolver.ResolvePath(OElitePathType.Logs, "api.log");

        // Ensure directories exist
        var cacheDir = _pathResolver.EnsureDirectory(OElitePathType.Cache, "images");

        // Check file existence
        if (_pathResolver.FileExists(OElitePathType.Config, "settings.json"))
        {
            // Process file
        }
    }
}

Custom Path Resolver

public class CustomPathResolver : IOElitePathResolver
{
    private readonly string _baseDirectory;

    public CustomPathResolver(string baseDirectory)
    {
        _baseDirectory = baseDirectory;
    }

    public string ResolvePath(OElitePathType pathType, string relativePath = "")
    {
        var subDir = pathType switch
        {
            OElitePathType.Data => "data",
            OElitePathType.Logs => "logs",
            OElitePathType.Cache => "cache",
            OElitePathType.Temp => "temp",
            OElitePathType.Config => "config",
            _ => "misc"
        };

        return Path.Combine(_baseDirectory, subDir, relativePath);
    }

    public string EnsureDirectory(OElitePathType pathType, string relativePath = "")
    {
        var fullPath = ResolvePath(pathType, relativePath);
        Directory.CreateDirectory(fullPath);
        return fullPath;
    }

    public bool FileExists(OElitePathType pathType, string relativePath)
    {
        return File.Exists(ResolvePath(pathType, relativePath));
    }
}

// Register custom path resolver
services.AddSingleton<IOElitePathResolver>(new CustomPathResolver("/app/data"));

Caching Integration

Automatic Cache Configuration

// Redis cache is automatically configured if connection string is available
var appConfig = new MyAppConfig(jsonConfig);

// If Redis is configured, services get:
// - IMemoryCache -> OElite.Restme memory cache
// - IDistributedCache -> OElite.Restme Redis cache

// If Redis is not configured, services get:
// - IMemoryCache -> OElite.Restme memory cache
// - IDistributedCache -> OElite.Restme memory cache (fallback)

Using Caches in Services

public class ProductService : IOEliteService
{
    private readonly IDistributedCache _distributedCache;
    private readonly IMemoryCache _memoryCache;

    public ProductService(IDistributedCache distributedCache, IMemoryCache memoryCache)
    {
        _distributedCache = distributedCache;
        _memoryCache = memoryCache;
    }

    public async Task<Product?> GetProductAsync(string productId)
    {
        // Try memory cache first (fastest)
        if (_memoryCache.TryGetValue($"product:{productId}", out Product? cached))
        {
            return cached;
        }

        // Try distributed cache (Redis)
        var distributedValue = await _distributedCache.GetStringAsync($"product:{productId}");
        if (distributedValue != null)
        {
            var product = JsonSerializer.Deserialize<Product>(distributedValue);
            // Store in memory cache for next time
            _memoryCache.Set($"product:{productId}", product, TimeSpan.FromMinutes(5));
            return product;
        }

        // Load from database and cache
        var dbProduct = await LoadFromDatabaseAsync(productId);
        if (dbProduct != null)
        {
            _memoryCache.Set($"product:{productId}", dbProduct, TimeSpan.FromMinutes(5));
            await _distributedCache.SetStringAsync($"product:{productId}",
                JsonSerializer.Serialize(dbProduct),
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
                });
        }

        return dbProduct;
    }
}

Service and Repository Patterns

Service Implementation

public interface IProductService : IOEliteService
{
    Task<Product?> GetProductAsync(string productId);
    Task<List<Product>> GetProductsAsync(int skip, int take);
}

public class ProductService : IProductService
{
    private readonly IProductRepository _repository;
    private readonly ILogger<ProductService> _logger;

    public ProductService(IProductRepository repository, ILogger<ProductService> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task<Product?> GetProductAsync(string productId)
    {
        _logger.LogDebug("Getting product {ProductId}", productId);
        return await _repository.GetByIdAsync(productId);
    }

    public async Task<List<Product>> GetProductsAsync(int skip, int take)
    {
        _logger.LogDebug("Getting products: skip={Skip}, take={Take}", skip, take);
        return await _repository.GetRangeAsync(skip, take);
    }
}

// Automatically registered as singleton with IProductService interface

Repository Implementation

public interface IProductRepository : IDataRepository<Product>
{
    Task<List<Product>> GetByCategoryAsync(string categoryId);
}

public class ProductRepository : IProductRepository
{
    private readonly DbCentre _dbCentre;

    public ProductRepository(DbCentre dbCentre)
    {
        _dbCentre = dbCentre;
    }

    public async Task<Product?> GetByIdAsync(string id)
    {
        return await _dbCentre.Products.GetByIdAsync(id);
    }

    public async Task<List<Product>> GetByCategoryAsync(string categoryId)
    {
        return await _dbCentre.Products.GetManyAsync(p => p.CategoryId == categoryId);
    }

    // Other IDataRepository<Product> implementations...
}

// Automatically registered as singleton with IProductRepository interface

Options Pattern Implementation

public class EmailOptions : IOEliteOptions
{
    public string SmtpServer { get; set; } = "";
    public int SmtpPort { get; set; } = 587;
    public string Username { get; set; } = "";
    public string Password { get; set; } = "";
    public bool EnableSsl { get; set; } = true;
}

// Automatically registered and can be injected as EmailOptions
public class EmailService : IOEliteService
{
    private readonly EmailOptions _options;

    public EmailService(EmailOptions options)
    {
        _options = options;
    }
}

DbCentre Integration

Custom DbCentre Implementation

// In your data project
public class MyAppDbCentre : DbCentre
{
    public IMongoDbCollection<Product> Products { get; }
    public IMongoDbCollection<Customer> Customers { get; }

    public MyAppDbCentre(IAppConfig appConfig) : base(appConfig)
    {
        Products = GetCollection<Product>();
        Customers = GetCollection<Customer>();
    }
}

// In your app config
public class MyAppConfig : BaseAppConfig
{
    public override string? DbCentreFullClassName =>
        "MyApp.Data.MyAppDbCentre, MyApp.Data";

    public MyAppConfig(string jsonConfig)
        : base(OeAppType.GeneralWebApp, jsonConfig)
    {
    }
}

Using DbCentre in Services

public class ProductService : IOEliteService
{
    private readonly MyAppDbCentre _dbCentre;

    public ProductService(MyAppDbCentre dbCentre)
    {
        _dbCentre = dbCentre; // Specific typed DbCentre
    }

    public async Task<Product?> GetProductAsync(string productId)
    {
        return await _dbCentre.Products.GetByIdAsync(productId);
    }
}

// Or use the base DbCentre if you don't need typed collections
public class GenericService : IOEliteService
{
    private readonly DbCentre _dbCentre;

    public GenericService(DbCentre dbCentre)
    {
        _dbCentre = dbCentre; // Base DbCentre
    }
}

Performance Optimizations

Assembly Loading Strategy

  • Loads only OElite.* assemblies to reduce discovery time
  • Uses cached assembly lists to avoid repeated file system access
  • Handles ReflectionTypeLoadException gracefully

Service Registration Efficiency

  • Services are registered as singletons by default for best performance
  • Interface registration is optimized to avoid duplicate registrations
  • Hosted services use proper ASP.NET Core registration patterns

Memory Management

  • Shared Redis connections prevent connection pool exhaustion
  • Bootstrap providers are disposed properly after initialization
  • Type discovery results are not cached to allow runtime assembly loading

Troubleshooting

Common Issues

Services Not Being Discovered

// Check assembly and namespace filters
services.AddOeApp(configuration, appConfig, options =>
{
    // Make sure your assembly is included
    options.IncludeAssemblyPrefixes.Add("YourApp");

    // Check for exclusion filters
    options.ExcludeNamespacePrefixes.Clear(); // Remove if blocking your services
});

Bootstrap Provider Not Running

// Ensure you call ExecuteBootstrapProviderAsync after host building
var host = hostBuilder.Build();
await host.Services.ExecuteBootstrapProviderAsync(); // Required!
await host.RunAsync();

DbCentre Not Resolving

public override string? DbCentreFullClassName =>
    "Correct.Namespace.YourDbCentre, Correct.AssemblyName";

// Verify the type can be loaded
var type = Type.GetType("Correct.Namespace.YourDbCentre, Correct.AssemblyName");
if (type == null)
{
    // Fix the type name or assembly reference
}

Cache Configuration Issues

// Check Redis connection string format
"oelite:data:redis:app": "localhost:6379"                    // ✅ Correct
"oelite:data:redis:app": "redis://localhost:6379"           // ✅ Correct
"oelite:data:redis:app": "redis://user:pass@host:6379/0"    // ✅ Correct
"oelite:data:redis": "localhost:6379"                       // ❌ Wrong path

Integration Examples

Console Application with Background Services

class Program
{
    static async Task Main(string[] args)
    {
        var jsonConfig = File.ReadAllText("appsettings.json");
        var appConfig = new MyAppConfig(jsonConfig);

        var host = Host.CreateDefaultBuilder(args)
            .ConfigureServices(services =>
            {
                services.AddOeApp(
                    new ConfigurationBuilder()
                        .AddJsonFile("appsettings.json")
                        .Build(),
                    appConfig);
            })
            .Build();

        await host.Services.ExecuteBootstrapProviderAsync();
        await host.RunAsync();
    }
}

Unit Testing with OElite.Common.Hosting

public class ServiceTests
{
    [Test]
    public async Task TestServiceDiscovery()
    {
        var services = new ServiceCollection();
        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new[]
            {
                new KeyValuePair<string, string>("oelite:data:mongodb:app", "mongodb://localhost:27017/test")
            })
            .Build();

        var appConfig = new TestAppConfig(configuration);

        services.AddOeApp(configuration, appConfig, options =>
        {
            // Only include test assemblies
            options.IncludeAssemblyPrefixes.Clear();
            options.IncludeAssemblyPrefixes.Add("MyApp.Tests");
        });

        var provider = services.BuildServiceProvider();

        // Verify services are registered
        var productService = provider.GetService<IProductService>();
        Assert.IsNotNull(productService);
    }
}

Version History

  • 5.0.9: Current version with .NET 10.0 support
  • Enhanced service discovery with filtering options
  • Improved bootstrap provider orchestration
  • Added comprehensive caching integration
  • Optimized assembly loading and type discovery
  • OElite.Common: Core shared components and infrastructure
  • OElite.Common.Hosting.AspNetCore: Web application hosting extensions
  • OElite.Services: Business logic services that integrate with this hosting
  • OElite.Data: Data access components with DbCentre patterns
  • OElite.Restme.Hosting: REST client and caching integration

License

Copyright © OElite Limited. All rights reserved.

Support

For support and documentation, visit https://www.oelite.com

Showing the top 20 packages that depend on OElite.Common.Hosting.

Packages Downloads
OElite.Common.Hosting.AspNetCore
Package Description
96
OElite.Common.Hosting.AspNetCore
Package Description
32
OElite.Common.Hosting.AspNetCore
Package Description
16
OElite.Common.Hosting.AspNetCore
Package Description
14
OElite.Common.Hosting.AspNetCore
Package Description
13
OElite.Common.Hosting.AspNetCore
Package Description
11
OElite.Common.Hosting.AspNetCore
Package Description
8
OElite.Common.Hosting.AspNetCore
Package Description
7
OElite.Common.Hosting.AspNetCore
Package Description
6
OElite.Common.Hosting.AspNetCore
Package Description
5

Version Downloads Last updated
5.0.9-develop.511 5 11/26/2025
5.0.9-develop.510 3 11/26/2025
5.0.9-develop.509 4 11/23/2025
5.0.9-develop.480 4 11/17/2025
5.0.9-develop.478 3 11/17/2025
5.0.9-develop.477 4 11/17/2025
5.0.9-develop.476 5 11/17/2025
5.0.9-develop.472 4 11/15/2025
5.0.9-develop.471 4 11/15/2025
5.0.9-develop.470 4 11/15/2025
5.0.9-develop.462 4 11/14/2025
5.0.9-develop.448 5 11/11/2025
5.0.9-develop.446 4 11/11/2025
5.0.9-develop.443 5 11/11/2025
5.0.9-develop.441 4 11/09/2025
5.0.9-develop.415 9 10/28/2025
5.0.9-develop.412 2 10/27/2025
5.0.9-develop.411 3 10/27/2025
5.0.9-develop.410 4 10/27/2025
5.0.9-develop.409 4 10/27/2025
5.0.9-develop.402 16 10/26/2025
5.0.9-develop.399 15 10/26/2025
5.0.9-develop.394 14 10/25/2025
5.0.9-develop.391 12 10/25/2025
5.0.9-develop.389 8 10/25/2025
5.0.9-develop.376 33 10/25/2025
5.0.9-develop.374 11 10/24/2025
5.0.9-develop.373 7 10/24/2025
5.0.9-develop.372 8 10/24/2025
5.0.9-develop.259 97 10/20/2025
5.0.9-develop.258 8 10/20/2025
5.0.9-develop.244 7 10/19/2025
5.0.9-develop.240 5 10/18/2025
5.0.9-develop.239 6 10/18/2025
5.0.9-develop.238 5 10/18/2025
5.0.9-develop.236 5 10/18/2025
5.0.9-develop.235 6 10/18/2025
5.0.9-develop.234 6 10/17/2025