diff --git a/docker-compose.ci.build.yml b/docker-compose.ci.build.yml index 9819f33..01b4d7b 100644 --- a/docker-compose.ci.build.yml +++ b/docker-compose.ci.build.yml @@ -2,7 +2,7 @@ version: '3.4' services: ci-build: - image: microsoft/dotnet:2.2-aspnetcore-runtime + image: mcr.microsoft.com/dotnet/core/aspnet:3.1 volumes: - .:/src working_dir: /src diff --git a/samples/IdentitySample.Web/Dockerfile b/samples/IdentitySample.Web/Dockerfile index 49e531d..41b886d 100644 --- a/samples/IdentitySample.Web/Dockerfile +++ b/samples/IdentitySample.Web/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/dotnet:2.2-aspnetcore-runtime +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 ARG source WORKDIR /app EXPOSE 80 diff --git a/samples/IdentitySample.Web/IdentitySample.Web.csproj b/samples/IdentitySample.Web/IdentitySample.Web.csproj index 4ade833..1c1930f 100644 --- a/samples/IdentitySample.Web/IdentitySample.Web.csproj +++ b/samples/IdentitySample.Web/IdentitySample.Web.csproj @@ -1,8 +1,7 @@  - netcoreapp2.2 - InProcess + netcoreapp3.1 latest ..\..\docker-compose.dcproj @@ -12,10 +11,8 @@ - - - - + + diff --git a/samples/IdentitySample.Web/Pages/Admin/Roles/Index.cshtml.cs b/samples/IdentitySample.Web/Pages/Admin/Roles/Index.cshtml.cs index d2a612d..261d6ce 100644 --- a/samples/IdentitySample.Web/Pages/Admin/Roles/Index.cshtml.cs +++ b/samples/IdentitySample.Web/Pages/Admin/Roles/Index.cshtml.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; namespace IdentitySample.Web.Pages.Admin { diff --git a/samples/IdentitySample.Web/Program.cs b/samples/IdentitySample.Web/Program.cs index 4f2c4dc..9128d89 100644 --- a/samples/IdentitySample.Web/Program.cs +++ b/samples/IdentitySample.Web/Program.cs @@ -1,8 +1,8 @@ -using AspNetCore.Identity.Cassandra.Extensions; -using IdentitySample.Web.Data; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.IO; namespace IdentitySample.Web { @@ -15,12 +15,27 @@ public static void Main(string[] args) public static IWebHost BuildWebHost(string[] args) { - var config = new ConfigurationBuilder().AddCommandLine(args).Build(); - return WebHost.CreateDefaultBuilder(args) + return new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + var env = hostingContext.HostingEnvironment; + + config + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{Environment.MachineName}.json", optional: true, reloadOnChange: false); + + if (args != null) + config.AddCommandLine(args); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration); + }) .UseStartup() - .UseConfiguration(config) - .Build() - .InitializeIdentityDb(); + .Build(); } } } diff --git a/samples/IdentitySample.Web/Startup.cs b/samples/IdentitySample.Web/Startup.cs index 40c46a6..f980813 100644 --- a/samples/IdentitySample.Web/Startup.cs +++ b/samples/IdentitySample.Web/Startup.cs @@ -1,5 +1,4 @@ -using System.Linq; -using AspNetCore.Identity.Cassandra; +using AspNetCore.Identity.Cassandra; using AspNetCore.Identity.Cassandra.Extensions; using IdentitySample.Web.Data; using IdentitySample.Web.Services; @@ -8,57 +7,37 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace IdentitySample.Web { public class Startup { - public IConfigurationRoot Configuration { get; set; } + public IConfiguration Configuration { get; private set; } + public IWebHostEnvironment Environment { get; private set; } - public Startup(IHostingEnvironment env) + public Startup(IConfiguration configuration, IWebHostEnvironment environment) { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true) - .AddEnvironmentVariables(); - - Configuration = builder.Build(); + Configuration = configuration; + Environment = environment; } public void ConfigureServices(IServiceCollection services) { services.AddOptions(); - services.Configure(Configuration.GetSection("Cassandra")); - services.AddCassandraSession(() => - { - var contactPoints = Configuration - .GetSection("Cassandra:ContactPoints") - .GetChildren() - .Select(x => x.Value); - var cluster = Cassandra.Cluster.Builder() - .AddContactPoints(contactPoints) - .WithCredentials( - Configuration.GetValue("Cassandra:Credentials:UserName"), - Configuration.GetValue("Cassandra:Credentials:Password")) - .Build(); - var session = cluster.Connect(); - return session; - }); + services.AddCassandra(Configuration); services.AddIdentity() .AddCassandraErrorDescriber() .UseCassandraStores() .AddDefaultTokenProviders(); - services.AddMvc() - .WithRazorPagesRoot("/Pages") - .AddRazorPagesOptions(options => - { - options.Conventions.AuthorizeFolder("/Account/Manage"); - options.Conventions.AuthorizePage("/Account/Logout"); - }); + services.AddRazorPages(options => + { + options.Conventions.AuthorizeFolder("/Account/Manage"); + options.Conventions.AuthorizePage("/Account/Logout"); + }); services.AddAuthentication("myCookie") .AddCookie("myCookie", options => @@ -69,16 +48,30 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + else + { + app.UseHsts(); + app.UseHttpsRedirection(); + } - app.UseAuthentication(); app.UseStaticFiles(); - app.UseMvc(); + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapRazorPages(); + }); + + app.UseCassandra(); } } } diff --git a/samples/IdentitySample.Web/appsettings.Production.json b/samples/IdentitySample.Web/appsettings.Production.json index 257d9cd..94e37bf 100644 --- a/samples/IdentitySample.Web/appsettings.Production.json +++ b/samples/IdentitySample.Web/appsettings.Production.json @@ -2,19 +2,24 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Error" } }, - "CassandraNodes": [ - "nosql.data" - ], - "CassandraOptions": { + "Cassandra": { + "ContactPoints": [ + "nosql.data" + ], + "Credentials": { + "UserName": "Cassandra", + "Password": "Cassandra" + }, "KeyspaceName": "identity", "Replication": { "class": "NetworkTopologyStrategy", "datacenter1": "1" + }, + "Query": { + "ConsistencyLevel": "One" } - } + } } diff --git a/src/AspNetCore.Identity.Cassandra/AspNetCore.Identity.Cassandra.csproj b/src/AspNetCore.Identity.Cassandra/AspNetCore.Identity.Cassandra.csproj index cbc96cc..764eb98 100644 --- a/src/AspNetCore.Identity.Cassandra/AspNetCore.Identity.Cassandra.csproj +++ b/src/AspNetCore.Identity.Cassandra/AspNetCore.Identity.Cassandra.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 true Lukas Kubis Lukas Kubis @@ -11,14 +11,17 @@ https://github.com/lkubis/AspNetCore.Identity.Cassandra Copyright 2017 (c) Lukas Kubis Apache Cassandra data store adapter for ASP.NET Core Identity, which allows you to build ASP.NET Core web applications, including membership, login, and user data. With this library, you can store your user's membership related data on Apache Cassandra. - 2.2.0 - 2.2.0.0 + 2.3.0 + 2.3.0.0 LICENSE + 2.3.0.0 - + + + diff --git a/src/AspNetCore.Identity.Cassandra/CassandraOptions.cs b/src/AspNetCore.Identity.Cassandra/CassandraOptions.cs index 348791c..1bb07d7 100644 --- a/src/AspNetCore.Identity.Cassandra/CassandraOptions.cs +++ b/src/AspNetCore.Identity.Cassandra/CassandraOptions.cs @@ -6,6 +6,8 @@ namespace AspNetCore.Identity.Cassandra public class CassandraOptions { public List ContactPoints { get; set; } + public int Port { get; set; } = 9042; + public int RetryCount { get; set; } = 3; public CassandraCredentials Credentials { get; set; } public string KeyspaceName { get; set; } public Dictionary Replication { get; set; } = null; diff --git a/src/AspNetCore.Identity.Cassandra/DbInitializer.cs b/src/AspNetCore.Identity.Cassandra/DbInitializer.cs index 4ae3895..9c3f810 100644 --- a/src/AspNetCore.Identity.Cassandra/DbInitializer.cs +++ b/src/AspNetCore.Identity.Cassandra/DbInitializer.cs @@ -16,7 +16,7 @@ public DbInitializer(ISession session) public void Initialize(CassandraOptions options) { - if (options == null) + if (options is null) throw new ArgumentNullException(nameof(options)); if (string.IsNullOrEmpty(options.KeyspaceName)) diff --git a/src/AspNetCore.Identity.Cassandra/Extensions/ApplicationBuilderExtensions.cs b/src/AspNetCore.Identity.Cassandra/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000..1842652 --- /dev/null +++ b/src/AspNetCore.Identity.Cassandra/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace AspNetCore.Identity.Cassandra.Extensions +{ + public static class ApplicationBuilderExtensions + { + public static IApplicationBuilder UseCassandra(this IApplicationBuilder app) + { + using (var scope = app.ApplicationServices.GetRequiredService().CreateScope()) + { + var services = scope.ServiceProvider; + var options = services.GetService(); + var initializer = services.GetService(); + + if (initializer is null) + return app; + + initializer.Initialize(options); + return app; + } + } + } +} diff --git a/src/AspNetCore.Identity.Cassandra/Extensions/IWebHostExtensions.cs b/src/AspNetCore.Identity.Cassandra/Extensions/IWebHostExtensions.cs deleted file mode 100644 index 860bb05..0000000 --- a/src/AspNetCore.Identity.Cassandra/Extensions/IWebHostExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Cassandra; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace AspNetCore.Identity.Cassandra.Extensions -{ - public static class IWebHostExtensions - { - public static IWebHost InitializeIdentityDb(this IWebHost host) - { - using (var scope = host.Services.CreateScope()) - { - var services = scope.ServiceProvider; - var session = services.GetRequiredService(); - var options = services.GetRequiredService>(); - - var initializer = new DbInitializer(session); - initializer.Initialize(options.Value); - - return host; - } - } - } -} \ No newline at end of file diff --git a/src/AspNetCore.Identity.Cassandra/Extensions/ServiceCollectionExtensions.cs b/src/AspNetCore.Identity.Cassandra/Extensions/ServiceCollectionExtensions.cs index 4e8c139..dc694aa 100644 --- a/src/AspNetCore.Identity.Cassandra/Extensions/ServiceCollectionExtensions.cs +++ b/src/AspNetCore.Identity.Cassandra/Extensions/ServiceCollectionExtensions.cs @@ -1,16 +1,66 @@ -using System; -using Cassandra; +using Cassandra; +using Cassandra.Mapping; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Polly; +using System; +using System.Net.Sockets; namespace AspNetCore.Identity.Cassandra.Extensions { public static class ServiceCollectionExtensions { - public static IServiceCollection AddCassandraSession( - this IServiceCollection services, - Func getSession) where TSession : class, ISession + public static IServiceCollection AddCassandra(this IServiceCollection services, IConfiguration configuration) { - return services.AddSingleton(getSession()); + services.Configure(configuration.GetSection("Cassandra")); + services.AddSingleton(x => x.GetRequiredService>().Value); + + return services + .AddTransient(serviceProvider => + { + var session = serviceProvider.GetRequiredService(); + return new Mapper(session); + }) + .AddSingleton(serviceProvider => + { + var options = serviceProvider.GetRequiredService(); + var logger = serviceProvider.GetRequiredService>(); + + var queryOptions = new QueryOptions(); + + if (options.Query != null && options.Query.ConsistencyLevel.HasValue) + { + if (Enum.TryParse(options.Query.ConsistencyLevel.ToString(), true, out ConsistencyLevel level)) + queryOptions.SetConsistencyLevel(level); + } + + var cluster = Cluster.Builder() + .AddContactPoints(options.ContactPoints) + .WithPort(options.Port) + .WithCredentials( + options.Credentials.UserName, + options.Credentials.Password) + .WithQueryOptions(queryOptions) + .Build(); + + ISession session = null; + var policy = Policy.Handle() + .Or() + .WaitAndRetry( + options.RetryCount, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (exception, retryCount, context) => logger.LogWarning($"Retry {retryCount} due to: {exception}")) + .Execute(() => session = cluster.Connect()); + + if (session is null) + throw new ApplicationException("FATAL ERROR: Cassandra session could not be created"); + + logger.LogInformation("Cassandra session created"); + return session; + }) + .AddSingleton(); } } } \ No newline at end of file