AddMassTransitTestHarness with Rabbitmq

This commit is contained in:
meysamhadeli 2022-05-15 02:17:31 +04:30
parent 7213b37db3
commit ff65f5daeb
28 changed files with 362 additions and 390 deletions

View File

@ -7,114 +7,117 @@
<ItemGroup>
<PackageReference Include="Ardalis.GuardClauses" Version="3.3.0"/>
<PackageReference Include="AspNetCore.HealthChecks.UI.SQLite.Storage" Version="5.0.1"/>
<PackageReference Include="Ben.BlockingDetector" Version="0.0.4"/>
<PackageReference Include="EasyCaching.Core" Version="1.4.1"/>
<PackageReference Include="EasyCaching.InMemory" Version="1.4.1"/>
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0"/>
<PackageReference Include="EntityFrameworkCore.Triggered" Version="3.0.0"/>
<PackageReference Include="Figgle" Version="0.4.0"/>
<PackageReference Include="FluentValidation" Version="10.3.6"/>
<PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6"/>
<PackageReference Include="Grpc.Net.Client" Version="2.44.0"/>
<PackageReference Include="MagicOnion" Version="4.4.0"/>
<PackageReference Include="MagicOnion.Abstractions" Version="4.4.0"/>
<PackageReference Include="MagicOnion.Client" Version="4.4.0"/>
<PackageReference Include="MagicOnion.Server" Version="4.4.0"/>
<PackageReference Include="MagicOnion.Server" Version="4.4.0"/>
<PackageReference Include="Polly" Version="7.2.3"/>
<PackageReference Include="Ardalis.GuardClauses" Version="3.3.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.SQLite.Storage" Version="5.0.1" />
<PackageReference Include="Ben.BlockingDetector" Version="0.0.4" />
<PackageReference Include="EasyCaching.Core" Version="1.4.1" />
<PackageReference Include="EasyCaching.InMemory" Version="1.4.1" />
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
<PackageReference Include="EntityFrameworkCore.Triggered" Version="3.0.0" />
<PackageReference Include="Figgle" Version="0.4.0" />
<PackageReference Include="FluentValidation" Version="10.3.6" />
<PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6" />
<PackageReference Include="Grpc.Net.Client" Version="2.44.0" />
<PackageReference Include="MagicOnion" Version="4.4.0" />
<PackageReference Include="MagicOnion.Abstractions" Version="4.4.0" />
<PackageReference Include="MagicOnion.Client" Version="4.4.0" />
<PackageReference Include="MagicOnion.Server" Version="4.4.0" />
<PackageReference Include="MagicOnion.Server" Version="4.4.0" />
<PackageReference Include="MartinCostello.Logging.XUnit" Version="0.2.0" />
<PackageReference Include="NSubstitute" Version="4.3.0" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="protobuf-net.BuildTools" Version="3.0.115">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="protobuf-net.Grpc" Version="1.0.152"/>
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.3.0"/>
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
<PackageReference Include="IdGen" Version="3.0.0"/>
<PackageReference Include="Mapster" Version="7.3.0"/>
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.0"/>
<PackageReference Include="MediatR" Version="9.0.0"/>
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0"/>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.1"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0"/>
<PackageReference Include="MongoDB.Driver" Version="2.14.1"/>
<PackageReference Include="Moq" Version="4.16.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
<PackageReference Include="OpenTelemetry.Contrib.Instrumentation.MassTransit" Version="1.0.0-beta2"/>
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152"/>
<PackageReference Include="Scrutor" Version="3.3.0"/>
<PackageReference Include="Scrutor.AspNetCore" Version="3.3.0"/>
<PackageReference Include="Serilog" Version="2.10.0"/>
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0"/>
<PackageReference Include="Serilog.Enrichers.Span" Version="2.2.0"/>
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="8.4.1"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1"/>
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="8.4.1"/>
<PackageReference Include="Serilog.Sinks.Seq" Version="5.1.0"/>
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.1.1"/>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.3"/>
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.2.3"/>
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3"/>
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="5.0.1"/>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1"/>
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="5.0.1"/>
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.1"/>
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="6.0.1"/>
<PackageReference Include="System.Interactive.Async" Version="5.1.0"/>
<PackageReference Include="MassTransit" Version="8.0.0"/>
<PackageReference Include="MassTransit.RabbitMQ" Version="8.0.0"/>
<PackageReference Include="DotNetCore.CAP" Version="6.0.0"/>
<PackageReference Include="DotNetCore.CAP.Dashboard" Version="6.0.0"/>
<PackageReference Include="DotNetCore.CAP.MongoDB" Version="6.0.0"/>
<PackageReference Include="DotNetCore.CAP.OpenTelemetry" Version="6.0.0"/>
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="6.0.0"/>
<PackageReference Include="DotNetCore.CAP.SqlServer" Version="6.0.0"/>
<PackageReference Include="Duende.IdentityServer" Version="6.0.0"/>
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.0.0"/>
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.0.0"/>
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.0.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1"/>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0"/>
<PackageReference Include="protobuf-net.Grpc" Version="1.0.152" />
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.3.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="IdGen" Version="3.0.0" />
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
<PackageReference Include="MongoDB.Driver" Version="2.14.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OpenTelemetry.Contrib.Instrumentation.MassTransit" Version="1.0.0-beta2" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" />
<PackageReference Include="Scrutor" Version="3.3.0" />
<PackageReference Include="Scrutor.AspNetCore" Version="3.3.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Enrichers.Span" Version="2.2.0" />
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="8.4.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="8.4.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.1.0" />
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.1.1" />
<PackageReference Include="Serilog.Sinks.Xunit2" Version="1.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="6.0.1" />
<PackageReference Include="System.Interactive.Async" Version="5.1.0" />
<PackageReference Include="MassTransit" Version="8.0.0" />
<PackageReference Include="MassTransit.RabbitMQ" Version="8.0.0" />
<PackageReference Include="DotNetCore.CAP" Version="6.0.0" />
<PackageReference Include="DotNetCore.CAP.Dashboard" Version="6.0.0" />
<PackageReference Include="DotNetCore.CAP.MongoDB" Version="6.0.0" />
<PackageReference Include="DotNetCore.CAP.OpenTelemetry" Version="6.0.0" />
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="6.0.0" />
<PackageReference Include="DotNetCore.CAP.SqlServer" Version="6.0.0" />
<PackageReference Include="Duende.IdentityServer" Version="6.0.0" />
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.0.0" />
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.0.0" />
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Jaeger" Version="0.3.7"/>
<PackageReference Include="OpenTracing" Version="0.12.1"/>
<PackageReference Include="prometheus-net" Version="6.0.0"/>
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0"/>
<PackageReference Include="Jaeger" Version="0.3.7" />
<PackageReference Include="OpenTracing" Version="0.12.1" />
<PackageReference Include="prometheus-net" Version="6.0.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
<PackageReference Include="OpenTelemetry" Version="1.2.0-rc3"/>
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.2.0-rc3"/>
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc9"/>
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9"/>
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc9"/>
<PackageReference Include="OpenTelemetry" Version="1.2.0-rc3" />
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.2.0-rc3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc9" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc9" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.0.64">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="22.0.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3"/>
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="22.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
</ItemGroup>
<ItemGroup>
<Folder Include="Contracts"/>
<Folder Include="EventStoreDB\BackgroundWorkers"/>
<Folder Include="Contracts" />
<Folder Include="EventStoreDB\BackgroundWorkers" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,7 @@
using System.Text.Encodings.Web;
using System.Text.Unicode;
using BuildingBlocks.Utils;
using BuildingBlocks.Web;
using DotNetCore.CAP;
using DotNetCore.CAP.Messages;
using Microsoft.EntityFrameworkCore;

View File

@ -1,6 +1,6 @@
namespace BuildingBlocks.Domain.Model;
public interface IAuditable
public interface IAuditable : IEntity
{
public DateTime? CreatedAt { get; set; }
public long? CreatedBy { get; set; }

View File

@ -2,7 +2,6 @@ namespace BuildingBlocks.Domain.Model;
public interface IEntity
{
long Id { get; }
}
public interface IEntity<out TId>

View File

@ -44,7 +44,7 @@ public abstract class AppDbContextBase : DbContext, IDbContext
await SaveChangesAsync(cancellationToken);
await _currentTransaction?.CommitAsync(cancellationToken)!;
}
catch(System.Exception ex)
catch
{
await RollbackTransactionAsync(cancellationToken);
throw;

View File

@ -1,4 +1,3 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -9,16 +8,15 @@ public static class Extensions
{
public static IServiceCollection AddCustomDbContext<TContext>(
this IServiceCollection services,
IConfiguration configuration,
Assembly migrationAssembly)
IConfiguration configuration)
where TContext : AppDbContextBase
{
services.AddScoped<IDbContext>(provider => provider.GetService<TContext>());
services.AddDbContext<TContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
x => x.MigrationsAssembly(migrationAssembly.GetName().Name)));
x => x.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name)));
services.AddScoped<IDbContext>(provider => provider.GetService<TContext>());
return services;
}

View File

@ -1,4 +1,5 @@
using BuildingBlocks.Utils;
using BuildingBlocks.Web;
using Duende.IdentityServer.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;

View File

@ -1,6 +1,7 @@
using System.Reflection;
using BuildingBlocks.Domain.Event;
using BuildingBlocks.Utils;
using BuildingBlocks.Web;
using Humanizer;
using MassTransit;
using Microsoft.AspNetCore.Hosting;

View File

@ -1,28 +0,0 @@
using System;
using System.ComponentModel;
namespace BuildingBlocks.Utils
{
//https://stackoverflow.com/a/19621488/581476
public static class EnumExtensions
{
// This extension method is broken out so you can use a similar pattern with
// other MetaData elements in the future. This is your base method for each.
public static T GetAttribute<T>(this Enum value) where T : Attribute {
var type = value.GetType();
var memberInfo = type.GetMember(value.ToString());
var attributes = memberInfo[0].GetCustomAttributes(typeof(T), false);
return attributes.Length > 0
? (T)attributes[0]
: null;
}
// This method creates a specific call to the above method, requesting the
// Description MetaData attribute.
public static string ToName(this Enum value) {
var attribute = value.GetAttribute<DescriptionAttribute>();
return attribute == null ? value.ToString() : attribute.Description;
}
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Linq;
using System.Web;
namespace BuildingBlocks.Utils
{
public static class ObjectExtensions
{
public static string GetQueryString(this object obj)
{
var properties = from p in obj.GetType().GetProperties()
where p.GetValue(obj, null) != null
select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());
return String.Join("&", properties.ToArray());
}
}
}

View File

@ -1,60 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
namespace BuildingBlocks.Utils
{
public static class ReflectionHelpers
{
private static readonly ConcurrentDictionary<Type, string> TypeCacheKeys = new();
private static readonly ConcurrentDictionary<Type, string> PrettyPrintCache = new();
public static string GetCacheKey(this Type type)
{
return TypeCacheKeys.GetOrAdd(type, t => $"{t.PrettyPrint()}");
}
public static string PrettyPrint(this Type type)
{
return PrettyPrintCache.GetOrAdd(
type,
t =>
{
try
{
return PrettyPrintRecursive(t, 0);
}
catch (System.Exception)
{
return t.Name;
}
});
}
public static bool IsActionDelegate(this Type sourceType)
{
if (sourceType.IsSubclassOf(typeof(MulticastDelegate)) &&
sourceType.GetMethod("Invoke").ReturnType == typeof(void))
return true;
return false;
}
private static string PrettyPrintRecursive(Type type, int depth)
{
if (depth > 3)
{
return type.Name;
}
var nameParts = type.Name.Split('`');
if (nameParts.Length == 1)
{
return nameParts[0];
}
var genericArguments = type.GetTypeInfo().GetGenericArguments();
return !type.IsConstructedGenericType
? $"{nameParts[0]}<{new string(',', genericArguments.Length - 1)}>"
: $"{nameParts[0]}<{string.Join(",", genericArguments.Select(t => PrettyPrintRecursive(t, depth + 1)))}>";
}
}
}

View File

@ -1,7 +1,7 @@
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Routing;
namespace BuildingBlocks.Web;
namespace BuildingBlocks.Utils;
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{

View File

@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.Web;
public static class ApiVersioningExtensions
{
public static void AddCustomVersioning(this IServiceCollection services,
Action<ApiVersioningOptions> configurator = null)
{
//https://www.meziantou.net/versioning-an-asp-net-core-api.htm
//https://exceptionnotfound.net/overview-of-api-versioning-in-asp-net-core-3-0/
services.AddApiVersioning(options =>
{
// Add the headers "api-supported-versions" and "api-deprecated-versions"
// This is better for discoverability
options.ReportApiVersions = true;
// AssumeDefaultVersionWhenUnspecified should only be enabled when supporting legacy services that did not previously
// support API versioning. Forcing existing clients to specify an explicit API version for an
// existing service introduces a breaking change. Conceptually, clients in this situation are
// bound to some API version of a service, but they don't know what it is and never explicit request it.
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
// // Defines how an API version is read from the current HTTP request
options.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"),
new UrlSegmentApiVersionReader());
configurator?.Invoke(options);
});
}
}

View File

@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.Utils;
namespace BuildingBlocks.Web;
public static class ConfigurationExtensions
{

View File

@ -1,6 +1,6 @@
using Microsoft.Extensions.Configuration;
namespace BuildingBlocks.Utils
namespace BuildingBlocks.Web
{
public static class ConfigurationHelper
{
@ -17,4 +17,4 @@ namespace BuildingBlocks.Utils
.Build();
}
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace BuildingBlocks.Web;
public static class CorrelationExtensions
{
private const string CorrelationId = "correlationId";
public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder app)
{
return app.Use(async (ctx, next) =>
{
if (!ctx.Request.Headers.TryGetValue(CorrelationId, out var correlationId))
correlationId = Guid.NewGuid().ToString("N");
ctx.Items[CorrelationId] = correlationId.ToString();
await next();
});
}
public static string GetCorrelationId(this HttpContext context)
{
return context.Items.TryGetValue(CorrelationId, out var correlationId) ? correlationId as string : null;
}
}

View File

@ -1,62 +0,0 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.Web
{
public static class Extensions
{
private const string CorrelationId = "correlationId";
public static void AddCustomVersioning(this IServiceCollection services,
Action<ApiVersioningOptions> configurator = null)
{
//https://www.meziantou.net/versioning-an-asp-net-core-api.htm
//https://exceptionnotfound.net/overview-of-api-versioning-in-asp-net-core-3-0/
services.AddApiVersioning(options =>
{
// Add the headers "api-supported-versions" and "api-deprecated-versions"
// This is better for discoverability
options.ReportApiVersions = true;
// AssumeDefaultVersionWhenUnspecified should only be enabled when supporting legacy services that did not previously
// support API versioning. Forcing existing clients to specify an explicit API version for an
// existing service introduces a breaking change. Conceptually, clients in this situation are
// bound to some API version of a service, but they don't know what it is and never explicit request it.
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
// // Defines how an API version is read from the current HTTP request
options.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"),
new UrlSegmentApiVersionReader());
configurator?.Invoke(options);
});
}
public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder app)
=> app.Use(async (ctx, next) =>
{
if (!ctx.Request.Headers.TryGetValue(CorrelationId, out var correlationId))
{
correlationId = Guid.NewGuid().ToString("N");
}
ctx.Items[CorrelationId] = correlationId.ToString();
await next();
});
public static string GetCorrelationId(this HttpContext context)
{
return context.Items.TryGetValue(CorrelationId, out var correlationId) ? correlationId as string : null;
}
}
}

View File

@ -0,0 +1,60 @@
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.Web;
public static class ServiceCollectionExtensions
{
public static void ReplaceScoped<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.Unregister<TService>();
services.AddScoped<TService, TImplementation>();
}
public static void ReplaceScoped<TService>(this IServiceCollection services,
Func<IServiceProvider, TService> implementationFactory)
where TService : class
{
services.Unregister<TService>();
services.AddScoped(implementationFactory);
}
public static void ReplaceTransient<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.Unregister<TService>();
services.AddTransient<TService, TImplementation>();
}
public static void ReplaceTransient<TService>(this IServiceCollection services,
Func<IServiceProvider, TService> implementationFactory)
where TService : class
{
services.Unregister<TService>();
services.AddTransient(implementationFactory);
}
public static void ReplaceSingleton<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.Unregister<TService>();
services.AddSingleton<TService, TImplementation>();
}
public static void ReplaceSingleton<TService>(this IServiceCollection services,
Func<IServiceProvider, TService> implementationFactory)
where TService : class
{
services.Unregister<TService>();
services.AddSingleton(implementationFactory);
}
public static void Unregister<TService>(this IServiceCollection services)
{
var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(TService));
services.Remove(descriptor);
}
}

View File

@ -31,7 +31,7 @@ builder.Services.Configure<GrpcOptions>(options => configuration.GetSection("Grp
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
builder.Services.AddTransient<IBusPublisher, BusPublisher>();
builder.Services.AddCustomDbContext<BookingDbContext>(configuration, typeof(BookingRoot).Assembly);
builder.Services.AddCustomDbContext<BookingDbContext>(configuration);
builder.AddCustomSerilog();
builder.Services.AddJwt();

View File

@ -8,10 +8,8 @@ using BuildingBlocks.Jwt;
using BuildingBlocks.Logging;
using BuildingBlocks.Mapster;
using BuildingBlocks.MassTransit;
using BuildingBlocks.Mongo;
using BuildingBlocks.OpenTelemetry;
using BuildingBlocks.Swagger;
using BuildingBlocks.Utils;
using BuildingBlocks.Web;
using Figgle;
using Flight;
@ -32,12 +30,10 @@ var env = builder.Environment;
var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
builder.Services.AddTransient<IBusPublisher, BusPublisher>();
builder.Services.AddCustomDbContext<FlightDbContext>(configuration, typeof(FlightRoot).Assembly);
builder.Services.AddMongoDbContext<FlightReadDbContext>(configuration);
builder.Services.AddCustomDbContext<FlightDbContext>(configuration);
builder.Services.AddScoped<IDataSeeder, FlightDataSeeder>();
builder.AddCustomSerilog();
builder.Services.AddJwt();
builder.Services.AddControllers();

View File

@ -1,11 +1,19 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=.\\sqlexpress;Database=FlightDB;Trusted_Connection=True;MultipleActiveResultSets=true"
"DefaultConnection": "Server=.\\sqlexpress;Database=FlightDB_Test;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"RabbitMq": {
"HostName": "rabbitmq",
"HostName": "localhost",
"ExchangeName": "flight",
"UserName": "guest",
"Password": "guest"
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Debug",
"Microsoft.EntityFrameworkCore.Database.Command": "Debug"
}
}
}

View File

@ -6,23 +6,23 @@ using Flight.Seats.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace Flight.Data
namespace Flight.Data;
public sealed class FlightDbContext : AppDbContextBase
{
public sealed class FlightDbContext : AppDbContextBase
public FlightDbContext(DbContextOptions<FlightDbContext> options, IHttpContextAccessor httpContextAccessor) : base(
options, httpContextAccessor)
{
public FlightDbContext(DbContextOptions<FlightDbContext> options, IHttpContextAccessor httpContextAccessor) : base(options, httpContextAccessor)
{
}
}
public DbSet<Flights.Models.Flight> Flights => Set<Flights.Models.Flight>();
public DbSet<Airport> Airports => Set<Airport>();
public DbSet<Aircraft> Aircraft => Set<Aircraft>();
public DbSet<Seat> Seats => Set<Seat>();
public DbSet<Flights.Models.Flight> Flights => Set<Flights.Models.Flight>();
public DbSet<Airport> Airports => Set<Airport>();
public DbSet<Aircraft> Aircraft => Set<Aircraft>();
public DbSet<Seat> Seats => Set<Seat>();
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
}

View File

@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Flight.Data.Migrations
{
[DbContext(typeof(FlightDbContext))]
[Migration("20220509213249_Init")]
[Migration("20220511215248_Init")]
partial class Init
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)

View File

@ -1,4 +1,8 @@
using System.Threading.Tasks;
using BuildingBlocks.Contracts.EventBus.Messages;
using Flight.Airports.Models;
using Flight.Flights.Features.GetFlightById;
using MassTransit.Testing;
using Xunit;
namespace Integration.Test;
@ -8,12 +12,16 @@ public class DeleteTests
{
private readonly TestFixture _fixture;
public DeleteTests(TestFixture fixture) => _fixture = fixture;
[Fact]
public Task Should_delete_flight()
public DeleteTests(TestFixture fixture)
{
return Task.CompletedTask;
_fixture = fixture;
}
[Fact]
public async Task Should_get_flight()
{
var query = new GetFlightByIdQuery(1);
var flight = await _fixture.SendAsync(query);
var airport = await _fixture.FindAsync<Airport>(1);
}
}

View File

@ -1,21 +1,24 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BuildingBlocks.Domain.Model;
using BuildingBlocks.EFCore;
using BuildingBlocks.MassTransit;
using BuildingBlocks.Web;
using Flight.Data;
using Flight.Data.Seed;
using MassTransit;
using MassTransit.Testing;
using MediatR;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Moq;
using NSubstitute;
using Respawn;
using Xunit;
@ -26,114 +29,84 @@ public class TestFixtureCollection : ICollectionFixture<TestFixture>
{
}
// ref: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
// ref: https://github.com/jbogard/ContosoUniversityDotNetCore-Pages/blob/master/ContosoUniversity.IntegrationTests/SliceFixture.cs
// ref: https://github.com/MassTransit/MassTransit/blob/00d6992286911a437b63b93c89a56e920b053c11/src/MassTransit.TestFramework/InMemoryTestFixture.cs
// ref: https://wrapt.dev/blog/building-an-event-driven-dotnet-application-integration-testing
// ref: https://github.com/jasontaylordev/CleanArchitecture/blob/main/tests/Application.IntegrationTests/Testing.cs
public class TestFixture : IAsyncLifetime
{
private readonly Checkpoint _checkpoint;
private readonly IConfiguration _configuration;
private readonly WebApplicationFactory<Program> _factory;
private readonly IServiceScopeFactory _scopeFactory;
private static InMemoryTestHarness _harness;
private Checkpoint _checkpoint;
private HttpClient _client;
private IConfiguration _configuration;
private WebApplicationFactory<Program> _factory;
private ITestHarness _harness;
private IServiceScopeFactory _scopeFactory;
public TestFixture()
public ILogger<TestFixture> Logger =>
_scopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<TestFixture>>();
public async Task InitializeAsync()
{
_factory = FlightTestApplicationFactory();
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "test");
_factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => builder.ConfigureServices(services =>
{
services.RemoveAll(typeof(IHostedService));
services.ReplaceSingleton(AddHttpContextAccessorMock);
services.ReplaceScoped<IDataSeeder, FlightDataSeeder>();
services.AddMassTransitTestHarness(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
var rabbitMqOptions = services.GetOptions<RabbitMqOptions>("RabbitMq");
var host = rabbitMqOptions.HostName;
cfg.Host(host, h =>
{
h.Username(rabbitMqOptions.UserName);
h.Password(rabbitMqOptions.Password);
});
cfg.ConfigureEndpoints(context);
});
});
}));
_harness = _factory.Services.GetTestHarness();
await _harness.Start();
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
_checkpoint = new Checkpoint();
}
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
public WebApplicationFactory<Program> FlightTestApplicationFactory()
{
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "test");
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
return new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices((services) =>
{
services.RemoveAll(typeof(IHostedService));
});
builder.ConfigureServices(services =>
{
builder.ConfigureLogging(logging =>
{
logging.ClearProviders(); // Remove other loggers
});
var httpContextAccessorService = services.FirstOrDefault(d =>
d.ServiceType == typeof(IHttpContextAccessor));
services.Remove(httpContextAccessorService);
services.AddSingleton(_ => Mock.Of<IHttpContextAccessor>());
services.AddScoped<InMemoryTestHarness>();
var provider = services.BuildServiceProvider();
var serviceScopeFactory = provider.GetService<IServiceScopeFactory>();
// MassTransit Start Setup -- Do Not Delete Comment
_harness = serviceScopeFactory?.CreateScope().ServiceProvider.GetRequiredService<InMemoryTestHarness>();
_harness?.Start().GetAwaiter().GetResult();
});
});
}
public Task InitializeAsync()
{
return _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
await EnsureDatabaseAsync();
}
public async Task DisposeAsync()
{
await _harness.Stop();
_harness.Cancel();
await _factory.DisposeAsync();
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
}
public async Task ExecuteScopeAsync(Func<IServiceProvider, Task> action)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<FlightDbContext>();
try
{
await dbContext.BeginTransactionAsync();
await action(scope.ServiceProvider);
await dbContext.CommitTransactionAsync();
}
catch (Exception)
{
await dbContext.RollbackTransactionAsync();
throw;
}
await action(scope.ServiceProvider);
}
public async Task<T> ExecuteScopeAsync<T>(Func<IServiceProvider, Task<T>> action)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<FlightDbContext>();
try
{
await dbContext.BeginTransactionAsync();
var result = await action(scope.ServiceProvider);
var result = await action(scope.ServiceProvider);
await dbContext.CommitTransactionAsync();
return result;
}
catch (Exception)
{
await dbContext.RollbackTransactionAsync();
throw;
}
return result;
}
public Task ExecuteDbContextAsync(Func<FlightDbContext, Task> action)
@ -171,6 +144,7 @@ public class TestFixture : IAsyncLifetime
return ExecuteDbContextAsync(db =>
{
foreach (var entity in entities) db.Set<T>().Add(entity);
return db.SaveChangesAsync();
});
}
@ -231,7 +205,7 @@ public class TestFixture : IAsyncLifetime
});
}
public Task<T> FindAsync<T>(int id)
public Task<T> FindAsync<T>(long id)
where T : class, IEntity
{
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
@ -258,13 +232,15 @@ public class TestFixture : IAsyncLifetime
}
// MassTransit Methods -- Do Not Delete Comment
// ref: https://github.com/MassTransit/MassTransit/blob/00d6992286911a437b63b93c89a56e920b053c11/src/MassTransit.TestFramework/InMemoryTestFixture.cs
// ref: https://wrapt.dev/blog/building-an-event-driven-dotnet-application-integration-testing
/// <summary>
/// Publishes a message to the bus, and waits for the specified response.
/// </summary>
/// <param name="message">The message that should be published.</param>
/// <typeparam name="TMessage">The message that should be published.</typeparam>
public static async Task PublishMessage<TMessage>(object message)
public async Task PublishMessage<TMessage>(object message)
where TMessage : class
{
await _harness.Bus.Publish<TMessage>(message);
@ -317,4 +293,34 @@ public class TestFixture : IAsyncLifetime
var consumerHarness = scope.ServiceProvider.GetRequiredService<IConsumerTestHarness<TConsumedBy>>();
return consumerHarness.Consumed.Any<TMessage>();
}
private async Task EnsureDatabaseAsync()
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<FlightDbContext>();
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
await context.Database.MigrateAsync();
foreach (var seeder in seeders) await seeder.SeedAllAsync();
}
// private async Task AddInMemoryHarnessAsync()
// {
// _harness = _factory.Services.GetRequiredService<InMemoryTestHarness>();
// await _harness.Start();
// }
private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider)
{
var httpContextAccessorMock = Substitute.For<IHttpContextAccessor>();
using var scope = serviceProvider.CreateScope();
httpContextAccessorMock.HttpContext = new DefaultHttpContext {RequestServices = scope.ServiceProvider};
httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 5000);
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
return httpContextAccessorMock;
}
}

View File

@ -28,7 +28,7 @@ var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
builder.Services.AddTransient<IBusPublisher, BusPublisher>();
builder.Services.AddCustomDbContext<PassengerDbContext>(configuration, typeof(PassengerRoot).Assembly);
builder.Services.AddCustomDbContext<PassengerDbContext>(configuration);
builder.AddCustomSerilog();
builder.Services.AddJwt();
builder.Services.AddControllers();
@ -41,7 +41,6 @@ builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly);
builder.Services.AddHttpContextAccessor();
builder.Services.AddTransient<IEventMapper, EventMapper>();
builder.Services.AddTransient<IBusPublisher, BusPublisher>();
builder.Services.AddCustomMassTransit(typeof(PassengerRoot).Assembly, env);
builder.Services.AddCustomOpenTelemetry();