From 5e9a5acd439fffe5a55a8fbc726c51a4909ee5db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:08:43 +0000 Subject: [PATCH 1/2] Initial plan From 1b9e4fd92ba42807f0d5ee307e12d9f814eaf786 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:41:22 +0000 Subject: [PATCH 2/2] Add comprehensive .NET Core migration examples with concrete code samples Co-authored-by: geofranzi <15946467+geofranzi@users.noreply.github.com> --- .../AppConfiguration.NetCore.cs | 391 +++++++++++++ .../BExIS.Web.Shell.NetCore.csproj | 179 ++++++ Migration-Examples/Migration-Checklist.md | 234 ++++++++ Migration-Examples/README.md | 49 ++ .../SessionManagement.NetCore.cs | 548 ++++++++++++++++++ Migration-Examples/Startup.cs | 428 ++++++++++++++ Migration-Examples/appsettings.json | 199 +++++++ 7 files changed, 2028 insertions(+) create mode 100644 Migration-Examples/AppConfiguration.NetCore.cs create mode 100644 Migration-Examples/BExIS.Web.Shell.NetCore.csproj create mode 100644 Migration-Examples/Migration-Checklist.md create mode 100644 Migration-Examples/README.md create mode 100644 Migration-Examples/SessionManagement.NetCore.cs create mode 100644 Migration-Examples/Startup.cs create mode 100644 Migration-Examples/appsettings.json diff --git a/Migration-Examples/AppConfiguration.NetCore.cs b/Migration-Examples/AppConfiguration.NetCore.cs new file mode 100644 index 000000000..a5cc78322 --- /dev/null +++ b/Migration-Examples/AppConfiguration.NetCore.cs @@ -0,0 +1,391 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Globalization; +using System.IO; +using System.Security.Principal; +using System.Threading; + +namespace Vaiona.Utils.Cfg +{ + public class Constants + { + public static readonly string AnonymousUser = @"Anonymous"; + public static readonly string EveryoneRole = "Everyone"; + } + + /// + /// .NET Core version of AppConfiguration using dependency injection instead of static access. + /// Replaces System.Web dependencies with ASP.NET Core equivalents. + /// + public class AppConfiguration + { + private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _environment; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IServiceProvider _serviceProvider; + + public AppConfiguration( + IConfiguration configuration, + IWebHostEnvironment environment, + IHttpContextAccessor httpContextAccessor, + IServiceProvider serviceProvider) + { + _configuration = configuration; + _environment = environment; + _httpContextAccessor = httpContextAccessor; + _serviceProvider = serviceProvider; + } + + public bool IsWebContext => _httpContextAccessor.HttpContext != null; + + public bool IsPostBack(HttpRequest request) + { + if (request.Headers.Referer.Count == 0) return false; + if (!Uri.TryCreate(request.Headers.Referer.ToString(), UriKind.Absolute, out Uri refererUri)) return false; + + bool isPost = "POST".Equals(request.Method, StringComparison.CurrentCultureIgnoreCase); + bool isSameUrl = request.Path.Equals(refererUri.AbsolutePath, StringComparison.CurrentCultureIgnoreCase); + + return isPost && isSameUrl; + } + + public string DefaultApplicationConnection => _configuration.GetConnectionString("ApplicationServices"); + + public string DefaultCulture => _configuration["DefaultCulture"] ?? "en-US"; + + public string DatabaseMappingFile => _configuration["DatabaseMappingFile"] ?? string.Empty; + + public string DatabaseDialect => _configuration["DatabaseDialect"] ?? "DB2Dialect"; + + public bool AutoCommitTransactions => _configuration.GetValue("AutoCommitTransactions", false); + + public string IoCProviderTypeInfo => _configuration["IoCProviderTypeInfo"] ?? string.Empty; + + public string AppRoot + { + get + { + // Try configuration first, then fall back to content root + string path = _configuration["ApplicationRoot"]; + return string.IsNullOrWhiteSpace(path) ? _environment.ContentRootPath : path; + } + } + + public string AreasPath + { + get + { + // Use WebRootPath instead of HostingEnvironment.MapPath + string path = Path.Combine(_environment.WebRootPath ?? _environment.ContentRootPath, "Areas"); + if (Directory.Exists(path)) + { + return path; + } + return Path.Combine(AppRoot, "Areas"); + } + } + + /// + /// DataPath shows the root folder containing business data. + /// Uses IConfiguration instead of ConfigurationManager.AppSettings + /// + public string DataPath + { + get + { + string path = _configuration["DataPath"]; + return string.IsNullOrWhiteSpace(path) ? Path.Combine(AppRoot, "Data") : path; + } + } + + private string _workspaceRootPath = string.Empty; + + public string WorkspaceRootPath + { + get + { + if (!string.IsNullOrWhiteSpace(_workspaceRootPath)) + return _workspaceRootPath; + + string path = _configuration["WorkspacePath"] ?? string.Empty; + int level = 0; + + if (string.IsNullOrWhiteSpace(path)) + level = 0; + else if (path.Contains(@"..\")) + { + level = path.Split(@"\".ToCharArray()).Length - 1; + } + else + { + _workspaceRootPath = path; + return _workspaceRootPath; + } + + // Find directory without Web.config (use appsettings.json as indicator for web root) + DirectoryInfo di = new DirectoryInfo(AppRoot); + while (di.GetFiles("appsettings.json").Length >= 1) + di = di.Parent; + + for (int i = 1; i < level; i++) + { + di = di.Parent; + } + + _workspaceRootPath = Path.Combine(di.FullName, "Workspace"); + return _workspaceRootPath; + } + } + + public string WorkspaceComponentRoot => Path.Combine(WorkspaceRootPath, "Components"); + public string WorkspaceModulesRoot => Path.Combine(WorkspaceRootPath, "Modules"); + public string WorkspaceGeneralRoot => Path.Combine(WorkspaceRootPath, "General"); + public string WorkspaceTenantsRoot => Path.Combine(WorkspaceRootPath, "Tenants"); + + public string GetModuleWorkspacePath(string moduleName) => Path.Combine(WorkspaceModulesRoot, moduleName); + public string GetComponentWorkspacePath(string componentName) => Path.Combine(WorkspaceComponentRoot, componentName); + + public bool UseSchemaInDatabaseGeneration => _configuration.GetValue("UseSchemaInDatabaseGeneration", false); + public bool CreateDatabase => _configuration.GetValue("CreateDatabase", false); + public bool ShowQueries => _configuration.GetValue("ShowQueries", false); + + private bool? _cacheQueryResults = null; + public bool CacheQueryResults + { + get + { + if (_cacheQueryResults.HasValue) + return _cacheQueryResults.Value; + + _cacheQueryResults = _configuration.GetValue("CacheQueryResults", false); + return _cacheQueryResults.Value; + } + } + + private string _themesPath; + public string ThemesPath + { + get + { + if (!string.IsNullOrEmpty(_themesPath)) + return _themesPath; + + _themesPath = _configuration["ThemesPath"] ?? "~/Themes"; + return _themesPath; + } + } + + private string _defaultThemeName; + public string DefaultThemeName + { + get + { + if (!string.IsNullOrEmpty(_defaultThemeName)) + return _defaultThemeName; + + _defaultThemeName = _configuration["DefaultThemeName"] ?? "Default"; + return _defaultThemeName; + } + } + + private string _activeLayoutName; + public string ActiveLayoutName + { + get + { + if (!string.IsNullOrEmpty(_activeLayoutName)) + return _activeLayoutName; + + _activeLayoutName = _configuration["ActiveLayoutName"] ?? "_Layout"; + return _activeLayoutName; + } + } + + public bool ThrowErrorWhenParialContentNotFound => _configuration.GetValue("ThrowErrorWhenParialContentNotFound", false); + + // Replace HttpContext.Current with IHttpContextAccessor + public HttpContext HttpContext => _httpContextAccessor.HttpContext; + + public Thread CurrentThread => Thread.CurrentThread; + public CultureInfo UICulture => Thread.CurrentThread.CurrentUICulture; + public CultureInfo Culture => Thread.CurrentThread.CurrentCulture; + public DateTime UTCDateTime => DateTime.UtcNow; + public DateTime DateTime => System.DateTime.Now; + + public byte[] GetUTCAsBytes() => System.Text.Encoding.UTF8.GetBytes(UTCDateTime.ToBinary().ToString()); + + public Uri CurrentRequestURL + { + get + { + try + { + var request = _httpContextAccessor.HttpContext?.Request; + if (request != null) + { + return new Uri($"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}"); + } + return new Uri("http://NotFound.htm"); + } + catch + { + return new Uri("http://NotFound.htm"); + } + } + } + + public IPrincipal User + { + get + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext?.User?.Identity == null || !httpContext.User.Identity.IsAuthenticated) + { + return CreateUser(Constants.AnonymousUser, Constants.EveryoneRole); + } + return httpContext.User; + } + } + + public bool TryGetCurrentUser(ref string userName) + { + try + { + var httpContext = _httpContextAccessor.HttpContext; + userName = httpContext?.User?.Identity?.Name ?? string.Empty; + return httpContext?.User?.Identity?.IsAuthenticated ?? false; + } + catch + { + return false; + } + } + + internal static IPrincipal CreateUser(string userName, string roleName) + { + // Simplified user creation - in real implementation, integrate with ASP.NET Core Identity + return new GenericPrincipal(new GenericIdentity(userName), new[] { roleName }); + } + + // Logging properties with caching + private bool? _isLoggingEnable = null; + public bool IsLoggingEnable + { + get + { + if (_isLoggingEnable.HasValue) + return _isLoggingEnable.Value; + + _isLoggingEnable = _configuration.GetValue("IsLoggingEnable", false); + return _isLoggingEnable.Value; + } + } + + private bool? _isPerformanceLoggingEnable = null; + public bool IsPerformanceLoggingEnable + { + get + { + if (_isPerformanceLoggingEnable.HasValue) + return _isPerformanceLoggingEnable.Value; + + _isPerformanceLoggingEnable = _configuration.GetValue("IsPerformanceLoggingEnable", false); + return _isPerformanceLoggingEnable.Value; + } + } + + private bool? _isDiagnosticLoggingEnable = null; + public bool IsDiagnosticLoggingEnable + { + get + { + if (_isDiagnosticLoggingEnable.HasValue) + return _isDiagnosticLoggingEnable.Value; + + _isDiagnosticLoggingEnable = _configuration.GetValue("IsDiagnosticLoggingEnable", false); + return _isDiagnosticLoggingEnable.Value; + } + } + + private bool? _isCallLoggingEnable = null; + public bool IsCallLoggingEnable + { + get + { + if (_isCallLoggingEnable.HasValue) + return _isCallLoggingEnable.Value; + + _isCallLoggingEnable = _configuration.GetValue("IsCallLoggingEnable", false); + return _isCallLoggingEnable.Value; + } + } + + private bool? _isExceptionLoggingEnable = null; + public bool IsExceptionLoggingEnable + { + get + { + if (_isExceptionLoggingEnable.HasValue) + return _isExceptionLoggingEnable.Value; + + _isExceptionLoggingEnable = _configuration.GetValue("IsExceptionLoggingEnable", false); + return _isExceptionLoggingEnable.Value; + } + } + + private bool? _isDataLoggingEnable = null; + public bool IsDataLoggingEnable + { + get + { + if (_isDataLoggingEnable.HasValue) + return _isDataLoggingEnable.Value; + + _isDataLoggingEnable = _configuration.GetValue("IsDataLoggingEnable", false); + return _isDataLoggingEnable.Value; + } + } + + private string _tenantId = ""; + public string TenantId + { + get + { + if (!string.IsNullOrWhiteSpace(_tenantId)) + return _tenantId; + + _tenantId = _configuration["TenantId"] ?? string.Empty; + return _tenantId; + } + } + + private int? _conversationIsolationLevel = null; + public int ConversationIsolationLevel + { + get + { + if (_conversationIsolationLevel.HasValue) + return _conversationIsolationLevel.Value; + + _conversationIsolationLevel = _configuration.GetValue("ConversationIsolationLevel", 2); + return _conversationIsolationLevel.Value; + } + } + } + + /// + /// Extension methods for registering AppConfiguration in .NET Core DI container + /// + public static class AppConfigurationExtensions + { + public static IServiceCollection AddAppConfiguration(this IServiceCollection services) + { + services.AddHttpContextAccessor(); + services.AddSingleton(); + return services; + } + } +} \ No newline at end of file diff --git a/Migration-Examples/BExIS.Web.Shell.NetCore.csproj b/Migration-Examples/BExIS.Web.Shell.NetCore.csproj new file mode 100644 index 000000000..4c8fdde09 --- /dev/null +++ b/Migration-Examples/BExIS.Web.Shell.NetCore.csproj @@ -0,0 +1,179 @@ + + + + net6.0 + enable + enable + BExIS.Web.Shell + BExIS.Web.Shell + + + InProcess + AspNetCoreModuleV2 + + + true + + + true + + + false + + + + + + full + true + DEBUG;TRACE + false + + + + pdbonly + false + TRACE + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Migration-Examples/Migration-Checklist.md b/Migration-Examples/Migration-Checklist.md new file mode 100644 index 000000000..d851fa7bd --- /dev/null +++ b/Migration-Examples/Migration-Checklist.md @@ -0,0 +1,234 @@ +# BEXIS2 .NET Core Migration Checklist + +This document provides a comprehensive checklist for migrating BEXIS2 from .NET Framework 4.8 to .NET 6 for Linux compatibility. + +## Phase 1: Infrastructure Migration (Weeks 1-3) + +### Core Components Migration + +#### Week 1: Configuration and Utilities +- [ ] **AppConfiguration.cs** - Replace with dependency injection pattern + - [ ] Replace `HttpContext.Current` with `IHttpContextAccessor` + - [ ] Replace `ConfigurationManager` with `IConfiguration` + - [ ] Replace `HostingEnvironment.MapPath` with `IWebHostEnvironment` + - [ ] Update path handling for cross-platform compatibility + - [ ] Add constructor injection for dependencies + - [ ] Test configuration loading from appsettings.json + +- [ ] **Project Files Conversion** + - [ ] Convert all 65+ projects from packages.config to PackageReference + - [ ] Update from .NET Framework 4.8 to .NET 6.0 + - [ ] Replace legacy project format with SDK-style projects + - [ ] Update NuGet package references to .NET 6 compatible versions + - [ ] Remove Windows-specific build configurations + +#### Week 2: Session and Context Management +- [ ] **Session Management** + - [ ] Replace `System.Web.SessionState` with ASP.NET Core session services + - [ ] Implement `ISessionManager` interface for type-safe session access + - [ ] Create session extension methods for backward compatibility + - [ ] Configure distributed session storage for scalability + - [ ] Test session persistence and timeout behavior + +- [ ] **HTTP Context Handling** + - [ ] Replace all static `HttpContext.Current` usage + - [ ] Implement `IHttpContextAccessor` injection throughout codebase + - [ ] Update request/response handling patterns + - [ ] Test cross-platform HTTP context behavior + +#### Week 3: Configuration System +- [ ] **Web.config to appsettings.json Migration** + - [ ] Convert 18+ Web.config files to appsettings.json format + - [ ] Create environment-specific configuration files + - [ ] Implement strongly-typed configuration classes + - [ ] Set up configuration validation and binding + - [ ] Test configuration loading in different environments + +### Testing Infrastructure Updates +- [ ] Update unit test projects to .NET 6 +- [ ] Replace MSTest/NUnit with xUnit if needed +- [ ] Update test configuration and mocking frameworks +- [ ] Create integration tests for configuration loading +- [ ] Set up test databases for Linux environment + +## Phase 2: Application Framework Migration (Weeks 4-7) + +### Week 4: MVC Framework Migration +- [ ] **ASP.NET MVC 5 to ASP.NET Core MVC** + - [ ] Replace `System.Web.Mvc` with `Microsoft.AspNetCore.Mvc` + - [ ] Update controller base classes and action filters + - [ ] Migrate custom model binders and value providers + - [ ] Update view engine configurations + - [ ] Test routing and action execution + +- [ ] **View Engine Updates** + - [ ] Replace custom view engines with view location expanders + - [ ] Update Razor view syntax for .NET Core + - [ ] Migrate HTML helpers to tag helpers where applicable + - [ ] Test view resolution and rendering + +### Week 5: Web API Migration +- [ ] **ASP.NET Web API 2 to ASP.NET Core Web API** + - [ ] Replace `System.Web.Http` with `Microsoft.AspNetCore.Mvc` + - [ ] Update API controller base classes + - [ ] Migrate custom message handlers to middleware + - [ ] Update serialization configuration + - [ ] Test API endpoints and data binding + +### Week 6: Authentication and Authorization +- [ ] **Security Framework Migration** + - [ ] Replace `System.Web.Security` with ASP.NET Core Identity + - [ ] Migrate custom authentication providers + - [ ] Update authorization policies and attributes + - [ ] Configure cookie authentication for compatibility + - [ ] Test user authentication and role management + +### Week 7: Dependency Injection and IoC +- [ ] **IoC Container Migration** + - [ ] Integrate existing IoC configuration with ASP.NET Core DI + - [ ] Replace Unity/Ninject registrations with built-in DI + - [ ] Update service lifetimes and scoping + - [ ] Test dependency resolution and injection + +## Phase 3: Application Lifecycle and Startup (Weeks 8-9) + +### Week 8: Global.asax to Startup.cs Migration +- [ ] **Application Lifecycle Events** + - [ ] Replace `Application_Start` with `Startup.ConfigureServices` + - [ ] Replace `Application_End` with hosted service shutdown + - [ ] Convert `Application_BeginRequest` to middleware + - [ ] Convert `Application_EndRequest` to middleware + - [ ] Convert `Application_Error` to exception handling middleware + - [ ] Convert `Session_Start` to session initialization middleware + +- [ ] **Middleware Pipeline Configuration** + - [ ] Set up middleware pipeline in correct order + - [ ] Configure CORS, authentication, and error handling + - [ ] Add custom middleware for BExIS-specific functionality + - [ ] Test middleware execution order and behavior + +### Week 9: Bundling and Optimization +- [ ] **Asset Management Migration** + - [ ] Replace `System.Web.Optimization` with WebOptimizer + - [ ] Update CSS and JavaScript bundling configuration + - [ ] Configure static file serving for wwwroot + - [ ] Test asset loading and optimization + +## Phase 4: Module Migration (Weeks 10-12) + +### Week 10: Core Modules +- [ ] **Primary Module Migration** + - [ ] Migrate DCM (Data Collection Module) + - [ ] Migrate DIM (Data Information Module) + - [ ] Migrate SAM (Security Administration Module) + - [ ] Update module routing and area configurations + - [ ] Test module functionality and integration + +### Week 11: Secondary Modules +- [ ] **Additional Module Migration** + - [ ] Migrate VIM (Visualization Module) + - [ ] Migrate BAM (Business Administration Module) + - [ ] Migrate DDM (Data Discovery Module) + - [ ] Update inter-module communication patterns + - [ ] Test module isolation and data sharing + +### Week 12: Third-party Dependencies +- [ ] **External Library Updates** + - [ ] Update Telerik UI controls for ASP.NET Core + - [ ] Migrate jQuery and JavaScript dependencies + - [ ] Update database drivers and ORM configurations + - [ ] Test all third-party integrations + +## Phase 5: Linux Deployment (Week 13) + +### Linux Environment Setup +- [ ] **Docker Configuration** + - [ ] Create Dockerfile for .NET 6 application + - [ ] Configure Linux-compatible file paths + - [ ] Set up database connections for Linux + - [ ] Configure logging for containerized environment + +- [ ] **Production Deployment** + - [ ] Set up reverse proxy (nginx/Apache) + - [ ] Configure SSL certificates and HTTPS + - [ ] Set up monitoring and health checks + - [ ] Configure backup and maintenance procedures + +### Performance and Security Testing +- [ ] **Load Testing** + - [ ] Test application performance under load + - [ ] Compare performance with .NET Framework version + - [ ] Optimize bottlenecks and memory usage + - [ ] Test session management under concurrent users + +- [ ] **Security Validation** + - [ ] Perform security scanning and penetration testing + - [ ] Validate authentication and authorization workflows + - [ ] Test CORS and API security configurations + - [ ] Verify data protection and privacy compliance + +## Critical Success Factors + +### Code Quality Gates +- [ ] All existing unit tests pass +- [ ] Integration tests validate core functionality +- [ ] Performance benchmarks meet requirements +- [ ] Security scans show no critical vulnerabilities +- [ ] All configuration settings are properly migrated + +### Documentation Updates +- [ ] Update deployment documentation for Linux +- [ ] Create troubleshooting guides for common issues +- [ ] Update development environment setup instructions +- [ ] Document breaking changes and migration notes +- [ ] Create rollback procedures + +### Training and Knowledge Transfer +- [ ] Train development team on .NET Core differences +- [ ] Create code review guidelines for .NET Core +- [ ] Document new development patterns and practices +- [ ] Establish monitoring and maintenance procedures + +## Risk Mitigation + +### High-Risk Areas Requiring Special Attention +1. **Custom HTTP Modules** - Need complete rewrite as middleware +2. **File System Operations** - Ensure cross-platform compatibility +3. **Database Connections** - Test connection strings and drivers +4. **Third-party Controls** - May require significant updates +5. **Custom Authentication** - Complex migration to ASP.NET Core Identity + +### Rollback Plan +- [ ] Maintain parallel .NET Framework deployment +- [ ] Create automated rollback scripts +- [ ] Document rollback procedures and timelines +- [ ] Test rollback scenarios in staging environment + +### Success Metrics +- [ ] Application starts successfully on Linux +- [ ] All core functionality works as expected +- [ ] Performance is equal or better than .NET Framework +- [ ] No data loss during migration +- [ ] User authentication and authorization work correctly +- [ ] All modules load and function properly + +## Post-Migration Tasks + +### Monitoring and Optimization +- [ ] Set up application performance monitoring +- [ ] Configure logging aggregation and analysis +- [ ] Monitor memory usage and garbage collection +- [ ] Track user experience and error rates + +### Continuous Improvement +- [ ] Plan for .NET LTS upgrade schedule +- [ ] Optimize Docker images and deployment process +- [ ] Implement automated testing and deployment pipelines +- [ ] Plan for future feature development on .NET Core + +--- + +**Estimated Timeline: 13 weeks** +**Resource Requirements: 2-3 senior developers** +**Risk Level: Medium-High (due to extensive Windows dependencies)** +**Success Probability: High (with proper planning and testing)** \ No newline at end of file diff --git a/Migration-Examples/README.md b/Migration-Examples/README.md new file mode 100644 index 000000000..dd58edc9b --- /dev/null +++ b/Migration-Examples/README.md @@ -0,0 +1,49 @@ +# .NET Core Migration Examples + +This directory contains concrete examples showing how to migrate Windows-dependent BEXIS2 components to .NET Core for Linux compatibility. + +## Files Included + +1. **AppConfiguration.NetCore.cs** - Refactored AppConfiguration class using dependency injection and IConfiguration +2. **Startup.cs** - Replacement for Global.asax.cs using ASP.NET Core startup patterns +3. **BExIS.Web.Shell.NetCore.csproj** - Example project file migration from .NET Framework to .NET 6 +4. **SessionManagement.NetCore.cs** - Replacement patterns for System.Web.SessionState +5. **appsettings.json** - Configuration replacement for Web.config + +## Key Migration Patterns + +### 1. HttpContext.Current → IHttpContextAccessor +```csharp +// Old (.NET Framework) +HttpContext.Current.User + +// New (.NET Core) +private readonly IHttpContextAccessor _httpContextAccessor; +_httpContextAccessor.HttpContext.User +``` + +### 2. HostingEnvironment.MapPath → IWebHostEnvironment +```csharp +// Old (.NET Framework) +HostingEnvironment.MapPath("~/Areas") + +// New (.NET Core) +private readonly IWebHostEnvironment _environment; +Path.Combine(_environment.WebRootPath, "Areas") +``` + +### 3. ConfigurationManager → IConfiguration +```csharp +// Old (.NET Framework) +ConfigurationManager.AppSettings["key"] + +// New (.NET Core) +private readonly IConfiguration _configuration; +_configuration["key"] +``` + +### 4. Global.asax → Startup.cs +Application lifecycle events are replaced with middleware pipeline configuration. + +### 5. Web.config → appsettings.json +XML-based configuration is replaced with JSON-based configuration with strong typing support. \ No newline at end of file diff --git a/Migration-Examples/SessionManagement.NetCore.cs b/Migration-Examples/SessionManagement.NetCore.cs new file mode 100644 index 000000000..b6c3bc72c --- /dev/null +++ b/Migration-Examples/SessionManagement.NetCore.cs @@ -0,0 +1,548 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Vaiona.Model.MTnt; + +namespace BExIS.Web.Shell.Helpers +{ + /// + /// .NET Core session management that replaces System.Web.SessionState functionality. + /// Provides strongly-typed session access and cross-platform compatibility. + /// + public interface ISessionManager + { + // Basic session operations + T Get(string key); + void Set(string key, T value); + void Remove(string key); + void Clear(); + bool Exists(string key); + IEnumerable Keys { get; } + + // BExIS-specific session methods + void SetTenant(Tenant tenant); + Tenant GetTenant(); + void SetUser(string userName, bool isAuthenticated); + (string UserName, bool IsAuthenticated) GetUser(); + void ApplyCulture(string culture); + string GetCulture(); + + // Session lifecycle + Task InitializeAsync(); + Task RefreshAsync(); + bool IsNewSession { get; } + } + + /// + /// Implementation of session management for ASP.NET Core. + /// Replaces the static session access patterns from System.Web. + /// + public class SessionManager : ISessionManager + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + + public SessionManager(IHttpContextAccessor httpContextAccessor, ILogger logger) + { + _httpContextAccessor = httpContextAccessor; + _logger = logger; + } + + private ISession Session => _httpContextAccessor.HttpContext?.Session; + + public bool IsNewSession + { + get + { + var session = Session; + return session != null && !session.Keys.Any(); + } + } + + public IEnumerable Keys => Session?.Keys ?? new List(); + + /// + /// Gets a strongly-typed value from session. + /// Replaces: HttpContext.Current.Session["key"] + /// + public T Get(string key) + { + try + { + var session = Session; + if (session == null) return default(T); + + var value = session.GetString(key); + if (string.IsNullOrEmpty(value)) + return default(T); + + if (typeof(T) == typeof(string)) + return (T)(object)value; + + if (typeof(T).IsValueType) + { + return (T)Convert.ChangeType(value, typeof(T)); + } + + return JsonConvert.DeserializeObject(value); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting session value for key: {Key}", key); + return default(T); + } + } + + /// + /// Sets a strongly-typed value in session. + /// Replaces: HttpContext.Current.Session["key"] = value + /// + public void Set(string key, T value) + { + try + { + var session = Session; + if (session == null) return; + + if (value == null) + { + session.Remove(key); + return; + } + + string serializedValue; + if (typeof(T) == typeof(string)) + { + serializedValue = value.ToString(); + } + else if (typeof(T).IsValueType) + { + serializedValue = value.ToString(); + } + else + { + serializedValue = JsonConvert.SerializeObject(value); + } + + session.SetString(key, serializedValue); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error setting session value for key: {Key}", key); + } + } + + /// + /// Removes a value from session. + /// Replaces: HttpContext.Current.Session.Remove("key") + /// + public void Remove(string key) + { + try + { + Session?.Remove(key); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error removing session value for key: {Key}", key); + } + } + + /// + /// Clears all session values. + /// Replaces: HttpContext.Current.Session.Clear() + /// + public void Clear() + { + try + { + Session?.Clear(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error clearing session"); + } + } + + /// + /// Checks if a session key exists. + /// Replaces: HttpContext.Current.Session["key"] != null + /// + public bool Exists(string key) + { + try + { + var session = Session; + return session != null && session.Keys.Contains(key); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking session key existence: {Key}", key); + return false; + } + } + + #region BExIS-Specific Session Methods + + /// + /// Sets the current tenant in session. + /// Replaces: Session["Tenant"] = tenant + /// + public void SetTenant(Tenant tenant) + { + const string key = "CurrentTenant"; + Set(key, tenant); + _logger.LogDebug("Tenant set in session: {TenantId}", tenant?.Id); + } + + /// + /// Gets the current tenant from session. + /// Replaces: (Tenant)Session["Tenant"] + /// + public Tenant GetTenant() + { + const string key = "CurrentTenant"; + return Get(key); + } + + /// + /// Sets user information in session. + /// Replaces manual session management for user state. + /// + public void SetUser(string userName, bool isAuthenticated) + { + Set("UserName", userName); + Set("IsAuthenticated", isAuthenticated); + _logger.LogDebug("User set in session: {UserName}, Authenticated: {IsAuthenticated}", userName, isAuthenticated); + } + + /// + /// Gets user information from session. + /// + public (string UserName, bool IsAuthenticated) GetUser() + { + var userName = Get("UserName") ?? string.Empty; + var isAuthenticated = Get("IsAuthenticated"); + return (userName, isAuthenticated); + } + + /// + /// Applies culture settings to the current thread and stores in session. + /// Replaces: Thread.CurrentThread.CurrentCulture = culture + /// + public void ApplyCulture(string culture) + { + try + { + var cultureInfo = new System.Globalization.CultureInfo(culture); + System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; + System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; + + Set("Culture", culture); + _logger.LogDebug("Culture applied: {Culture}", culture); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error applying culture: {Culture}", culture); + } + } + + /// + /// Gets the current culture from session. + /// + public string GetCulture() + { + return Get("Culture") ?? "en-US"; + } + + #endregion + + #region Session Lifecycle + + /// + /// Initializes a new session with default values. + /// Replaces: Global.asax Session_Start logic + /// + public async Task InitializeAsync() + { + try + { + if (IsNewSession) + { + _logger.LogInformation("Initializing new session"); + + // Set default values + Set("SessionStartTime", DateTime.UtcNow); + Set("SessionId", Guid.NewGuid().ToString()); + + // Apply default culture + ApplyCulture("en-US"); + + _logger.LogDebug("Session initialized successfully"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error initializing session"); + throw; + } + } + + /// + /// Refreshes session timeout. + /// Useful for keeping active sessions alive. + /// + public async Task RefreshAsync() + { + try + { + var session = Session; + if (session != null) + { + Set("LastActivity", DateTime.UtcNow); + await session.CommitAsync(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing session"); + } + } + + #endregion + } + + /// + /// Extension methods for ISession to provide backward compatibility + /// with existing BExIS session usage patterns. + /// + public static class SessionExtensions + { + /// + /// Extension method that replicates the old Session.ApplyCulture functionality. + /// Usage: context.Session.ApplyCulture("en-US") + /// + public static void ApplyCulture(this ISession session, string defaultCulture) + { + try + { + var culture = new System.Globalization.CultureInfo(defaultCulture); + System.Threading.Thread.CurrentThread.CurrentCulture = culture; + System.Threading.Thread.CurrentThread.CurrentUICulture = culture; + + session.SetString("Culture", defaultCulture); + } + catch (Exception) + { + // Fallback to en-US if culture is invalid + var fallbackCulture = new System.Globalization.CultureInfo("en-US"); + System.Threading.Thread.CurrentThread.CurrentCulture = fallbackCulture; + System.Threading.Thread.CurrentThread.CurrentUICulture = fallbackCulture; + session.SetString("Culture", "en-US"); + } + } + + /// + /// Extension method for setting tenant in session. + /// Usage: context.Session.SetTenant(tenant) + /// + public static void SetTenant(this ISession session, Tenant tenant) + { + if (tenant != null) + { + var tenantJson = JsonConvert.SerializeObject(tenant); + session.SetString("CurrentTenant", tenantJson); + session.SetString("TenantId", tenant.Id.ToString()); + session.SetString("TenantName", tenant.Name ?? string.Empty); + } + } + + /// + /// Extension method for getting tenant from session. + /// Usage: var tenant = context.Session.GetTenant() + /// + public static Tenant GetTenant(this ISession session) + { + try + { + var tenantJson = session.GetString("CurrentTenant"); + if (!string.IsNullOrEmpty(tenantJson)) + { + return JsonConvert.DeserializeObject(tenantJson); + } + + // Fallback: try to construct from individual properties + var tenantIdString = session.GetString("TenantId"); + var tenantName = session.GetString("TenantName"); + + if (!string.IsNullOrEmpty(tenantIdString) && long.TryParse(tenantIdString, out long tenantId)) + { + return new Tenant { Id = tenantId, Name = tenantName }; + } + } + catch (Exception) + { + // Return null if deserialization fails + } + + return null; + } + + /// + /// Generic extension method for getting strongly-typed values from session. + /// Usage: var value = context.Session.Get("key") + /// + public static T Get(this ISession session, string key) + { + try + { + var value = session.GetString(key); + if (string.IsNullOrEmpty(value)) + return default(T); + + if (typeof(T) == typeof(string)) + return (T)(object)value; + + if (typeof(T).IsValueType) + { + return (T)Convert.ChangeType(value, typeof(T)); + } + + return JsonConvert.DeserializeObject(value); + } + catch + { + return default(T); + } + } + + /// + /// Generic extension method for setting strongly-typed values in session. + /// Usage: context.Session.Set("key", myObject) + /// + public static void Set(this ISession session, string key, T value) + { + try + { + if (value == null) + { + session.Remove(key); + return; + } + + string serializedValue; + if (typeof(T) == typeof(string)) + { + serializedValue = value.ToString(); + } + else if (typeof(T).IsValueType) + { + serializedValue = value.ToString(); + } + else + { + serializedValue = JsonConvert.SerializeObject(value); + } + + session.SetString(key, serializedValue); + } + catch + { + // Silently fail - logging should be handled at higher level + } + } + } + + /// + /// Service collection extensions for registering session management services. + /// + public static class SessionManagerServiceExtensions + { + /// + /// Adds session management services to the dependency injection container. + /// Call this in Startup.ConfigureServices. + /// + public static IServiceCollection AddBExISSessionManagement(this IServiceCollection services) + { + // Add distributed memory cache for session storage + services.AddDistributedMemoryCache(); + + // Configure session options + services.AddSession(options => + { + options.IdleTimeout = TimeSpan.FromMinutes(30); + options.Cookie.HttpOnly = true; + options.Cookie.IsEssential = true; + options.Cookie.Name = "BExIS.SessionId"; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.Cookie.SameSite = SameSiteMode.Lax; + }); + + // Register session manager + services.AddScoped(); + + return services; + } + } +} + +/* +Migration Guide: System.Web.SessionState to ASP.NET Core Sessions + +OLD (.NET Framework): +=================== +1. HttpContext.Current.Session["key"] = value +2. var value = (MyType)HttpContext.Current.Session["key"] +3. HttpContext.Current.Session.Remove("key") +4. HttpContext.Current.Session.Clear() +5. HttpContext.Current.Session.Abandon() + +NEW (.NET Core): +=============== +1. Using ISessionManager (Dependency Injection): + - _sessionManager.Set("key", value) + - var value = _sessionManager.Get("key") + - _sessionManager.Remove("key") + - _sessionManager.Clear() + +2. Using ISession directly: + - context.Session.Set("key", value) // extension method + - var value = context.Session.Get("key") // extension method + - context.Session.Remove("key") + - context.Session.Clear() + +3. In Controllers: + - HttpContext.Session.Set("key", value) + - var value = HttpContext.Session.Get("key") + +Key Differences: +================ +1. No more static HttpContext.Current access +2. Dependency injection replaces static access +3. Sessions are async-aware (CommitAsync) +4. Better type safety with generic methods +5. JSON serialization for complex objects +6. Configurable session providers (memory, Redis, SQL Server) +7. Cross-platform compatible storage + +Configuration Required: +====================== +1. Add services.AddSession() in ConfigureServices +2. Add app.UseSession() in Configure pipeline +3. Register ISessionManager in DI container +4. Configure session options (timeout, cookie settings) + +Benefits: +========= +1. Testable (can mock ISessionManager) +2. Type-safe with generics +3. Better error handling +4. Configurable storage backends +5. Linux/Docker compatible +6. Better security options +*/ \ No newline at end of file diff --git a/Migration-Examples/Startup.cs b/Migration-Examples/Startup.cs new file mode 100644 index 000000000..c9048b1a8 --- /dev/null +++ b/Migration-Examples/Startup.cs @@ -0,0 +1,428 @@ +using BExIS.App.Bootstrap; +using BExIS.UI.Helpers; +using BExIS.Utils.Config; +using BExIS.Web.Shell.Helpers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using Vaiona.IoC; +using Vaiona.Model.MTnt; +using Vaiona.MultiTenancy.Api; +using Vaiona.Utils.Cfg; +using Vaiona.Web.Extensions; + +namespace BExIS.Web.Shell +{ + /// + /// .NET Core Startup class that replaces Global.asax.cs functionality. + /// Handles application configuration, dependency injection, and middleware pipeline. + /// + public class Startup + { + private BExIS.App.Bootstrap.Application _app = null; + + public Startup(IConfiguration configuration, IWebHostEnvironment environment) + { + Configuration = configuration; + Environment = environment; + } + + public IConfiguration Configuration { get; } + public IWebHostEnvironment Environment { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Configure JSON serialization (replaces Global.asax JsonConvert.DefaultSettings) + services.Configure(options => + { + options.SerializerSettings.Formatting = Formatting.Indented; + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + }); + + // Add framework services + services.AddControllersWithViews() + .AddNewtonsoftJson() // For compatibility with existing JSON handling + .AddRazorRuntimeCompilation(); // For development-time view compilation + + // Add session services (replaces System.Web.SessionState) + services.AddDistributedMemoryCache(); + services.AddSession(options => + { + options.IdleTimeout = TimeSpan.FromMinutes(30); + options.Cookie.HttpOnly = true; + options.Cookie.IsEssential = true; + options.Cookie.Name = "BExIS.SessionId"; // Custom session cookie name + }); + + // Add AppConfiguration with dependency injection + services.AddAppConfiguration(); + + // Add HTTP context accessor for accessing HTTP context in services + services.AddHttpContextAccessor(); + + // Add custom view location expanders (replaces Global.asax CustomViewEngine) + services.Configure(options => + { + options.ViewLocationExpanders.Add(new CustomViewLocationExpander()); + }); + + // Add CORS services + services.AddCors(options => + { + options.AddDefaultPolicy(builder => + { + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); + }); + + // Add IoC container integration + ConfigureIoCContainer(services); + + // Register BExIS application services + ConfigureBExISServices(services); + + // Add bundling and minification (replaces BundleConfig) + services.AddWebOptimizer(pipeline => + { + // Configure CSS and JS bundling here + pipeline.AddCssBundle("/css/bundle.css", "css/**/*.css"); + pipeline.AddJavaScriptBundle("/js/bundle.js", "js/**/*.js"); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) + { + // Configure error handling (replaces Application_Error in Global.asax) + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); + } + + // Add custom error handling middleware + app.UseMiddleware(); + + // Security headers (replaces Application_PreSendRequestHeaders) + app.Use(async (context, next) => + { + context.Response.Headers.Remove("Server"); + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "DENY"); + context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); + await next(); + }); + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + // Use bundling and minification + app.UseWebOptimizer(); + + // CORS middleware (replaces Application_BeginRequest CORS headers) + app.UseCors(); + + // Session middleware (replaces System.Web.SessionState) + app.UseSession(); + + // Custom session initialization middleware (replaces Session_Start) + app.UseMiddleware(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + name: "areas", + pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); + + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + + // API routes + endpoints.MapControllers(); + }); + + // Initialize BExIS application (replaces Application_Start) + InitializeBExISApplication(); + + logger.LogInformation("BExIS application started successfully"); + } + + private void ConfigureIoCContainer(IServiceCollection services) + { + // Configure IoC container integration + // This replaces the IoC initialization from Global.asax + services.AddSingleton(); + + // Add other IoC registrations here + } + + private void ConfigureBExISServices(IServiceCollection services) + { + // Register BExIS-specific services + services.AddScoped(); + + // Add other BExIS service registrations + } + + private void InitializeBExISApplication() + { + // This replaces Application_Start logic from Global.asax + try + { + _app = BExIS.App.Bootstrap.Application.GetInstance(RunStage.Production); + _app.Start(ConfigureWebApi, true); + } + catch (Exception ex) + { + // Log startup errors + // Consider using ILogger here + throw new ApplicationException("Failed to initialize BExIS application", ex); + } + } + + private void ConfigureWebApi(IServiceCollection services) + { + // Web API configuration - replaces WebApiConfig.Register + services.AddControllers() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + }); + } + + // This replaces Application_End from Global.asax + public void Dispose() + { + _app?.Stop(); + } + } + + /// + /// Custom view location expander that replaces the CustomViewEngine logic from Global.asax + /// + public class CustomViewLocationExpander : IViewLocationExpander + { + public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + // Add additional view search paths + var additionalLocations = new[] + { + "/Areas/{2}/Views/{1}/{0}.cshtml", + "/Areas/{2}/Views/Shared/{0}.cshtml", + "/Areas/{2}/{3}.{1}.UI/Views/{1}/{0}.cshtml", // One directory lower + "/Areas/{2}/{3}.{1}.UI/Views/Shared/{0}.cshtml" + }; + + return additionalLocations.Concat(viewLocations); + } + + public void PopulateValues(ViewLocationExpanderContext context) + { + // Can add contextual values for view location expansion + } + } + + /// + /// Middleware that handles session initialization (replaces Session_Start from Global.asax) + /// + public class SessionInitializationMiddleware + { + private readonly RequestDelegate _next; + private readonly IServiceProvider _serviceProvider; + + public SessionInitializationMiddleware(RequestDelegate next, IServiceProvider serviceProvider) + { + _next = next; + _serviceProvider = serviceProvider; + } + + public async Task InvokeAsync(HttpContext context) + { + // Check if this is a new session + if (!context.Session.Keys.Any()) + { + await InitializeSession(context); + } + + await _next(context); + } + + private async Task InitializeSession(HttpContext context) + { + try + { + // Session timeout handling (replaces Global.asax Session_Start logic) + var sessionCookie = context.Request.Cookies["BExIS.SessionId"]; + if (!string.IsNullOrEmpty(sessionCookie)) + { + // Handle session timeout scenario + context.Response.Redirect("/Home/SessionTimeout"); + return; + } + + // Initialize IoC session-level container + using (var scope = _serviceProvider.CreateScope()) + { + var iocFactory = scope.ServiceProvider.GetService(); + iocFactory?.Container?.StartSessionLevelContainer(); + + // Apply culture settings + var appConfig = scope.ServiceProvider.GetService(); + if (appConfig != null) + { + context.Session.ApplyCulture(appConfig.DefaultCulture); + } + + // Resolve tenant + var tenantResolver = scope.ServiceProvider.GetService(); + if (tenantResolver != null) + { + var tenant = tenantResolver.Resolve(context.Request); + context.Session.SetTenant(tenant); + } + } + } + catch (Exception ex) + { + // Log session initialization errors + // In production, consider graceful degradation + throw new ApplicationException("Failed to initialize session", ex); + } + } + } + + /// + /// Custom error handling middleware (replaces Application_Error from Global.asax) + /// + public class CustomErrorHandlingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public CustomErrorHandlingMiddleware(RequestDelegate next, ILogger logger, IConfiguration configuration) + { + _next = next; + _logger = logger; + _configuration = configuration; + } + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + + private async Task HandleExceptionAsync(HttpContext context, Exception exception) + { + bool sendExceptions = _configuration.GetValue("SendExceptions", false); + var statusCode = 500; + + if (exception is HttpRequestException httpEx) + { + statusCode = 400; // Bad Request + } + + _logger.LogError(exception, "An unhandled exception occurred"); + + // Replaces the error email functionality from Global.asax + if (sendExceptions && + statusCode != 404 && + !(exception is InvalidOperationException) && + !exception.Message.StartsWith("Multiple types were found that match the controller named")) + { + await SendErrorEmail(exception); + } + + context.Response.StatusCode = statusCode; + + if (!context.Response.HasStarted) + { + await context.Response.WriteAsync("An error occurred while processing your request."); + } + } + + private async Task SendErrorEmail(Exception exception) + { + try + { + // Implement error email functionality + // This replaces ErrorHelper.SendEmailWithErrors from Global.asax + _logger.LogCritical(exception, "Critical error occurred - email notification should be sent"); + } + catch (Exception emailEx) + { + _logger.LogError(emailEx, "Failed to send error notification email"); + } + } + } + + /// + /// Extension methods for session management (replaces System.Web.SessionState extensions) + /// + public static class SessionExtensions + { + public static void ApplyCulture(this ISession session, string defaultCulture) + { + // Apply culture settings to current thread + var culture = new System.Globalization.CultureInfo(defaultCulture); + System.Threading.Thread.CurrentThread.CurrentCulture = culture; + System.Threading.Thread.CurrentThread.CurrentUICulture = culture; + + // Store in session for persistence + session.SetString("Culture", defaultCulture); + } + + public static void SetTenant(this ISession session, Tenant tenant) + { + if (tenant != null) + { + session.SetString("TenantId", tenant.Id.ToString()); + session.SetString("TenantName", tenant.Name ?? string.Empty); + } + } + + public static Tenant GetTenant(this ISession session) + { + var tenantId = session.GetString("TenantId"); + var tenantName = session.GetString("TenantName"); + + if (!string.IsNullOrEmpty(tenantId) && long.TryParse(tenantId, out long id)) + { + return new Tenant { Id = id, Name = tenantName }; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Migration-Examples/appsettings.json b/Migration-Examples/appsettings.json new file mode 100644 index 000000000..1d97c36b2 --- /dev/null +++ b/Migration-Examples/appsettings.json @@ -0,0 +1,199 @@ +{ + // Configuration settings that replace Web.config appSettings and connectionStrings + // This file provides cross-platform compatibility for Linux deployment + + "ConnectionStrings": { + "ApplicationServices": "Data Source=localhost;Initial Catalog=BExIS;Integrated Security=True;MultipleActiveResultSets=True", + "DefaultConnection": "Data Source=localhost;Initial Catalog=BExIS;Integrated Security=True;MultipleActiveResultSets=True" + }, + + // Application settings (replaces Web.config appSettings) + "DefaultCulture": "en-US", + "DatabaseMappingFile": "Database.Mapping.xml", + "DatabaseDialect": "SqlServer2012Dialect", + "AutoCommitTransactions": false, + "IoCProviderTypeInfo": "Vaiona.IoC.Unity.UnityIoCProvider, Vaiona.IoC.Unity", + + // Path configurations (cross-platform compatible) + "ApplicationRoot": "", // Empty = use ContentRootPath + "DataPath": "Data", + "WorkspacePath": "../Workspace", + "ThemesPath": "~/Themes", + "DefaultThemeName": "Default", + "ActiveLayoutName": "_Layout", + + // Database settings + "UseSchemaInDatabaseGeneration": false, + "CreateDatabase": false, + "ShowQueries": false, + "CacheQueryResults": true, + "ConversationIsolationLevel": 2, + + // Logging configuration (replaces Web.config logging settings) + "IsLoggingEnable": true, + "IsPerformanceLoggingEnable": false, + "IsDiagnosticLoggingEnable": false, + "IsCallLoggingEnable": false, + "IsExceptionLoggingEnable": true, + "IsDataLoggingEnable": false, + + // Multi-tenancy + "TenantId": "", + + // Error handling + "SendExceptions": false, + "ThrowErrorWhenParialContentNotFound": false, + + // ASP.NET Core specific logging configuration + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.AspNetCore": "Warning", + "BExIS": "Information" + }, + "Console": { + "IncludeScopes": true + }, + "File": { + "Path": "Logs/bexis-{Date}.log", + "MinLevel": "Information", + "OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}" + } + }, + + // Session configuration (replaces System.Web.SessionState) + "Session": { + "TimeoutMinutes": 30, + "CookieName": "BExIS.SessionId", + "CookieHttpOnly": true, + "CookieSecure": "SameAsRequest", // SameAsRequest, Always, Never + "SameSite": "Lax" // Strict, Lax, None + }, + + // CORS settings (replaces Global.asax CORS headers) + "Cors": { + "AllowedOrigins": [ "*" ], + "AllowedMethods": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ], + "AllowedHeaders": [ "*" ], + "AllowCredentials": false + }, + + // Bundling and optimization settings + "WebOptimizer": { + "EnableCaching": true, + "EnableCompression": true, + "EnableTagHelperBundling": true + }, + + // Security settings + "Security": { + "RequireHttps": false, + "HstsMaxAge": 31536000, + "ContentSecurityPolicy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" + }, + + // Application insights (optional - for monitoring) + "ApplicationInsights": { + "InstrumentationKey": "", + "EnableAdaptiveSampling": true + }, + + // Health checks configuration + "HealthChecks": { + "Database": { + "Enabled": true, + "TimeoutSeconds": 30 + }, + "FileSystem": { + "Enabled": true, + "Paths": [ "Data", "Workspace" ] + } + }, + + // Kestrel server configuration (for Linux deployment) + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5000" + }, + "Https": { + "Url": "https://localhost:5001", + "Certificate": { + "Path": "certificates/bexis.pfx", + "Password": "" + } + } + }, + "Limits": { + "MaxConcurrentConnections": 100, + "MaxRequestBodySize": 104857600, // 100MB + "RequestHeadersTimeout": "00:00:30", + "KeepAliveTimeout": "00:02:00" + } + }, + + // Authentication settings (if using JWT or similar) + "Authentication": { + "DefaultScheme": "Cookies", + "Cookies": { + "LoginPath": "/Account/Login", + "LogoutPath": "/Account/Logout", + "AccessDeniedPath": "/Account/AccessDenied", + "ExpireTimeSpan": "01:00:00", // 1 hour + "SlidingExpiration": true + }, + "JwtBearer": { + "ValidateIssuer": true, + "ValidateAudience": true, + "ValidateLifetime": true, + "ValidateIssuerSigningKey": true, + "Issuer": "https://localhost:5001", + "Audience": "BExIS.API", + "SecretKey": "YourSecretKeyHereMinimum32Characters!" + } + }, + + // Email settings (if using SMTP) + "Email": { + "SmtpServer": "localhost", + "SmtpPort": 587, + "SmtpUsername": "", + "SmtpPassword": "", + "EnableSsl": true, + "FromAddress": "noreply@bexis.uni-jena.de", + "FromName": "BExIS System" + }, + + // File upload settings + "FileUpload": { + "MaxFileSize": 104857600, // 100MB in bytes + "AllowedExtensions": [ ".csv", ".xlsx", ".txt", ".xml", ".json", ".zip" ], + "UploadPath": "Data/Uploads", + "TempPath": "Data/Temp" + } +} + +/* +Migration Notes from Web.config to appsettings.json: + +1. ConnectionStrings section maps directly +2. appSettings become root-level properties or grouped logically +3. system.web/httpRuntime settings -> Kestrel configuration +4. system.web/compilation -> Not needed (handled by MSBuild) +5. system.web/authentication -> Authentication configuration section +6. system.web/sessionState -> Session configuration section +7. system.web/customErrors -> Exception handling middleware +8. system.web/globalization -> RequestLocalization middleware +9. system.webServer/handlers -> Not needed (built into ASP.NET Core) +10. system.webServer/modules -> Middleware pipeline in Startup.cs + +Key Benefits: +- JSON is cross-platform and easier to parse +- Strong typing with IConfiguration.Get() +- Environment-specific overrides (appsettings.Development.json) +- Secret management integration +- Hierarchical configuration with colons (e.g., "Database:ConnectionString") +- Built-in validation and binding to POCOs +*/ \ No newline at end of file