diff --git a/ContextMiddleware/ConnectionStringMiddleware.cs b/ContextMiddleware/ConnectionStringMiddleware.cs new file mode 100644 index 00000000..d5eb5909 --- /dev/null +++ b/ContextMiddleware/ConnectionStringMiddleware.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; + +namespace TodoApi.ContextMiddleware +{ + public class ConnectionStringMiddleware + { + private readonly RequestDelegate _next; + private readonly IConfiguration _configuration; + + public ConnectionStringMiddleware(RequestDelegate next, IConfiguration configuration) + { + _next = next; + _configuration = configuration; + } + + public async Task InvokeAsync(HttpContext context) + { + // Extract the client identifier + var clientId = context.Request.Headers["X-Client-ID"].ToString(); + + if (string.IsNullOrEmpty(clientId) || + !_configuration.GetSection("ConnectionStrings:ClientDatabases").Exists()) + { + context.Response.StatusCode = 400; // Bad Request + await context.Response.WriteAsync("Invalid or missing client ID."); + return; + } + + // Retrieve the connection string + var connectionString = _configuration.GetSection($"ConnectionStrings:ClientDatabases:{clientId}").Value; + + // Store the connection string in the HttpContext for downstream services + context.Items["ConnectionString"] = connectionString; + + await _next(context); + } + } + +} diff --git a/Controllers/TodoController.cs b/Controllers/TodoController.cs index aad8137b..a310b5a8 100644 --- a/Controllers/TodoController.cs +++ b/Controllers/TodoController.cs @@ -16,23 +16,33 @@ namespace TodoApi.Controllers public class TodoController : Controller { private readonly TodoContext _context; - - public TodoController(TodoContext context) + private readonly DynamicDbContextFactory _dbContextFactory; + private readonly TodoContext _contextDynamic; + public TodoController(TodoContext context, DynamicDbContextFactory dbContextFactory) { _context = context; - + if (_context.TodoItems.Count() == 0) { _context.TodoItems.Add(new TodoItem { Name = "Item1" }); _context.SaveChanges(); } + + _dbContextFactory = dbContextFactory; + _contextDynamic = _dbContextFactory.CreateDbContext(); + if (_contextDynamic.TodoItems.Count() == 0) + { + _contextDynamic.TodoItems.Add(new TodoItem { Name = "Item1" }); + _contextDynamic.SaveChanges(); + } } // GET: api/Todo [HttpGet] public async Task>> GetTodoItem() { - return await _context.TodoItems.ToListAsync(); + + return await _contextDynamic.TodoItems.ToListAsync(); } // GET: api/Todo/5 diff --git a/Models/DynamicDbContextFactory.cs b/Models/DynamicDbContextFactory.cs new file mode 100644 index 00000000..26178553 --- /dev/null +++ b/Models/DynamicDbContextFactory.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using System.Data.Entity.Infrastructure; +using System.Data.Entity; +using TodoApi.Services; + +namespace TodoApi.Models +{ + + public class DynamicDbContextFactory + { + private readonly ITenantService _tenantService; + + public DynamicDbContextFactory(ITenantService tenantService) + { + _tenantService = tenantService; + } + + + public TodoContext CreateDbContext() + { + var connectionString = _tenantService.GetConnectionString(); + + var optionsBuilder = new DbContextOptionsBuilder(); + + //TODO: here need to set the connection string for the database + //optionsBuilder.UseSqlServer(connectionString); + //optionsBuilder.UseSqlite(connectionString); + + optionsBuilder.UseInMemoryDatabase(connectionString); + return new TodoContext(optionsBuilder.Options); + } + } +} diff --git a/Services/TenantService.cs b/Services/TenantService.cs new file mode 100644 index 00000000..95eef256 --- /dev/null +++ b/Services/TenantService.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using System; + +namespace TodoApi.Services +{ + public interface ITenantService + { + string GetConnectionString(); + } + public class TenantService : ITenantService + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IConfiguration _configuration; + + public TenantService(IHttpContextAccessor httpContextAccessor, IConfiguration configuration) + { + _httpContextAccessor = httpContextAccessor; + _configuration = configuration; + } + + public string GetConnectionString() + { + // Extract client ID from header + var clientId = _httpContextAccessor.HttpContext?.Request.Headers["X-Client-ID"].ToString(); + + if (string.IsNullOrEmpty(clientId)) + throw new UnauthorizedAccessException("Client ID is missing."); + + // Retrieve the connection string from configuration + var connectionString = _configuration.GetSection($"ConnectionStrings:ClientDatabases:{clientId}").Value; + + if (string.IsNullOrEmpty(connectionString)) + throw new UnauthorizedAccessException("Invalid client ID or connection string not found."); + + return connectionString; + } + + } +} \ No newline at end of file diff --git a/Startup.cs b/Startup.cs index aeb859af..b5c5c1dd 100644 --- a/Startup.cs +++ b/Startup.cs @@ -6,6 +6,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; using TodoApi.Models; +using TodoApi.ContextMiddleware; +using System.Data.Entity.Infrastructure; +using System.Data.Entity; +using TodoApi.Services; namespace TodoApi { @@ -30,6 +34,11 @@ public void ConfigureServices(IServiceCollection services) }); services.AddDbContext(options => options.UseInMemoryDatabase("TodoList")); + + services.AddHttpContextAccessor(); + // Add the tenant service + services.AddScoped(); + services.AddScoped(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -50,6 +59,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseDeveloperExceptionPage(); } + // app.UseMiddleware(); //app.UseHttpsRedirection(); app.UseDefaultFiles(); @@ -60,6 +70,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthorization(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); diff --git a/appsettings.Development.json b/appsettings.Development.json index c9294ca4..142f0578 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -5,5 +5,11 @@ "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } + }, + "ConnectionStrings": { + "ClientDatabases": { + "ClientA": "DatabaseforClientA", + "ClientB": "DatabaseforClientB" + } } } diff --git a/wwwroot/app/scripts/todoListSvc.js b/wwwroot/app/scripts/todoListSvc.js index 55a18d60..0ebffc29 100644 --- a/wwwroot/app/scripts/todoListSvc.js +++ b/wwwroot/app/scripts/todoListSvc.js @@ -4,6 +4,8 @@ angular.module('todoApp') $http.defaults.useXDomain = true; delete $http.defaults.headers.common['X-Requested-With']; + // Add X-Client-ID header to all requests + $http.defaults.headers.common['X-Client-ID'] = 'ClientA'; return { getItems : function(){