Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This repository offers a wide collection of .NET packages for use in microservic
# Sections

- [RabbitMQ Consumer](#Consumer)
- [JsonSchema generator for Rabbit events](#jsonschema-generator-for-rabbit-events)
- [Logging](#Logging)
- [API Middlewares](#Middlewares)
- [API Documentation](#Documentation)
Expand Down Expand Up @@ -159,6 +160,42 @@ services
.AddPlatformRabbitMqSqlServerConsumerLock(configuration.GetConnectionString("ms sql connection string"));
```

### JsonSchema Generator for Rabbit events

Allow to generate json schema for consuming and producing types,
based on [NJsonSchema](https://github.com/RicoSuter/NJsonSchema)

To use just install
[Luxoft.Bss.Platform.RabbitMq.JsonSchemaGenerator](https://www.nuget.org/packages/Luxoft.Bss.Platform.RabbitMq.JsonSchemaGenerator)
package and add a new middleware via the extension:
```C#
if (app.Environment.IsDevelopment())
{
app.UseRabbitJsonSchemaGenerator(opt =>
{
opt.Path = "/api/rabbit-json-schema";
opt.SystemPrefix = "TSS.";
opt.ProducedEventTypes = typeof(IEvent).Assembly.GetTypes()
.Where(x => x.IsPublic && !x.IsAbstract && !x.IsInterface)
.Where(x => x.GetInterfaces().Contains(typeof(IEvent)));
});
}
```
The next two options are required only for produced events:
- `SystemPrefix` - the prefix added to the produced type names (`SaveEmployee` -> `TSS.SaveEmployee`) when exporting types in the schema
- `ProducedEventTypes` - collection of all produced types, that need to be exported in the schema

Consumed events are automatically added if you use the new way to register consumer with
method [AddPlatformRabbitMqConsumerWithMessages](#Register-event-with-builder)

For more complex cases please use middleware without extensions:
```C#
app.UseMiddleware<GenerateSchemaMiddleware>("/api/rabbit-json-schema", allEventsDict);
```
where `Dictionary<string, Type> allEventsDict` contains the mapping between routing keys
and types (for both types - consumed and produced)


## Logging

To use platform logger, first install the [NuGet package](https://www.nuget.org/packages/Luxoft.Bss.Platform.Logging):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Luxoft.Bss.Platform.RabbitMq.JsonSchemaGenerator</PackageId>
<RootNamespace>Bss.Platform.RabbitMq.JsonSchemaGenerator</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
<PackageReference Include="NJsonSchema" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bss.Platform.RabbitMq.Consumer\Bss.Platform.RabbitMq.Consumer.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Bss.Platform.RabbitMq.JsonSchemaGenerator;

public static class DependencyInjection
{
public static IApplicationBuilder UseRabbitJsonSchemaGenerator(
this IApplicationBuilder app,
Action<GenerateSchemaOptions>? setup = null)
{
var options = new GenerateSchemaOptions();
setup?.Invoke(options);

var consumedEvents =
app.ApplicationServices
.GetKeyedService<Dictionary<string, Type>>(Consumer.DependencyInjection.RoutingMessageProviderKey)
?.Select(x => (x.Key, x.Value))
?? [];

var producedEvents = options.ProducedEventTypes.Select(x => ($"{options.SystemPrefix}{x.Name}", x));

var allEventsDict = producedEvents.Concat(consumedEvents)
.DistinctBy(x => x.Item1, StringComparer.OrdinalIgnoreCase)
.ToDictionary(x => x.Item1, x => x.Item2);

return app.UseMiddleware<GenerateSchemaMiddleware>(options.Path, allEventsDict);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Http;

using NJsonSchema;
using NJsonSchema.Generation;

namespace Bss.Platform.RabbitMq.JsonSchemaGenerator;

public class GenerateSchemaMiddleware(RequestDelegate next, string path, Dictionary<string, Type> eventsDict)
{
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Method == "GET"
&& context.Request.Path.Value.Equals(path, StringComparison.InvariantCultureIgnoreCase))
{
await this.GenerateSchema(context);
return;
}

await next(context);
}

private async Task GenerateSchema(HttpContext context)
{
var settings = new SystemTextJsonSchemaGeneratorSettings
{
FlattenInheritanceHierarchy = true,
GenerateAbstractProperties = false,
AllowReferencesWithProperties = false
};

var schemaContainer = new JsonSchema();
var appender = new JsonSchemaAppender(schemaContainer, new MappedNameGenerator(eventsDict));
var generator = new NJsonSchema.Generation.JsonSchemaGenerator(settings);

var jsonSchemas = eventsDict.Select(x => x.Value).Select(generator.Generate);
foreach (var schema in jsonSchemas)
{
appender.AppendSchema(schema, null);
}

context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(schemaContainer.ToJson());
}
}

file class MappedNameGenerator(Dictionary<string, Type> eventsDict) : ITypeNameGenerator
{
private readonly Dictionary<string, string> mapping = eventsDict.DistinctBy(x => x.Value)
.ToDictionary(x => x.Value.Name, x => x.Key);

public string Generate(JsonSchema schema, string? typeNameHint, IEnumerable<string> reservedTypeNames) =>
this.mapping.GetValueOrDefault(schema.Title ?? throw new("JsonSchema title is null"), schema.Title);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Bss.Platform.RabbitMq.JsonSchemaGenerator;

public class GenerateSchemaOptions
{
public string Path { get; set; } = "/api/rabbit-json-schema";


/// <summary>
/// Collection of produced events
/// </summary>
public IEnumerable<Type> ProducedEventTypes { get; set; } = [];

/// <summary>
/// Prefix to add to produced event type
/// </summary>
public string SystemPrefix { get; set; } = string.Empty;
}
7 changes: 7 additions & 0 deletions src/Bss.Platform.sln
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notifications", "Notificati
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.Notifications.Audit", "Bss.Platform.Notifications.Audit\Bss.Platform.Notifications.Audit.csproj", "{03CD2FDC-2BF1-497D-BF18-F15170C0AD85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.RabbitMq.JsonSchemaGenerator", "Bss.Platform.RabbitMq.JsonSchemaGenerator\Bss.Platform.RabbitMq.JsonSchemaGenerator.csproj", "{0FC70E24-62A9-44BF-A433-804C98514A09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -58,6 +60,7 @@ Global
{BE2E219F-3F3B-48E8-B13B-A71321BB6372} = {80AA8C01-842D-4A5F-BCBE-48E0361783A0}
{A97421A9-58B7-4948-8231-2028B5D11F36} = {94640F4D-8BC6-407F-835E-73B454972CAD}
{03CD2FDC-2BF1-497D-BF18-F15170C0AD85} = {94640F4D-8BC6-407F-835E-73B454972CAD}
{0FC70E24-62A9-44BF-A433-804C98514A09} = {0F54786D-AB46-46AC-88DF-BEB789A62C1F}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{860BFBD8-26EA-44F9-980E-21B828FC8F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -108,5 +111,9 @@ Global
{03CD2FDC-2BF1-497D-BF18-F15170C0AD85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03CD2FDC-2BF1-497D-BF18-F15170C0AD85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03CD2FDC-2BF1-497D-BF18-F15170C0AD85}.Release|Any CPU.Build.0 = Release|Any CPU
{0FC70E24-62A9-44BF-A433-804C98514A09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FC70E24-62A9-44BF-A433-804C98514A09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FC70E24-62A9-44BF-A433-804C98514A09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FC70E24-62A9-44BF-A433-804C98514A09}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
5 changes: 0 additions & 5 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\__SolutionItems\CommonAssemblyInfo.cs" Link="Properties\CommonAssemblyInfo.cs"/>
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageVersion Include="DotNetCore.CAP.RabbitMQ" Version="8.2.0" />
<PackageVersion Include="DotNetCore.CAP.SqlServer" Version="8.2.0" />
<PackageVersion Include="Microsoft.SqlServer.SqlManagementObjects" Version="172.52.0" />
<PackageVersion Include="NJsonSchema" Version="11.5.1" />
<PackageVersion Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1" />
<PackageVersion Include="MediatR" Version="12.4.1" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.22.0" />
Expand Down