├── README.md ├── .gitmodules ├── src ├── BuildingBlocks.Core │ ├── BuildingBlocks.Core.csproj │ ├── IUser.cs │ └── IApiInfo.cs ├── BuildingBlocks.Idempotency │ ├── BuildingBlocks.Idempotency.csproj │ ├── IRequestManager.cs │ ├── InMemoryRequestManager.cs │ └── CircularQueue.cs ├── BuildingBlocks.Mediatr │ ├── Commands │ │ ├── ICommand.cs │ │ ├── IdentifiedCommand.cs │ │ └── IdentifiedCommandHandler.cs │ ├── Exceptions │ │ ├── MediatrPipelineException.cs │ │ ├── ThrowMediatrPipelineException.cs │ │ └── HttpGlobalExceptionFilter.cs │ ├── BuildingBlocks.Mediatr.csproj │ ├── Validators │ │ └── ValidatorsBehavior.cs │ └── Autofac │ │ └── MediatrModule.cs ├── BuildingBlocks.AspnetCoreIdentity.RavenDB │ ├── Role.cs │ ├── BuildingBlocks.AspnetCoreIdentity.RavenDB.csproj │ ├── Startup.cs │ ├── User.cs │ ├── RoleStore.cs │ └── UserStore.cs ├── BuildingBlocks.Autofac │ ├── BuildingBlocks.Autofac.csproj │ └── Startup.cs ├── BuindingBlocks.Resilience │ ├── BuindingBlocks.Resilience.csproj │ └── Http │ │ ├── Authorization.cs │ │ ├── IHttpClient.cs │ │ ├── HttpRequestMessageExtensions.cs │ │ ├── ResilientHttpClient.cs │ │ └── StandardHttpClient.cs ├── BuildingBlocks.IdentityServer4.RavenDB │ ├── BuildingBlocks.IdentityServer4.RavenDB.csproj │ ├── Extensions │ │ └── Startup.cs │ └── Stores │ │ ├── ClientStore.cs │ │ ├── PersistedGrantStore.cs │ │ └── ResourceStore.cs ├── BuildingBlocks.Mvc │ ├── BuildingBlocks.Mvc.csproj │ ├── SetupFeatureFolders.cs │ ├── ModelStateExtensions.cs │ ├── FeaturesLocationExpander.cs │ ├── HttpContextUser.cs │ ├── SetupPermissiveCors.cs │ └── SetupIdentity.cs └── BuildingBlocks.Swagger │ ├── BuildingBlocks.Swagger.csproj │ ├── OperationFilterContextExtensions.cs │ ├── AuthorizeCheckOperationFilter.cs │ └── Startup.cs ├── .gitignore └── BuildingBlocks.sln /README.md: -------------------------------------------------------------------------------- 1 | # BuildingBlocks 2 | Building blocks for Aspnet Core Microservices Development. 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/HealthChecks"] 2 | path = submodules/HealthChecks 3 | url = https://github.com/seven1986/HealthChecks.git 4 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Core/BuildingBlocks.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Idempotency/BuildingBlocks.Idempotency.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace BuildingBlocks.Mediatr.Commands 4 | { 5 | public interface ICommand : IRequest 6 | { 7 | } 8 | 9 | public interface ICommand : ICommand 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BuildingBlocks.AspnetCoreIdentity.RavenDB/Role.cs: -------------------------------------------------------------------------------- 1 | namespace BuildingBlocks.AspnetCoreIdentity.RavenDB 2 | { 3 | public class Role 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string NormalizedName { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Core/IUser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Security.Claims; 3 | 4 | namespace BuildingBlocks.Core 5 | { 6 | public interface IUser 7 | { 8 | string Id { get; } 9 | string Name { get; } 10 | IEnumerable Claims { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Autofac/BuildingBlocks.Autofac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Exceptions/MediatrPipelineException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BuildingBlocks.Mediatr.Validators 4 | { 5 | public class MediatrPipelineException : Exception 6 | { 7 | public MediatrPipelineException(string message, Exception innerException) 8 | : base(message, innerException) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/BuindingBlocks.Resilience/BuindingBlocks.Resilience.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/BuildingBlocks.IdentityServer4.RavenDB/BuildingBlocks.IdentityServer4.RavenDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Idempotency/IRequestManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace BuildingBlocks.Idempotency 6 | { 7 | public interface IRequestManager 8 | { 9 | Task IsRegistered( 10 | Guid id, 11 | CancellationToken cancellationToken = default(CancellationToken)); 12 | 13 | Task Register(Guid id, 14 | CancellationToken cancellationToken = default(CancellationToken)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Commands/IdentifiedCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace BuildingBlocks.Mediatr.Commands 5 | { 6 | public class IdentifiedCommand : 7 | IRequest 8 | where TCommand : IRequest 9 | { 10 | public Guid Id { get; } 11 | public TCommand Command { get; } 12 | 13 | public IdentifiedCommand(TCommand command, Guid id) 14 | { 15 | Id = id; 16 | Command = command; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/BuildingBlocks.Mvc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Swagger/BuildingBlocks.Swagger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/BuildingBlocks.AspnetCoreIdentity.RavenDB/BuildingBlocks.AspnetCoreIdentity.RavenDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Swagger/OperationFilterContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace BuildingBlocks.Swagger 6 | { 7 | internal static class OperationFilterContextExtensions 8 | { 9 | internal static bool HasAuthorize(this OperationFilterContext context) 10 | { 11 | var apiDescription = context.ApiDescription; 12 | 13 | return 14 | apiDescription.ControllerAttributes().OfType().Any() || 15 | apiDescription.ActionAttributes().OfType().Any(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/SetupFeatureFolders.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | 3 | using BuildingBlocks.Mvc; 4 | using Microsoft.AspNetCore.Mvc.Razor; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class SetupFeatureFolders 9 | { 10 | public static IServiceCollection AddFeatureFoldersSupport( 11 | this IServiceCollection services 12 | ) 13 | { 14 | services.Configure(options => 15 | { 16 | options.ViewLocationExpanders.Add(new FeaturesLocationExpander()); 17 | }); 18 | return services; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/BuildingBlocks.Mediatr.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/ModelStateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | 5 | namespace BuildingBlocks.Mvc 6 | { 7 | public static class ModelStateExtensions 8 | { 9 | public static BadRequestObjectResult GenerateBadRequestFromExceptions( 10 | this ModelStateDictionary modelState 11 | ) 12 | { 13 | var errors = modelState.Values 14 | .SelectMany(value => value.Errors) 15 | .Select(error => error.Exception.InnerException.Message) 16 | .ToArray(); 17 | 18 | return new BadRequestObjectResult(errors); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BuindingBlocks.Resilience/Http/Authorization.cs: -------------------------------------------------------------------------------- 1 | namespace BuindingBlocks.Resilience.Http 2 | { 3 | public struct Authorization 4 | { 5 | public string Token { get; } 6 | public string Method { get; } 7 | 8 | public Authorization( 9 | string token = null, 10 | string method = "Bearer" 11 | ) 12 | { 13 | Token = token; 14 | Method = method; 15 | } 16 | 17 | public static readonly Authorization Empty = new Authorization(); 18 | 19 | public static implicit operator Authorization(string token) => 20 | new Authorization(token); 21 | 22 | public bool IsEmpty => Token == null; 23 | } 24 | } -------------------------------------------------------------------------------- /src/BuildingBlocks.Idempotency/InMemoryRequestManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace BuildingBlocks.Idempotency 6 | { 7 | public class InMemoryRequestManager : IRequestManager 8 | { 9 | private readonly CircularQueue _queue = new CircularQueue(); 10 | 11 | public Task IsRegistered(Guid id, CancellationToken cancellationToken = default(CancellationToken)) => 12 | Task.FromResult(_queue.Contains(id)); 13 | 14 | public Task Register(Guid id, CancellationToken cancellationToken = default(CancellationToken)) 15 | { 16 | _queue.Enqueue(id); 17 | return Task.CompletedTask; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/FeaturesLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | 4 | namespace BuildingBlocks.Mvc 5 | { 6 | public class FeaturesLocationExpander : IViewLocationExpander 7 | 8 | { 9 | public void PopulateValues(ViewLocationExpanderContext context) 10 | { 11 | // nothing 12 | } 13 | 14 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 15 | { 16 | return new[] 17 | { 18 | "/Features/{1}/{0}.cshtml", // feature specific content 19 | "/Features/Shared/{0}.cshtml" // shared 20 | }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Autofac/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using Autofac.Core; 4 | using Autofac.Extensions.DependencyInjection; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class Startup 9 | { 10 | public static IServiceProvider ConvertToAutofac( 11 | this IServiceCollection services, 12 | params IModule[] modules 13 | ) 14 | { 15 | var container = new ContainerBuilder(); 16 | foreach (var module in modules) 17 | { 18 | container.RegisterModule(module); 19 | } 20 | container.Populate(services); 21 | return new AutofacServiceProvider(container.Build()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/BuindingBlocks.Resilience/Http/IHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace BuindingBlocks.Resilience.Http 6 | { 7 | public interface IHttpClient 8 | { 9 | Task GetStringAsync( 10 | string uri, 11 | Authorization authorization = default(Authorization), 12 | CancellationToken cancellationToken = default(CancellationToken) 13 | ); 14 | 15 | Task PutAsync( 16 | string uri, T item, 17 | string requestId = null, 18 | Authorization authorization = default(Authorization), 19 | CancellationToken cancellationToken = default(CancellationToken) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/HttpContextUser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using BuildingBlocks.Core; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace BuildingBlocks.Mvc 8 | { 9 | public class HttpContextUser : IUser 10 | { 11 | private readonly IHttpContextAccessor _httpContextAccessor; 12 | 13 | public HttpContextUser(IHttpContextAccessor httpContextAccessor) 14 | { 15 | _httpContextAccessor = httpContextAccessor; 16 | } 17 | 18 | public string Id => _httpContextAccessor.HttpContext.User.Claims.First(c => c.Type == "sub").Value; 19 | public string Name => _httpContextAccessor.HttpContext.User.Identity.Name; 20 | public IEnumerable Claims => _httpContextAccessor.HttpContext.User.Claims; 21 | } 22 | } -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/SetupPermissiveCors.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection 4 | { 5 | public static class SetupPermissiveCors 6 | { 7 | public static IServiceCollection AddPermissiveCors( 8 | this IServiceCollection services 9 | ) => services 10 | .AddCors(options => 11 | { 12 | options.AddPolicy("PermissiveCorsPolicy", builder => builder 13 | .AllowAnyOrigin() 14 | .AllowAnyMethod() 15 | .AllowAnyHeader() 16 | .AllowCredentials() 17 | ); 18 | }); 19 | 20 | public static IApplicationBuilder UsePermissiveCors(this IApplicationBuilder app) 21 | => app.UseCors("PermissiveCorsPolicy"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Idempotency/CircularQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Linq; 3 | 4 | namespace BuildingBlocks.Idempotency 5 | { 6 | public class CircularQueue 7 | { 8 | private readonly ConcurrentQueue _innerQueue = new ConcurrentQueue(); 9 | private readonly object _lockObject = new object(); 10 | 11 | public int Limit { get; } 12 | 13 | public CircularQueue(int limit = 1000) 14 | { 15 | Limit = limit; 16 | } 17 | 18 | public void Enqueue(T obj) 19 | { 20 | lock (_lockObject) 21 | { 22 | _innerQueue.Enqueue(obj); 23 | while (_innerQueue.Count > Limit && _innerQueue.TryDequeue(out T _)) ; 24 | } 25 | } 26 | 27 | public bool Contains(T value) 28 | { 29 | lock (_lockObject) 30 | { 31 | return _innerQueue.Contains(value); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/BuildingBlocks.Core/IApiInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | 4 | namespace BuildingBlocks.Core 5 | { 6 | public interface IApiInfo 7 | { 8 | string AuthenticationAuthority { get; } 9 | string JwtBearerAudience { get; } 10 | string Code { get; } 11 | string Title { get; } 12 | string Version { get; } 13 | Assembly ApplicationAssembly { get; } 14 | 15 | IDictionary Scopes { get; } 16 | SwaggerAuthInfo SwaggerAuthInfo { get; } 17 | 18 | } 19 | 20 | public class SwaggerAuthInfo 21 | { 22 | public string ClientId { get; } 23 | public string Secret { get; } 24 | public string Realm { get; } 25 | 26 | public SwaggerAuthInfo( 27 | string clientId, 28 | string secret, 29 | string realm 30 | ) 31 | { 32 | ClientId = clientId; 33 | Secret = secret; 34 | Realm = realm; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/BuildingBlocks.AspnetCoreIdentity.RavenDB/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Identity; 3 | using BuildingBlocks.AspnetCoreIdentity.RavenDB; 4 | using Raven.Client.Documents; 5 | 6 | // ReSharper disable once CheckNamespace 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | public static class Startup 10 | { 11 | public static IServiceCollection AddRavenDBIdentity( 12 | this IServiceCollection services, 13 | IDocumentStore documentStore, string databaseName 14 | ) 15 | { 16 | var session = documentStore.OpenAsyncSession(databaseName); 17 | services.AddIdentity(); 18 | 19 | services.AddScoped>( 20 | (sp) => new UserStore(session) 21 | ); 22 | services.AddScoped>( 23 | (sp) => new RoleStore(session) 24 | ); 25 | 26 | return services; 27 | } 28 | 29 | public static IApplicationBuilder UseRavenDBIdentity(this IApplicationBuilder app) => app; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Swagger/AuthorizeCheckOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using BuildingBlocks.Core; 4 | using Swashbuckle.AspNetCore.Swagger; 5 | using Swashbuckle.AspNetCore.SwaggerGen; 6 | 7 | namespace BuildingBlocks.Swagger 8 | { 9 | public class AuthorizeCheckOperationFilter : IOperationFilter 10 | { 11 | private readonly IApiInfo _apiInfo; 12 | 13 | public AuthorizeCheckOperationFilter(IApiInfo apiInfo) 14 | { 15 | _apiInfo = apiInfo; 16 | } 17 | 18 | public void Apply( 19 | Operation operation, 20 | OperationFilterContext context 21 | ) 22 | { 23 | if (!context.HasAuthorize()) return; 24 | 25 | operation.Responses.Add("401", new Response { Description = "Unauthorized" }); 26 | operation.Responses.Add("403", new Response { Description = "Forbidden" }); 27 | 28 | operation.Security = new List>> 29 | { 30 | new Dictionary> 31 | { 32 | {"oauth2", _apiInfo.Scopes.Keys.ToArray() } 33 | } 34 | }; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/BuindingBlocks.Resilience/Http/HttpRequestMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Net.Http.Headers; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace BuindingBlocks.Resilience.Http 6 | { 7 | public static class HttpRequestMessageExtensions 8 | { 9 | public static HttpRequestMessage CopyAuthorizationHeaderFrom( 10 | this HttpRequestMessage request, 11 | HttpContext context 12 | ) 13 | { 14 | var authorizationHeader = context 15 | .Request 16 | .Headers["Authorization"]; 17 | 18 | if (!string.IsNullOrEmpty(authorizationHeader)) 19 | { 20 | request.Headers.Add("Authorization", new string[] { authorizationHeader }); 21 | } 22 | 23 | return request; 24 | } 25 | 26 | public static HttpRequestMessage Apply( 27 | this HttpRequestMessage request, 28 | Authorization authorization 29 | ) 30 | { 31 | if (!authorization.IsEmpty) 32 | { 33 | request.Headers.Authorization = new AuthenticationHeaderValue( 34 | authorization.Method, 35 | authorization.Token 36 | ); 37 | } 38 | return request; 39 | } 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /src/BuildingBlocks.Mvc/SetupIdentity.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using BuildingBlocks.Core; 3 | using BuildingBlocks.Mvc; 4 | using Microsoft.AspNetCore.Authentication.JwtBearer; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | // ReSharper disable once CheckNamespace 8 | namespace Microsoft.Extensions.DependencyInjection 9 | { 10 | public static class SetupIdentity 11 | { 12 | public static IServiceCollection AddCustomIdentity( 13 | this IServiceCollection services, 14 | IApiInfo apiInfo 15 | ) 16 | { 17 | JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); 18 | 19 | services.AddAuthentication(options => 20 | { 21 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 22 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 23 | 24 | }).AddJwtBearer(options => 25 | { 26 | options.Authority = apiInfo.AuthenticationAuthority; 27 | options.RequireHttpsMetadata = false; 28 | options.Audience = apiInfo.JwtBearerAudience; 29 | }); 30 | 31 | services.AddSingleton(); 32 | services.AddScoped(); 33 | 34 | return services; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Validators/ValidatorsBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using FluentValidation; 5 | using MediatR; 6 | 7 | namespace BuildingBlocks.Mediatr.Validators 8 | { 9 | public class ValidatorsBehavior 10 | : IPipelineBehavior 11 | { 12 | private readonly IValidator[] _validators; 13 | 14 | public ValidatorsBehavior(IValidator[] validators) 15 | { 16 | _validators = validators; 17 | } 18 | 19 | public async Task Handle( 20 | TRequest request, 21 | CancellationToken cancellationToken, 22 | RequestHandlerDelegate next) 23 | { 24 | var failures = _validators 25 | .Select(validator => validator.Validate(request)) 26 | .SelectMany(result => result.Errors) 27 | .Where(error => error != null) 28 | .ToList(); 29 | 30 | if (failures.Any()) 31 | { 32 | throw new MediatrPipelineException( 33 | $"Command Validation Errors for type {typeof(TRequest).Name}", 34 | new ValidationException("Validation exception", failures) 35 | ); 36 | } 37 | 38 | return await next(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/BuildingBlocks.IdentityServer4.RavenDB/Extensions/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BuildingBlocks.IdentityServer4.RavenDB.Stores; 3 | using IdentityServer4.Stores; 4 | using Raven.Client.Documents; 5 | 6 | // ReSharper disable once CheckNamespace 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | // ReSharper disable once InconsistentNaming 10 | public static class Startup 11 | { 12 | public static IIdentityServerBuilder AddRavenDBConfigurationStore( 13 | this IIdentityServerBuilder builder, 14 | IDocumentStore documentStore, string databaseName, 15 | Action setup 16 | ) 17 | { 18 | var session = documentStore.OpenAsyncSession(databaseName); 19 | 20 | builder.Services 21 | .AddTransient((sp) => new ClientStore(session)) 22 | .AddTransient((sp) => new ResourceStore(session)); 23 | 24 | setup(new ClientStore(session), new ResourceStore(session)); 25 | 26 | return builder; 27 | } 28 | 29 | public static IIdentityServerBuilder AddRavenDBOperationalStore( 30 | this IIdentityServerBuilder builder, 31 | IDocumentStore documentStore, string databaseName 32 | ) 33 | { 34 | var session = documentStore.OpenAsyncSession(databaseName); 35 | 36 | builder.Services 37 | .AddScoped(sp => new PersistedGrantStore(session)); 38 | 39 | return builder; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Exceptions/ThrowMediatrPipelineException.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using BuildingBlocks.Mediatr.Validators; 3 | using FluentValidation; 4 | using FluentValidation.Results; 5 | 6 | namespace BuildingBlocks.Mediatr.Exceptions 7 | { 8 | [DebuggerStepThrough] 9 | public static class ThrowMediatrPipelineException 10 | { 11 | public static void IdentifiedCommandWithoutId() 12 | { 13 | throw new MediatrPipelineException( 14 | "Invalid IdentifiedCommand", 15 | new ValidationException("Validation Exception", 16 | new[] { new ValidationFailure("Id", "You need to specify a valid id.") } 17 | ) 18 | ); 19 | } 20 | 21 | public static void IdentifiedCommandWithoutInnerCommand() 22 | { 23 | throw new MediatrPipelineException( 24 | "Invalid IdentifiedCommand", 25 | new ValidationException( 26 | "Validation Exception", 27 | new[] { new ValidationFailure("Command", "You need to specify a valid command to run.") } 28 | ) 29 | ); 30 | } 31 | 32 | public static void CommandWasAlreadyExecuted() 33 | { 34 | throw new MediatrPipelineException( 35 | "Invalid IdentifiedCommand", 36 | new ValidationException("Validation Exception", 37 | new[] 38 | { 39 | new ValidationFailure("Id", "This command was already executed.") 40 | })); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/BuildingBlocks.IdentityServer4.RavenDB/Stores/ClientStore.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using IdentityServer4.Models; 3 | using IdentityServer4.Stores; 4 | using Raven.Client.Documents; 5 | using Raven.Client.Documents.Session; 6 | 7 | namespace BuildingBlocks.IdentityServer4.RavenDB.Stores 8 | { 9 | public interface IRavenDBClientStore : IClientStore 10 | { 11 | Task StoreAsync(Client client); 12 | Task HasStoredClients(); 13 | } 14 | internal class ClientStore : IRavenDBClientStore 15 | { 16 | private readonly IAsyncDocumentSession _session; 17 | 18 | public ClientStore(IAsyncDocumentSession session) 19 | { 20 | _session = session; 21 | } 22 | 23 | public async Task FindClientByIdAsync(string clientId) => 24 | (await _session.LoadAsync(RavenClient.DocId(clientId))); 25 | 26 | public async Task StoreAsync(Client client) 27 | { 28 | await _session.StoreAsync(client, RavenClient.DocId(client.ClientId)); 29 | await _session.SaveChangesAsync(); 30 | } 31 | 32 | public Task HasStoredClients() 33 | { 34 | return _session.Query() 35 | .Customize(o => o.WaitForNonStaleResults()) 36 | .AnyAsync(); 37 | } 38 | 39 | internal class RavenClient : Client 40 | { 41 | public string Id => DocId(ClientId); 42 | 43 | public static string DocId(string clientId) 44 | { 45 | return $"client/{clientId}"; 46 | } 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Commands/IdentifiedCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | 6 | using BuildingBlocks.Idempotency; 7 | using BuildingBlocks.Mediatr.Exceptions; 8 | 9 | namespace BuildingBlocks.Mediatr.Commands 10 | { 11 | public class IdentifiedCommandHandler 12 | : IRequestHandler, TResult> 13 | where TCommand : IRequest 14 | { 15 | private readonly IMediator _mediator; 16 | private readonly IRequestManager _requestManager; 17 | 18 | public IdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) 19 | { 20 | _mediator = mediator; 21 | _requestManager = requestManager; 22 | } 23 | 24 | public async Task Handle( 25 | IdentifiedCommand message, 26 | CancellationToken cancellationToken = default(CancellationToken) 27 | ) 28 | { 29 | if (message.Id == Guid.Empty) 30 | { 31 | ThrowMediatrPipelineException.IdentifiedCommandWithoutId(); 32 | } 33 | 34 | if (message.Command == null) 35 | { 36 | ThrowMediatrPipelineException.IdentifiedCommandWithoutInnerCommand(); 37 | } 38 | 39 | var alreadyRegistered = await _requestManager.IsRegistered(message.Id, cancellationToken); 40 | if (alreadyRegistered) 41 | { 42 | ThrowMediatrPipelineException.CommandWasAlreadyExecuted(); 43 | } 44 | 45 | await _requestManager.Register(message.Id, cancellationToken); 46 | var result = await _mediator.Send(message.Command, cancellationToken); 47 | return result; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/BuildingBlocks.AspnetCoreIdentity.RavenDB/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | 5 | namespace BuildingBlocks.AspnetCoreIdentity.RavenDB 6 | { 7 | public class User 8 | { 9 | private readonly List _claims = new List(); 10 | 11 | public string Id { get; set; } 12 | public string Name { get; set; } 13 | public string NormalizedName { get; set; } 14 | public string PasswordHash { get; set; } 15 | 16 | public IEnumerable Claims 17 | { 18 | get => _claims; 19 | internal set 20 | { 21 | if (value != null) _claims.AddRange(value); 22 | } 23 | } 24 | 25 | internal void AddClaim(SimplifiedClaim claim) 26 | { 27 | if (claim == null) 28 | { 29 | throw new ArgumentNullException(nameof(claim)); 30 | } 31 | 32 | _claims.Add(claim); 33 | } 34 | 35 | internal void RemoveClaim(SimplifiedClaim claim) 36 | { 37 | _claims.Remove(claim); 38 | } 39 | } 40 | 41 | public class SimplifiedClaim : IEquatable, IEquatable 42 | { 43 | public string Type { get; set; } 44 | public string Value { get; set; } 45 | 46 | public static implicit operator SimplifiedClaim(Claim original) => 47 | new SimplifiedClaim { Type = original.Type, Value = original.Value }; 48 | 49 | public static implicit operator Claim(SimplifiedClaim simplified) => 50 | new Claim(simplified.Type, simplified.Value); 51 | 52 | public bool Equals(SimplifiedClaim other) 53 | => Type == other.Type && Value == other.Value; 54 | 55 | public bool Equals(Claim other) 56 | => Type == other.Type && Value == other.Value; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Swagger/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | using Swashbuckle.AspNetCore.Examples; 5 | using Swashbuckle.AspNetCore.Swagger; 6 | 7 | using BuildingBlocks.Core; 8 | 9 | namespace BuildingBlocks.Swagger 10 | { 11 | public static class Startup 12 | { 13 | public static IServiceCollection AddCustomSwagger( 14 | this IServiceCollection services, 15 | IApiInfo apiInfo 16 | 17 | ) => services 18 | .AddSwaggerGen(options => 19 | { 20 | options.DescribeAllEnumsAsStrings(); 21 | 22 | options.SwaggerDoc(apiInfo.Version, new Info 23 | { 24 | Title = apiInfo.Title, 25 | Version = apiInfo.Version, 26 | Description = apiInfo.Version 27 | }); 28 | 29 | if (apiInfo.AuthenticationAuthority != null) 30 | { 31 | options.AddSecurityDefinition("oauth2", new OAuth2Scheme 32 | { 33 | Type = "oauth2", 34 | Flow = "implicit", 35 | AuthorizationUrl = $"{apiInfo.AuthenticationAuthority}/connect/authorize", 36 | TokenUrl = $"{apiInfo.AuthenticationAuthority}/connect/token", 37 | Scopes = apiInfo.Scopes 38 | }); 39 | } 40 | 41 | options.OperationFilter(apiInfo); 42 | options.OperationFilter(); 43 | options.OperationFilter(); 44 | }); 45 | 46 | public static IApplicationBuilder UseCustomSwagger( 47 | this IApplicationBuilder app, 48 | IApiInfo apiInfo 49 | ) => app 50 | .UseSwagger() 51 | .UseSwaggerUI(c => 52 | { 53 | c.SwaggerEndpoint($"/swagger/{apiInfo.Version}/swagger.json", $"{apiInfo.Title} {apiInfo.Version}"); 54 | if (apiInfo.AuthenticationAuthority != null) 55 | { 56 | c.ConfigureOAuth2( 57 | apiInfo.SwaggerAuthInfo.ClientId, 58 | apiInfo.SwaggerAuthInfo.Secret, 59 | apiInfo.SwaggerAuthInfo.Realm, 60 | $"{apiInfo.Title} - ${apiInfo.Version} - Swagger UI" 61 | ); 62 | } 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Exceptions/HttpGlobalExceptionFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Net; 3 | using BuildingBlocks.Mediatr.Validators; 4 | using FluentValidation; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.Filters; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace BuildingBlocks.Mediatr.Exceptions 11 | { 12 | public class HttpGlobalExceptionFilter : IExceptionFilter 13 | { 14 | private readonly IHostingEnvironment _env; 15 | private readonly ILogger _logger; 16 | 17 | public HttpGlobalExceptionFilter( 18 | IHostingEnvironment env, 19 | ILogger logger 20 | ) 21 | { 22 | _env = env; 23 | _logger = logger; 24 | } 25 | 26 | public void OnException(ExceptionContext context) 27 | { 28 | _logger.LogError(new EventId(context.Exception.HResult), 29 | context.Exception, 30 | context.Exception.Message); 31 | 32 | if (context.Exception.GetType() == typeof(MediatrPipelineException)) 33 | { 34 | var validationException = context.Exception.InnerException as ValidationException; 35 | if (validationException != null) 36 | { 37 | var json = new JsonErrorResponse 38 | { 39 | Messages = validationException.Errors 40 | .Select(e => e.ErrorMessage) 41 | .ToArray() 42 | }; 43 | 44 | context.Result = new BadRequestObjectResult(json); 45 | } 46 | context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; 47 | } 48 | else 49 | { 50 | var json = new JsonErrorResponse 51 | { 52 | Messages = new[] 53 | { 54 | "Internal Error. Try again later.", 55 | context.Exception.GetType().ToString(), 56 | context.Exception.Message 57 | } 58 | }; 59 | 60 | context.Result = new ObjectResult(json) { StatusCode = 500 }; 61 | context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 62 | } 63 | context.ExceptionHandled = true; 64 | } 65 | 66 | public class JsonErrorResponse 67 | { 68 | public string[] Messages { get; set; } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/BuindingBlocks.Resilience/Http/ResilientHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | using Polly; 10 | using Polly.Wrap; 11 | 12 | namespace BuindingBlocks.Resilience.Http 13 | { 14 | public delegate IEnumerable PolicyFactory(string origin); 15 | 16 | public class ResilientHttpClient : IHttpClient 17 | { 18 | private readonly PolicyFactory _policyFactory; 19 | private readonly StandardHttpClient _standardHttpClient; 20 | 21 | private readonly ConcurrentDictionary _policyWrappers = 22 | new ConcurrentDictionary(); 23 | 24 | public ResilientHttpClient( 25 | PolicyFactory policyFactory, 26 | IHttpContextAccessor accessor 27 | ) 28 | { 29 | _policyFactory = policyFactory; 30 | _standardHttpClient = new StandardHttpClient(accessor); 31 | 32 | } 33 | 34 | public Task GetStringAsync(string uri, Authorization authorization = default(Authorization), 35 | CancellationToken cancellationToken = default(CancellationToken)) => HttpInvoker( 36 | new Uri(uri).GetOriginFromUri(), 37 | () => _standardHttpClient.GetStringAsync( 38 | uri, authorization, cancellationToken 39 | ) 40 | ); 41 | 42 | public Task PutAsync(string uri, T item, string requestId = null, Authorization authorization = default(Authorization), 43 | CancellationToken cancellationToken = default(CancellationToken)) => HttpInvoker( 44 | new Uri(uri).GetOriginFromUri(), 45 | () => _standardHttpClient.PutAsync( 46 | uri, item, requestId, authorization, cancellationToken 47 | ) 48 | ); 49 | 50 | private async Task HttpInvoker(string origin, Func> action) 51 | { 52 | var normalizedOrigin = origin?.Trim()?.ToLower(); 53 | 54 | if (!_policyWrappers.TryGetValue(normalizedOrigin, out var policyWrap)) 55 | { 56 | var policies = _policyFactory(normalizedOrigin).ToArray(); 57 | policyWrap = Policy.WrapAsync(policies); 58 | _policyWrappers.TryAdd(normalizedOrigin, policyWrap); 59 | } 60 | 61 | return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin)); 62 | } 63 | } 64 | 65 | public static class UriExtensions 66 | { 67 | public static string GetOriginFromUri(this Uri uri) => 68 | $"{uri.Scheme}://{uri.DnsSafeHost}:{uri.Port}"; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/BuindingBlocks.Resilience/Http/StandardHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.AspNetCore.Http; 4 | using System.Net.Http; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | 9 | namespace BuindingBlocks.Resilience.Http 10 | { 11 | public class StandardHttpClient : IHttpClient, IDisposable 12 | { 13 | private readonly IHttpContextAccessor _httpContextAccessor; 14 | private readonly HttpClient _httpClient; 15 | 16 | public StandardHttpClient(IHttpContextAccessor httpContextAccessor) 17 | { 18 | _httpContextAccessor = httpContextAccessor; 19 | _httpClient = new HttpClient(); 20 | } 21 | 22 | public async Task GetStringAsync( 23 | string uri, 24 | Authorization authorization = default(Authorization), 25 | CancellationToken cancellationToken = default(CancellationToken) 26 | ) 27 | { 28 | var message = new HttpRequestMessage(HttpMethod.Get, uri) 29 | .CopyAuthorizationHeaderFrom(_httpContextAccessor.HttpContext) 30 | .Apply(authorization); 31 | 32 | var response = await _httpClient.SendAsync(message, cancellationToken); 33 | 34 | if (response.StatusCode == HttpStatusCode.InternalServerError) 35 | { 36 | throw new HttpRequestException(); 37 | } 38 | 39 | return await response.Content.ReadAsStringAsync(); 40 | } 41 | 42 | public async Task PutAsync( 43 | string uri, 44 | T item, string requestId = null, 45 | Authorization authorization = default(Authorization), 46 | CancellationToken cancellationToken = default(CancellationToken)) 47 | { 48 | var requestMessage = new HttpRequestMessage(HttpMethod.Put, uri) 49 | .CopyAuthorizationHeaderFrom(_httpContextAccessor.HttpContext) 50 | .Apply(authorization); 51 | 52 | requestMessage.Content = new StringContent( 53 | JsonConvert.SerializeObject(item), 54 | System.Text.Encoding.UTF8, "application/json" 55 | ); 56 | 57 | if (requestId != null) 58 | { 59 | requestMessage.Headers.Add("x-requestid", requestId); 60 | } 61 | 62 | var response = await _httpClient.SendAsync(requestMessage, cancellationToken); 63 | 64 | if (response.StatusCode == HttpStatusCode.InternalServerError) 65 | { 66 | throw new HttpRequestException(); 67 | } 68 | 69 | return response; 70 | } 71 | 72 | public void Dispose() 73 | { 74 | _httpClient?.Dispose(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/BuildingBlocks.IdentityServer4.RavenDB/Stores/PersistedGrantStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using IdentityServer4.Models; 5 | using IdentityServer4.Stores; 6 | 7 | using Raven.Client.Documents; 8 | using Raven.Client.Documents.Linq; 9 | using Raven.Client.Documents.Session; 10 | 11 | namespace BuildingBlocks.IdentityServer4.RavenDB.Stores 12 | { 13 | internal class PersistedGrantStore : IPersistedGrantStore 14 | { 15 | private readonly IAsyncDocumentSession _session; 16 | 17 | public PersistedGrantStore(IAsyncDocumentSession session) 18 | { 19 | _session = session; 20 | } 21 | 22 | public async Task StoreAsync(PersistedGrant grant) 23 | { 24 | await _session.StoreAsync(grant, RavenPersistedGrant.DocId(grant.Key)); 25 | await _session.SaveChangesAsync(); 26 | } 27 | 28 | public async Task GetAsync(string key) => 29 | (await _session.LoadAsync(RavenPersistedGrant.DocId(key))); 30 | 31 | public async Task> GetAllAsync(string subjectId) 32 | { 33 | var grants = await _session 34 | .Query() 35 | .Customize(opt => opt.WaitForNonStaleResults()) 36 | .Where(g => g.SubjectId == subjectId) 37 | .ToListAsync(); 38 | 39 | return grants; 40 | } 41 | 42 | 43 | public async Task RemoveAsync(string key) 44 | { 45 | _session.Delete(RavenPersistedGrant.DocId(key)); 46 | await _session.SaveChangesAsync(); 47 | } 48 | 49 | public async Task RemoveAllAsync(string subjectId, string clientId) 50 | { 51 | await RemoveWhereAsync(g => (g.SubjectId == subjectId) && (g.ClientId == clientId)); 52 | } 53 | 54 | public async Task RemoveAllAsync(string subjectId, string clientId, string type) 55 | { 56 | await RemoveWhereAsync(g => (g.SubjectId == subjectId) && (g.ClientId == clientId) && (g.Type == type)); 57 | } 58 | 59 | private async Task RemoveWhereAsync(Func @where) 60 | { 61 | var itemsToDelete = 62 | await _session 63 | .Query() 64 | .Customize(opt => opt.WaitForNonStaleResults()) 65 | .Where(g => @where.Invoke(g)) 66 | .ToListAsync(); 67 | 68 | foreach (var grant in itemsToDelete) 69 | _session.Delete(grant); 70 | 71 | await _session.SaveChangesAsync(); 72 | } 73 | 74 | internal class RavenPersistedGrant : PersistedGrant 75 | { 76 | public string Id => DocId(Key); 77 | 78 | public static string DocId(string key) 79 | { 80 | return $"scope/{key}"; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/BuildingBlocks.IdentityServer4.RavenDB/Stores/ResourceStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using IdentityServer4.Models; 5 | using IdentityServer4.Stores; 6 | using Raven.Client.Documents; 7 | using Raven.Client.Documents.Linq; 8 | using Raven.Client.Documents.Session; 9 | 10 | namespace BuildingBlocks.IdentityServer4.RavenDB.Stores 11 | { 12 | public interface IRavenDBResourceStore : IResourceStore 13 | { 14 | Task StoreAsync(IdentityResource identity); 15 | Task StoreAsync(ApiResource api); 16 | Task HasStoredIdentities(); 17 | Task HasStoredApis(); 18 | } 19 | 20 | internal class ResourceStore : IRavenDBResourceStore 21 | { 22 | private readonly IAsyncDocumentSession _session; 23 | 24 | public ResourceStore(IAsyncDocumentSession session) 25 | { 26 | _session = session; 27 | } 28 | 29 | public async Task> FindIdentityResourcesByScopeAsync(IEnumerable scopeNames) 30 | { 31 | var scopes = scopeNames.ToArray(); 32 | 33 | var identities = await _session.Query() 34 | .Where(r => r.Name.In(scopes)) 35 | .ToListAsync(); 36 | 37 | return identities; //.Select(identity => identity.ToModel()); 38 | } 39 | 40 | public async Task> FindApiResourcesByScopeAsync(IEnumerable scopeNames) 41 | { 42 | var scopes = scopeNames.ToArray(); 43 | 44 | var apis = await _session.Query() 45 | .Where(r => r.Name.In(scopes)) 46 | .ToListAsync(); 47 | 48 | return apis; 49 | } 50 | 51 | public async Task FindApiResourceAsync(string name) 52 | { 53 | var api = await _session.Query() 54 | .Where(r => r.Name == name) 55 | .FirstOrDefaultAsync(); 56 | 57 | return api; 58 | } 59 | 60 | public async Task GetAllResourcesAsync() 61 | { 62 | var identities = await _session.Query() 63 | .ToListAsync(); 64 | 65 | var apis = await _session.Query() 66 | .ToListAsync(); 67 | 68 | var result = new Resources( 69 | identities, //.Select(identity => identity.ToModel()), 70 | apis //.Select(api => api.ToModel()) 71 | ); 72 | 73 | return result; 74 | } 75 | 76 | public async Task StoreAsync(IdentityResource identity) 77 | { 78 | await _session.StoreAsync(identity); 79 | await _session.SaveChangesAsync(); 80 | } 81 | 82 | public async Task StoreAsync(ApiResource api) 83 | { 84 | await _session.StoreAsync(api); 85 | await _session.SaveChangesAsync(); 86 | } 87 | 88 | public Task HasStoredIdentities() 89 | { 90 | return _session.Query() 91 | .Customize(o => o.WaitForNonStaleResults()) 92 | .AnyAsync(); 93 | } 94 | 95 | public Task HasStoredApis() 96 | { 97 | return _session.Query() 98 | .Customize(o => o.WaitForNonStaleResults()) 99 | .AnyAsync(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/BuildingBlocks.Mediatr/Autofac/MediatrModule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | using FluentValidation; 6 | using MediatR; 7 | using Autofac; 8 | using Module = Autofac.Module; 9 | 10 | using BuildingBlocks.Mediatr.Commands; 11 | using BuildingBlocks.Mediatr.Validators; 12 | 13 | 14 | namespace BuildingBlocks.Mediatr.Autofac 15 | { 16 | public class MediatrModule : Module 17 | { 18 | private readonly Assembly _applicationAssembly; 19 | 20 | private MediatrModule(Assembly applicationAssembly) 21 | { 22 | _applicationAssembly = applicationAssembly; 23 | } 24 | 25 | public static MediatrModule Create(Assembly applicationAssembly) 26 | => new MediatrModule(applicationAssembly); 27 | 28 | protected override void Load(ContainerBuilder builder) 29 | { 30 | builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) 31 | .AsImplementedInterfaces(); 32 | 33 | builder.RegisterAssemblyTypes(_applicationAssembly) 34 | .AsClosedTypesOf(typeof(IRequestHandler<,>)); 35 | 36 | var handlers = _applicationAssembly.GetTypes() 37 | .Where(t => t.IsClosedTypeOf(typeof(IRequestHandler<,>))) 38 | .ToList(); 39 | 40 | handlers.ForEach(t => 41 | { 42 | var localHandlers = t.GetInterfaces() 43 | .Where(@interface => @interface.IsClosedTypeOf(typeof(IRequestHandler<,>))); 44 | 45 | foreach (var localHandler in localHandlers) 46 | { 47 | var implementation = typeof(IdentifiedCommandHandler<,>) 48 | .MakeGenericType(localHandler.GenericTypeArguments); 49 | 50 | var arg0 = typeof(IdentifiedCommand<,>) 51 | .MakeGenericType(localHandler.GenericTypeArguments); 52 | var arg1 = localHandler.GenericTypeArguments[1]; 53 | 54 | var service = typeof(IRequestHandler<,>) 55 | .MakeGenericType(arg0, arg1); 56 | 57 | builder.RegisterType(implementation).As(service); 58 | } 59 | }); 60 | 61 | var sharedLogicMediatr = typeof(IdentifiedCommand<,>).GetTypeInfo().Assembly; 62 | builder.RegisterAssemblyTypes(sharedLogicMediatr) 63 | .AsClosedTypesOf(typeof(IRequestHandler<,>)); 64 | 65 | builder 66 | .RegisterAssemblyTypes(sharedLogicMediatr) 67 | .Where(t => t.IsClosedTypeOf(typeof(IValidator<>))) 68 | .AsImplementedInterfaces(); 69 | 70 | builder 71 | .RegisterAssemblyTypes(_applicationAssembly) 72 | .AsClosedTypesOf(typeof(IValidator<>)); 73 | 74 | builder.Register(context => 75 | { 76 | var componentContext = context.Resolve(); 77 | return t => 78 | { 79 | return componentContext.TryResolve(t, out var o) ? o : null; 80 | }; 81 | }); 82 | 83 | builder.Register(context => 84 | { 85 | var componentContext = context.Resolve(); 86 | 87 | return t => 88 | { 89 | var resolved = (IEnumerable)componentContext.Resolve(typeof(IEnumerable<>).MakeGenericType(t)); 90 | return resolved; 91 | }; 92 | }); 93 | 94 | builder.RegisterGeneric(typeof(ValidatorsBehavior<,>)).As(typeof(IPipelineBehavior<,>)); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/BuildingBlocks.AspnetCoreIdentity.RavenDB/RoleStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Raven.Client.Documents; 7 | using Raven.Client.Documents.Session; 8 | using Raven.Client.Exceptions; 9 | 10 | namespace BuildingBlocks.AspnetCoreIdentity.RavenDB 11 | { 12 | public class RoleStore : 13 | IQueryableRoleStore 14 | { 15 | public IdentityErrorDescriber ErrorDescriber { get; } 16 | 17 | public RoleStore( 18 | IAsyncDocumentSession session, 19 | IdentityErrorDescriber errorDescriber = null 20 | ) 21 | { 22 | ErrorDescriber = errorDescriber; 23 | 24 | 25 | session.Advanced.UseOptimisticConcurrency = true; 26 | Session = session; 27 | } 28 | 29 | public IAsyncDocumentSession Session { get; } 30 | 31 | public IQueryable Roles => throw new NotSupportedException(); 32 | 33 | public Task SaveChanges( 34 | CancellationToken cancellationToken = default(CancellationToken) 35 | ) => Session.SaveChangesAsync(cancellationToken); 36 | 37 | 38 | #region IDisposable 39 | private void ThrowIfDisposed() 40 | { 41 | if (_disposed) 42 | { 43 | throw new ObjectDisposedException(GetType().Name); 44 | } 45 | } 46 | 47 | private bool _disposed; 48 | public void Dispose() 49 | { 50 | _disposed = true; 51 | } 52 | #endregion 53 | 54 | #region IQueryableRoleStore 55 | public async Task CreateAsync(Role role, CancellationToken cancellationToken) 56 | { 57 | cancellationToken.ThrowIfCancellationRequested(); 58 | ThrowIfDisposed(); 59 | 60 | if (role == null) 61 | { 62 | throw new ArgumentNullException(nameof(role)); 63 | } 64 | 65 | await Session.StoreAsync(role, cancellationToken); 66 | await SaveChanges(cancellationToken); 67 | 68 | return IdentityResult.Success; 69 | } 70 | 71 | public async Task UpdateAsync(Role role, CancellationToken cancellationToken) 72 | { 73 | cancellationToken.ThrowIfCancellationRequested(); 74 | ThrowIfDisposed(); 75 | if (role == null) 76 | { 77 | throw new ArgumentNullException(nameof(role)); 78 | } 79 | 80 | 81 | await Session.StoreAsync(role, cancellationToken); 82 | 83 | try 84 | { 85 | await SaveChanges(cancellationToken); 86 | } 87 | catch (ConcurrencyException) 88 | { 89 | return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); 90 | } 91 | 92 | return IdentityResult.Success; 93 | } 94 | 95 | public async Task DeleteAsync(Role role, CancellationToken cancellationToken) 96 | { 97 | cancellationToken.ThrowIfCancellationRequested(); 98 | ThrowIfDisposed(); 99 | if (role == null) 100 | { 101 | throw new ArgumentNullException(nameof(role)); 102 | } 103 | 104 | Session.Delete(role.Id); 105 | 106 | try 107 | { 108 | await SaveChanges(cancellationToken); 109 | } 110 | catch (ConcurrencyException) 111 | { 112 | return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); 113 | } 114 | 115 | return IdentityResult.Success; 116 | } 117 | 118 | public Task GetRoleIdAsync(Role role, CancellationToken cancellationToken) 119 | { 120 | cancellationToken.ThrowIfCancellationRequested(); 121 | ThrowIfDisposed(); 122 | 123 | if (role == null) 124 | { 125 | throw new ArgumentNullException(nameof(role)); 126 | } 127 | 128 | return Task.FromResult(role.Id); 129 | } 130 | 131 | public Task GetRoleNameAsync(Role role, CancellationToken cancellationToken) 132 | { 133 | cancellationToken.ThrowIfCancellationRequested(); 134 | ThrowIfDisposed(); 135 | 136 | if (role == null) 137 | { 138 | throw new ArgumentNullException(nameof(role)); 139 | } 140 | 141 | return Task.FromResult(role.Name); 142 | } 143 | 144 | public Task SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken) 145 | { 146 | cancellationToken.ThrowIfCancellationRequested(); 147 | ThrowIfDisposed(); 148 | if (role == null) 149 | { 150 | throw new ArgumentNullException(nameof(role)); 151 | } 152 | role.Name = roleName; 153 | return Task.CompletedTask; 154 | } 155 | 156 | public Task GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken) 157 | { 158 | cancellationToken.ThrowIfCancellationRequested(); 159 | ThrowIfDisposed(); 160 | 161 | if (role == null) 162 | { 163 | throw new ArgumentNullException(nameof(role)); 164 | } 165 | 166 | return Task.FromResult(role.NormalizedName); 167 | } 168 | 169 | public Task SetNormalizedRoleNameAsync(Role role, string normalizedName, CancellationToken cancellationToken) 170 | { 171 | cancellationToken.ThrowIfCancellationRequested(); 172 | ThrowIfDisposed(); 173 | if (role == null) 174 | { 175 | throw new ArgumentNullException(nameof(role)); 176 | } 177 | role.NormalizedName = normalizedName; 178 | return Task.CompletedTask; 179 | } 180 | 181 | public Task FindByIdAsync(string roleId, CancellationToken cancellationToken) 182 | { 183 | cancellationToken.ThrowIfCancellationRequested(); 184 | ThrowIfDisposed(); 185 | 186 | return Session.LoadAsync(roleId, cancellationToken); 187 | } 188 | 189 | public Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) 190 | { 191 | cancellationToken.ThrowIfCancellationRequested(); 192 | ThrowIfDisposed(); 193 | 194 | return Session.Query().FirstOrDefaultAsync( 195 | role => role.NormalizedName == normalizedRoleName, token: cancellationToken 196 | ); 197 | } 198 | #endregion 199 | 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.svclog 84 | *.scc 85 | 86 | # Chutzpah Test files 87 | _Chutzpah* 88 | 89 | # Visual C++ cache files 90 | ipch/ 91 | *.aps 92 | *.ncb 93 | *.opendb 94 | *.opensdf 95 | *.sdf 96 | *.cachefile 97 | *.VC.db 98 | *.VC.VC.opendb 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # Visual Studio Trace Files 107 | *.e2e 108 | 109 | # TFS 2012 Local Workspace 110 | $tf/ 111 | 112 | # Guidance Automation Toolkit 113 | *.gpState 114 | 115 | # ReSharper is a .NET coding add-in 116 | _ReSharper*/ 117 | *.[Rr]e[Ss]harper 118 | *.DotSettings.user 119 | 120 | # JustCode is a .NET coding add-in 121 | .JustCode 122 | 123 | # TeamCity is a build add-in 124 | _TeamCity* 125 | 126 | # DotCover is a Code Coverage Tool 127 | *.dotCover 128 | 129 | # AxoCover is a Code Coverage Tool 130 | .axoCover/* 131 | !.axoCover/settings.json 132 | 133 | # Visual Studio code coverage results 134 | *.coverage 135 | *.coveragexml 136 | 137 | # NCrunch 138 | _NCrunch_* 139 | .*crunch*.local.xml 140 | nCrunchTemp_* 141 | 142 | # MightyMoose 143 | *.mm.* 144 | AutoTest.Net/ 145 | 146 | # Web workbench (sass) 147 | .sass-cache/ 148 | 149 | # Installshield output folder 150 | [Ee]xpress/ 151 | 152 | # DocProject is a documentation generator add-in 153 | DocProject/buildhelp/ 154 | DocProject/Help/*.HxT 155 | DocProject/Help/*.HxC 156 | DocProject/Help/*.hhc 157 | DocProject/Help/*.hhk 158 | DocProject/Help/*.hhp 159 | DocProject/Help/Html2 160 | DocProject/Help/html 161 | 162 | # Click-Once directory 163 | publish/ 164 | 165 | # Publish Web Output 166 | *.[Pp]ublish.xml 167 | *.azurePubxml 168 | # Note: Comment the next line if you want to checkin your web deploy settings, 169 | # but database connection strings (with potential passwords) will be unencrypted 170 | *.pubxml 171 | *.publishproj 172 | 173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 174 | # checkin your Azure Web App publish settings, but sensitive information contained 175 | # in these scripts will be unencrypted 176 | PublishScripts/ 177 | 178 | # NuGet Packages 179 | *.nupkg 180 | # The packages folder can be ignored because of Package Restore 181 | **/[Pp]ackages/* 182 | # except build/, which is used as an MSBuild target. 183 | !**/[Pp]ackages/build/ 184 | # Uncomment if necessary however generally it will be regenerated when needed 185 | #!**/[Pp]ackages/repositories.config 186 | # NuGet v3's project.json files produces more ignorable files 187 | *.nuget.props 188 | *.nuget.targets 189 | 190 | # Microsoft Azure Build Output 191 | csx/ 192 | *.build.csdef 193 | 194 | # Microsoft Azure Emulator 195 | ecf/ 196 | rcf/ 197 | 198 | # Windows Store app package directories and files 199 | AppPackages/ 200 | BundleArtifacts/ 201 | Package.StoreAssociation.xml 202 | _pkginfo.txt 203 | *.appx 204 | 205 | # Visual Studio cache files 206 | # files ending in .cache can be ignored 207 | *.[Cc]ache 208 | # but keep track of directories ending in .cache 209 | !*.[Cc]ache/ 210 | 211 | # Others 212 | ClientBin/ 213 | ~$* 214 | *~ 215 | *.dbmdl 216 | *.dbproj.schemaview 217 | *.jfm 218 | *.pfx 219 | *.publishsettings 220 | orleans.codegen.cs 221 | 222 | # Including strong name files can present a security risk 223 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 224 | #*.snk 225 | 226 | # Since there are multiple workflows, uncomment next line to ignore bower_components 227 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 228 | #bower_components/ 229 | 230 | # RIA/Silverlight projects 231 | Generated_Code/ 232 | 233 | # Backup & report files from converting an old project file 234 | # to a newer Visual Studio version. Backup files are not needed, 235 | # because we have git ;-) 236 | _UpgradeReport_Files/ 237 | Backup*/ 238 | UpgradeLog*.XML 239 | UpgradeLog*.htm 240 | 241 | # SQL Server files 242 | *.mdf 243 | *.ldf 244 | *.ndf 245 | 246 | # Business Intelligence projects 247 | *.rdl.data 248 | *.bim.layout 249 | *.bim_*.settings 250 | 251 | # Microsoft Fakes 252 | FakesAssemblies/ 253 | 254 | # GhostDoc plugin setting file 255 | *.GhostDoc.xml 256 | 257 | # Node.js Tools for Visual Studio 258 | .ntvs_analysis.dat 259 | node_modules/ 260 | 261 | # TypeScript v1 declaration files 262 | typings/ 263 | 264 | # Visual Studio 6 build log 265 | *.plg 266 | 267 | # Visual Studio 6 workspace options file 268 | *.opt 269 | 270 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 271 | *.vbw 272 | 273 | # Visual Studio LightSwitch build output 274 | **/*.HTMLClient/GeneratedArtifacts 275 | **/*.DesktopClient/GeneratedArtifacts 276 | **/*.DesktopClient/ModelManifest.xml 277 | **/*.Server/GeneratedArtifacts 278 | **/*.Server/ModelManifest.xml 279 | _Pvt_Extensions 280 | 281 | # Paket dependency manager 282 | .paket/paket.exe 283 | paket-files/ 284 | 285 | # FAKE - F# Make 286 | .fake/ 287 | 288 | # JetBrains Rider 289 | .idea/ 290 | *.sln.iml 291 | 292 | # CodeRush 293 | .cr/ 294 | 295 | # Python Tools for Visual Studio (PTVS) 296 | __pycache__/ 297 | *.pyc 298 | 299 | # Cake - Uncomment if you are using it 300 | # tools/** 301 | # !tools/packages.config 302 | 303 | # Tabs Studio 304 | *.tss 305 | 306 | # Telerik's JustMock configuration file 307 | *.jmconfig 308 | 309 | # BizTalk build output 310 | *.btp.cs 311 | *.btm.cs 312 | *.odx.cs 313 | *.xsd.cs 314 | 315 | # OpenCover UI analysis results 316 | OpenCover/ 317 | 318 | # Azure Stream Analytics local run output 319 | ASALocalRun/ 320 | 321 | # MSBuild Binary and Structured Log 322 | *.binlog -------------------------------------------------------------------------------- /BuildingBlocks.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2024 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0219332F-4EB1-4036-9960-D2E1F921F454}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.Core", "src\BuildingBlocks.Core\BuildingBlocks.Core.csproj", "{A5C515E7-E5E2-4BA8-8E1D-56B9E2AE542B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.Autofac", "src\BuildingBlocks.Autofac\BuildingBlocks.Autofac.csproj", "{612D9EE0-191E-4223-9DA9-1677CEAF198B}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.Swagger", "src\BuildingBlocks.Swagger\BuildingBlocks.Swagger.csproj", "{C6CB91A9-5D36-41FA-A0F5-74F568A501EC}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.Mvc", "src\BuildingBlocks.Mvc\BuildingBlocks.Mvc.csproj", "{11570978-BE74-4086-A44D-67B271365ACB}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.Mediatr", "src\BuildingBlocks.Mediatr\BuildingBlocks.Mediatr.csproj", "{0122CC47-AD82-4D2B-A4CC-CED322D7755D}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.Idempotency", "src\BuildingBlocks.Idempotency\BuildingBlocks.Idempotency.csproj", "{2BBB0B0F-F2DF-4F96-8ABA-57E1F8735AC3}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuindingBlocks.Resilience", "src\BuindingBlocks.Resilience\BuindingBlocks.Resilience.csproj", "{848A3936-3F34-4398-97EC-E6B1D839A8D8}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.IdentityServer4.RavenDB", "src\BuildingBlocks.IdentityServer4.RavenDB\BuildingBlocks.IdentityServer4.RavenDB.csproj", "{91C1F84A-15D1-4596-9BF3-464988B739AB}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingBlocks.AspnetCoreIdentity.RavenDB", "src\BuildingBlocks.AspnetCoreIdentity.RavenDB\BuildingBlocks.AspnetCoreIdentity.RavenDB.csproj", "{A5D7048A-756E-46F1-BA5A-7CC4FA6A25CC}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {A5C515E7-E5E2-4BA8-8E1D-56B9E2AE542B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {A5C515E7-E5E2-4BA8-8E1D-56B9E2AE542B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {A5C515E7-E5E2-4BA8-8E1D-56B9E2AE542B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {A5C515E7-E5E2-4BA8-8E1D-56B9E2AE542B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {612D9EE0-191E-4223-9DA9-1677CEAF198B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {612D9EE0-191E-4223-9DA9-1677CEAF198B}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {612D9EE0-191E-4223-9DA9-1677CEAF198B}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {612D9EE0-191E-4223-9DA9-1677CEAF198B}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {C6CB91A9-5D36-41FA-A0F5-74F568A501EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {C6CB91A9-5D36-41FA-A0F5-74F568A501EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {C6CB91A9-5D36-41FA-A0F5-74F568A501EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C6CB91A9-5D36-41FA-A0F5-74F568A501EC}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {11570978-BE74-4086-A44D-67B271365ACB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {11570978-BE74-4086-A44D-67B271365ACB}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {11570978-BE74-4086-A44D-67B271365ACB}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {11570978-BE74-4086-A44D-67B271365ACB}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {0122CC47-AD82-4D2B-A4CC-CED322D7755D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {0122CC47-AD82-4D2B-A4CC-CED322D7755D}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {0122CC47-AD82-4D2B-A4CC-CED322D7755D}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {0122CC47-AD82-4D2B-A4CC-CED322D7755D}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {2BBB0B0F-F2DF-4F96-8ABA-57E1F8735AC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {2BBB0B0F-F2DF-4F96-8ABA-57E1F8735AC3}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {2BBB0B0F-F2DF-4F96-8ABA-57E1F8735AC3}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {2BBB0B0F-F2DF-4F96-8ABA-57E1F8735AC3}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {618C4AE0-8691-4C89-ABB4-24C19E13779B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {618C4AE0-8691-4C89-ABB4-24C19E13779B}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {618C4AE0-8691-4C89-ABB4-24C19E13779B}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {618C4AE0-8691-4C89-ABB4-24C19E13779B}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {7CEEBFEB-89A5-48DA-A261-694E35529D91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {7CEEBFEB-89A5-48DA-A261-694E35529D91}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {7CEEBFEB-89A5-48DA-A261-694E35529D91}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {7CEEBFEB-89A5-48DA-A261-694E35529D91}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {178904AB-74A2-4A74-923D-D706A7D7E67E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {178904AB-74A2-4A74-923D-D706A7D7E67E}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {178904AB-74A2-4A74-923D-D706A7D7E67E}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {178904AB-74A2-4A74-923D-D706A7D7E67E}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {848A3936-3F34-4398-97EC-E6B1D839A8D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {848A3936-3F34-4398-97EC-E6B1D839A8D8}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {848A3936-3F34-4398-97EC-E6B1D839A8D8}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {848A3936-3F34-4398-97EC-E6B1D839A8D8}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {256110C6-374A-44C5-860C-7F717B542681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {256110C6-374A-44C5-860C-7F717B542681}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {256110C6-374A-44C5-860C-7F717B542681}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {256110C6-374A-44C5-860C-7F717B542681}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {8266F4AD-1288-4B9E-9A24-98D51564A063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {8266F4AD-1288-4B9E-9A24-98D51564A063}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {8266F4AD-1288-4B9E-9A24-98D51564A063}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {8266F4AD-1288-4B9E-9A24-98D51564A063}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {91C1F84A-15D1-4596-9BF3-464988B739AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {91C1F84A-15D1-4596-9BF3-464988B739AB}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {91C1F84A-15D1-4596-9BF3-464988B739AB}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {91C1F84A-15D1-4596-9BF3-464988B739AB}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {A5D7048A-756E-46F1-BA5A-7CC4FA6A25CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {A5D7048A-756E-46F1-BA5A-7CC4FA6A25CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 86 | {A5D7048A-756E-46F1-BA5A-7CC4FA6A25CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {A5D7048A-756E-46F1-BA5A-7CC4FA6A25CC}.Release|Any CPU.Build.0 = Release|Any CPU 88 | {87A3BC61-D820-411B-AB87-34B531FB8518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {87A3BC61-D820-411B-AB87-34B531FB8518}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {87A3BC61-D820-411B-AB87-34B531FB8518}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {87A3BC61-D820-411B-AB87-34B531FB8518}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {06D0A301-1508-4F05-98DB-8DE8F83E1FED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 93 | {06D0A301-1508-4F05-98DB-8DE8F83E1FED}.Debug|Any CPU.Build.0 = Debug|Any CPU 94 | {06D0A301-1508-4F05-98DB-8DE8F83E1FED}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {06D0A301-1508-4F05-98DB-8DE8F83E1FED}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {318B4B14-FB2E-43B8-B45A-C3A21A1FA214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 97 | {318B4B14-FB2E-43B8-B45A-C3A21A1FA214}.Debug|Any CPU.Build.0 = Debug|Any CPU 98 | {318B4B14-FB2E-43B8-B45A-C3A21A1FA214}.Release|Any CPU.ActiveCfg = Release|Any CPU 99 | {318B4B14-FB2E-43B8-B45A-C3A21A1FA214}.Release|Any CPU.Build.0 = Release|Any CPU 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(NestedProjects) = preSolution 105 | {A5C515E7-E5E2-4BA8-8E1D-56B9E2AE542B} = {0219332F-4EB1-4036-9960-D2E1F921F454} 106 | {612D9EE0-191E-4223-9DA9-1677CEAF198B} = {0219332F-4EB1-4036-9960-D2E1F921F454} 107 | {C6CB91A9-5D36-41FA-A0F5-74F568A501EC} = {0219332F-4EB1-4036-9960-D2E1F921F454} 108 | {11570978-BE74-4086-A44D-67B271365ACB} = {0219332F-4EB1-4036-9960-D2E1F921F454} 109 | {0122CC47-AD82-4D2B-A4CC-CED322D7755D} = {0219332F-4EB1-4036-9960-D2E1F921F454} 110 | {2BBB0B0F-F2DF-4F96-8ABA-57E1F8735AC3} = {0219332F-4EB1-4036-9960-D2E1F921F454} 111 | {618C4AE0-8691-4C89-ABB4-24C19E13779B} = {D7BE979C-1D23-4964-8ADF-BA5A17FFC310} 112 | {7CEEBFEB-89A5-48DA-A261-694E35529D91} = {A0134598-514D-47BD-9C9C-D8CCD78FBD67} 113 | {178904AB-74A2-4A74-923D-D706A7D7E67E} = {A0134598-514D-47BD-9C9C-D8CCD78FBD67} 114 | {848A3936-3F34-4398-97EC-E6B1D839A8D8} = {0219332F-4EB1-4036-9960-D2E1F921F454} 115 | {A0134598-514D-47BD-9C9C-D8CCD78FBD67} = {D7BE979C-1D23-4964-8ADF-BA5A17FFC310} 116 | {8AF5D4F1-06C6-4D9C-808F-A98FA6B00542} = {D7BE979C-1D23-4964-8ADF-BA5A17FFC310} 117 | {256110C6-374A-44C5-860C-7F717B542681} = {8AF5D4F1-06C6-4D9C-808F-A98FA6B00542} 118 | {8266F4AD-1288-4B9E-9A24-98D51564A063} = {8AF5D4F1-06C6-4D9C-808F-A98FA6B00542} 119 | {91C1F84A-15D1-4596-9BF3-464988B739AB} = {0219332F-4EB1-4036-9960-D2E1F921F454} 120 | {A5D7048A-756E-46F1-BA5A-7CC4FA6A25CC} = {0219332F-4EB1-4036-9960-D2E1F921F454} 121 | {87A3BC61-D820-411B-AB87-34B531FB8518} = {A0134598-514D-47BD-9C9C-D8CCD78FBD67} 122 | {06D0A301-1508-4F05-98DB-8DE8F83E1FED} = {78D13A70-557D-49C1-A992-532E772014F5} 123 | {318B4B14-FB2E-43B8-B45A-C3A21A1FA214} = {78D13A70-557D-49C1-A992-532E772014F5} 124 | EndGlobalSection 125 | GlobalSection(ExtensibilityGlobals) = postSolution 126 | SolutionGuid = {03F10F18-BED4-45E5-A771-8E257E384EC1} 127 | EndGlobalSection 128 | EndGlobal 129 | -------------------------------------------------------------------------------- /src/BuildingBlocks.AspnetCoreIdentity.RavenDB/UserStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Identity; 8 | using Raven.Client.Documents; 9 | using Raven.Client.Documents.Session; 10 | 11 | namespace BuildingBlocks.AspnetCoreIdentity.RavenDB 12 | { 13 | public class UserStore : 14 | IUserPasswordStore, 15 | IUserClaimStore 16 | { 17 | 18 | public IAsyncDocumentSession Session { get; } 19 | 20 | public UserStore(IAsyncDocumentSession session) 21 | { 22 | session.Advanced.UseOptimisticConcurrency = true; 23 | Session = session; 24 | } 25 | 26 | #region IUserStore 27 | 28 | public Task GetUserIdAsync(User user, CancellationToken cancellationToken) 29 | { 30 | cancellationToken.ThrowIfCancellationRequested(); 31 | ThrowIfDisposed(); 32 | if (user == null) 33 | { 34 | throw new ArgumentNullException(nameof(user)); 35 | } 36 | 37 | return Task.FromResult(user.Id); 38 | } 39 | 40 | public Task GetUserNameAsync(User user, CancellationToken cancellationToken) 41 | { 42 | cancellationToken.ThrowIfCancellationRequested(); 43 | ThrowIfDisposed(); 44 | if (user == null) 45 | { 46 | throw new ArgumentNullException(nameof(user)); 47 | } 48 | 49 | return Task.FromResult(user.Name); 50 | } 51 | 52 | public Task SetUserNameAsync(User user, string name, CancellationToken cancellationToken) 53 | { 54 | cancellationToken.ThrowIfCancellationRequested(); 55 | ThrowIfDisposed(); 56 | 57 | if (user == null) 58 | { 59 | throw new ArgumentNullException(nameof(user)); 60 | } 61 | 62 | if (name == null) 63 | { 64 | throw new ArgumentNullException(nameof(name)); 65 | } 66 | 67 | user.Name = name; 68 | 69 | return Task.CompletedTask; 70 | } 71 | 72 | public Task GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken) 73 | { 74 | cancellationToken.ThrowIfCancellationRequested(); 75 | ThrowIfDisposed(); 76 | 77 | if (user == null) 78 | { 79 | throw new ArgumentNullException(nameof(user)); 80 | } 81 | 82 | return Task.FromResult(user.NormalizedName); 83 | } 84 | 85 | public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken) 86 | { 87 | cancellationToken.ThrowIfCancellationRequested(); 88 | ThrowIfDisposed(); 89 | 90 | if (user == null) 91 | { 92 | throw new ArgumentNullException(nameof(user)); 93 | } 94 | 95 | user.NormalizedName = normalizedName ?? 96 | throw new ArgumentNullException(nameof(normalizedName)); 97 | 98 | return Task.CompletedTask; 99 | } 100 | 101 | public async Task CreateAsync(User user, CancellationToken cancellationToken) 102 | { 103 | cancellationToken.ThrowIfCancellationRequested(); 104 | ThrowIfDisposed(); 105 | 106 | if (user == null) 107 | { 108 | throw new ArgumentNullException(nameof(user)); 109 | } 110 | await Session.StoreAsync(user, cancellationToken); 111 | await Session.SaveChangesAsync(cancellationToken); 112 | 113 | return IdentityResult.Success; 114 | } 115 | 116 | public async Task UpdateAsync(User user, CancellationToken cancellationToken) 117 | { 118 | cancellationToken.ThrowIfCancellationRequested(); 119 | ThrowIfDisposed(); 120 | 121 | if (user == null) 122 | { 123 | throw new ArgumentNullException(nameof(user)); 124 | } 125 | 126 | await Session.StoreAsync(user, cancellationToken); 127 | await Session.SaveChangesAsync(cancellationToken); 128 | return IdentityResult.Success; 129 | } 130 | 131 | public async Task DeleteAsync(User user, CancellationToken cancellationToken) 132 | { 133 | cancellationToken.ThrowIfCancellationRequested(); 134 | ThrowIfDisposed(); 135 | 136 | if (user == null) 137 | { 138 | throw new ArgumentNullException(nameof(user)); 139 | } 140 | 141 | Session.Delete(user.Id); 142 | await Session.SaveChangesAsync(cancellationToken); 143 | return IdentityResult.Success; 144 | } 145 | 146 | public Task FindByIdAsync(string userId, CancellationToken cancellationToken) 147 | { 148 | cancellationToken.ThrowIfCancellationRequested(); 149 | ThrowIfDisposed(); 150 | 151 | cancellationToken.ThrowIfCancellationRequested(); 152 | return Session.LoadAsync(userId, cancellationToken); 153 | } 154 | 155 | public Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) 156 | { 157 | cancellationToken.ThrowIfCancellationRequested(); 158 | ThrowIfDisposed(); 159 | 160 | return Session.Query().FirstOrDefaultAsync( 161 | u => u.NormalizedName == normalizedUserName, cancellationToken 162 | ); 163 | } 164 | 165 | #endregion 166 | 167 | #region IUserPasswordStore 168 | 169 | public Task SetPasswordHashAsync(User user, string passwordHash, 170 | CancellationToken cancellationToken = default(CancellationToken)) 171 | { 172 | cancellationToken.ThrowIfCancellationRequested(); 173 | ThrowIfDisposed(); 174 | 175 | if (user == null) 176 | { 177 | throw new ArgumentNullException(nameof(user)); 178 | } 179 | 180 | user.PasswordHash = passwordHash; 181 | 182 | return Task.CompletedTask; 183 | } 184 | 185 | public Task GetPasswordHashAsync(User user, 186 | CancellationToken cancellationToken = default(CancellationToken)) 187 | { 188 | cancellationToken.ThrowIfCancellationRequested(); 189 | ThrowIfDisposed(); 190 | 191 | if (user == null) 192 | { 193 | throw new ArgumentNullException(nameof(user)); 194 | } 195 | 196 | return Task.FromResult(user.PasswordHash); 197 | } 198 | 199 | public Task HasPasswordAsync(User user, CancellationToken cancellationToken = default(CancellationToken)) 200 | { 201 | cancellationToken.ThrowIfCancellationRequested(); 202 | ThrowIfDisposed(); 203 | 204 | if (user == null) 205 | { 206 | throw new ArgumentNullException(nameof(user)); 207 | } 208 | 209 | return Task.FromResult(user.PasswordHash != null); 210 | } 211 | 212 | #endregion 213 | 214 | #region IDisposable 215 | 216 | private void ThrowIfDisposed() 217 | { 218 | if (_disposed) 219 | { 220 | throw new ObjectDisposedException(GetType().Name); 221 | } 222 | } 223 | 224 | private bool _disposed; 225 | 226 | public void Dispose() 227 | { 228 | //Session.Dispose(); 229 | _disposed = true; 230 | } 231 | 232 | #endregion 233 | 234 | #region IUserClaimStore 235 | 236 | public Task> GetClaimsAsync(User user, 237 | CancellationToken cancellationToken = default(CancellationToken)) 238 | { 239 | cancellationToken.ThrowIfCancellationRequested(); 240 | ThrowIfDisposed(); 241 | 242 | if (user == null) 243 | { 244 | throw new ArgumentNullException(nameof(user)); 245 | } 246 | 247 | return Task.FromResult>(user.Claims.Select(c => new Claim(c.Type, c.Value)).ToList()); 248 | } 249 | 250 | public Task AddClaimsAsync(User user, IEnumerable claims, 251 | CancellationToken cancellationToken = default(CancellationToken)) 252 | { 253 | cancellationToken.ThrowIfCancellationRequested(); 254 | ThrowIfDisposed(); 255 | 256 | if (user == null) 257 | { 258 | throw new ArgumentNullException(nameof(user)); 259 | } 260 | 261 | if (claims == null) 262 | { 263 | throw new ArgumentNullException(nameof(claims)); 264 | } 265 | 266 | foreach (var claim in claims) 267 | { 268 | user.AddClaim(claim); 269 | } 270 | 271 | return Task.CompletedTask; 272 | } 273 | 274 | public Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim, 275 | CancellationToken cancellationToken = default(CancellationToken)) 276 | { 277 | cancellationToken.ThrowIfCancellationRequested(); 278 | ThrowIfDisposed(); 279 | 280 | if (user == null) 281 | { 282 | throw new ArgumentNullException(nameof(user)); 283 | } 284 | 285 | if (claim == null) 286 | { 287 | throw new ArgumentNullException(nameof(claim)); 288 | } 289 | 290 | if (newClaim == null) 291 | { 292 | throw new ArgumentNullException(nameof(newClaim)); 293 | } 294 | 295 | user.RemoveClaim(claim); 296 | user.AddClaim(newClaim); 297 | 298 | return Task.CompletedTask; 299 | } 300 | 301 | public Task RemoveClaimsAsync(User user, IEnumerable claims, 302 | CancellationToken cancellationToken = default(CancellationToken)) 303 | { 304 | cancellationToken.ThrowIfCancellationRequested(); 305 | ThrowIfDisposed(); 306 | 307 | if (user == null) 308 | { 309 | throw new ArgumentNullException(nameof(user)); 310 | } 311 | 312 | if (claims == null) 313 | { 314 | throw new ArgumentNullException(nameof(claims)); 315 | } 316 | 317 | foreach (var claim in claims) 318 | { 319 | user.RemoveClaim(claim); 320 | } 321 | 322 | return Task.CompletedTask; 323 | } 324 | 325 | public Task> GetUsersForClaimAsync(Claim claim, 326 | CancellationToken cancellationToken = default(CancellationToken)) 327 | { 328 | cancellationToken.ThrowIfCancellationRequested(); 329 | ThrowIfDisposed(); 330 | 331 | if (claim == null) 332 | { 333 | throw new ArgumentNullException(nameof(claim)); 334 | } 335 | 336 | var query = 337 | from user in Session.Query() 338 | where user.Claims.Any(c => c.Type == claim.Type && c.Value == claim.Value) 339 | select user; 340 | 341 | // LIMIT TO 128 RESULTS 342 | return Task.FromResult((IList)query.ToList()); 343 | } 344 | 345 | #endregion 346 | } 347 | } 348 | --------------------------------------------------------------------------------