How to get list of users from UserManager from Microsoft.AspNetCore.Identity using [ScopedService]
Below is what I already tried:
using System.Linq;
using hostapp.Data;
using hostapp.Models;
using HotChocolate;
using HotChocolate.Data;
using Microsoft.AspNetCore.Identity;
namespace hostapp.GraphQL
{
public class Query
{
// 1.
[UseDbContext(typeof(DataContext))]
public IQueryable<AppUser> Users([ScopedService] UserManager<AppUser> userManager)
{
return (IQueryable<AppUser>)userManager.Users;
}
// 2.
[UseDbContext(typeof(DataContext))]
public async Task<List<AppUser>> Users([ScopedService] UserManager<AppUser> userManager)
{
return await userManager.Users.ToListAsync();
}
}
}
Input:
query {
users {
emailConfirmed
}
}
Output:
{
"errors": [
{
"message": "Unexpected Execution Error",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"users"
]
}
],
"data": {
"users": null
}
}
You do not need to use [ScopedService] but rather [Service]
You really only even need to use [ScopedService] in case of a DBContext in combination with UseDbContext.
We will fix this confusion in the next version
Related
I have a number of Get operations in my Api. Currently the swagger document includes the following parameters:
"parameters": [
{
"in": "body",
"name": "body",
"schema": { }
}
],
Is there any way to remove these using an OperationFilter? I've tried the following but the operations do not have any parameters
public class RemoveBodyParametersFilter : IOpenApiOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var parametersToRemove = operation.Parameters.Where(p => p.Name == "body").ToList();
foreach (var parameter in parametersToRemove)
{
operation.Parameters.Remove(parameter);
}
}
}
The endpoint looks like this:
[HttpGet(Name = "GetOperationStatus")]
[SwaggerConsumes("application/json")]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 200)]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 202)]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 400)]
[Metadata("Get an Operation Status", "Get the operation status for the operation ID provided.", VisibilityType.Internal)]
public async Task<IActionResult> GetOperationStatus(string operationId)
I built a c# web api based on .net 6 with integration of Microsoft.AspNetCore.OData.
I use a Dictionary to include dynamic properties in my response:
public class TestObj
{
public string Id { get; set; }
public IDictionary<string, object> Properties { get; set; }
public TestObj()
{
Properties = new Dictionary<string, object>();
}
}
Example response of my GET operation:
{
"id": "TestId",
"properties": {
"key1": "value1",
"key2": "value2"
}
}
But I want the keyvaluepairs of the Properties Dictionary on the top level of the response:
{
"id": "TestId",
"key1": "value1",
"key2": "value2"
}
Is that possible? I've tried different approaches but haven't found a solution.
Your response doesn't look like OData's response. OData response should look like this:
{
"#odata.context": "https://localhost:44383/odata/$metadata#TestObj",
"value": [
{
"Id": "TestId"
}
]
}
So, please check your controllers and routes:
Your controller must inherit from ODataController
Your request is going to odata route
If you have a dictionary in your class and you use ODataConventionModelBuilder it will automatically make it OpenType: https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/use-open-types-in-odata-v4
So, if you are using ODataModelBuilder you need configure your dictionary like:
static IEdmModel GetEdmModel()
{
var odataBuilder = new ODataModelBuilder();
var classExample = odataBuilder.EntityType<TestObj>();
classExample.HasKey(s => s.Id);
classExample.HasDynamicProperties(d => d.Properties);
odataBuilder.EntitySet<TestObj>("TestObj");
return odataBuilder.GetEdmModel();
}
Then your response will look like this:
{
"#odata.context": "https://localhost:44383/odata/$metadata#TestObj",
"value": [
{
"Id": "TestId",
"key1": "value1",
"key2": "value2"
}
]
}
I tried several OData package versions, e.g. Microsoft.AspNetCore.OData 7.5.12 in an .net5 sample project.
Because my real goal is to migrate my existing asp.net v4 REST api to .net6, I also did some tests with .net6 and Microsoft.AspNetCore.OData 8.0.7 using VS2022
While slim down my sample code based on .net6 / Microsoft.AspNetCore.OData 8.0.7 for this post, I had my first success!
Now the Swagger site shows two GET operations:
GET /DynProp
and
GET /odata/DynProp.
The /odata/DynProp operation shows the dynamic properties correctly!!!
Now I have a basis that I can use for my migration. Next steps will be: Configuring the routing (e.g. removing Non-OData operations from Swagger and customize the OData routing paths for my operations).
Thank you very much for your tips and suggestions that helped me not to give up.
My working sample code:
Controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
namespace DynProp.Controllers
{
[ApiController]
[Route("[controller]")]
public class DynPropController : ODataController
{
[EnableQuery]
[HttpGet(Name = "GetDyn")]
public IEnumerable<DynProp> Get()
{
List<DynProp> list = new List<DynProp>();
DynProp obj1 = new DynProp();
obj1.Id = "TestId";
obj1.Properties.Add("key1", "value1");
obj1.Properties.Add("key2", "value2");
list.Add(obj1);
DynProp obj2 = new DynProp();
obj2.Id = "TestId2";
obj2.Properties.Add("key1", "value3");
obj2.Properties.Add("key2", "value4");
list.Add(obj2);
return list;
}
}
}
DynProp class / EDM:
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using System.ComponentModel.DataAnnotations;
namespace DynProp
{
public class DynProp
{
[Key]
public string Id { get; set; }
public IDictionary<string, object> Properties { get; set; }
public DynProp()
{
Id = "";
Properties = new Dictionary<string, object>();
}
}
public class TestDataModel
{
public IEdmModel GetEdmModel()
{
var odataBuilder = new ODataModelBuilder();
var classExample = odataBuilder.EntityType<DynProp>();
classExample.HasKey(s => s.Id);
classExample.HasDynamicProperties(d => d.Properties);
var entitySet = odataBuilder.EntitySet<DynProp>("DynProp");
return odataBuilder.GetEdmModel();
}
}
}
Program.cs (in .net6 it includes all parts from Startup.cs)
using DynProp;
using Microsoft.AspNetCore.OData;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddOData(options =>
{
options.Select().Filter().OrderBy();
options.AddRouteComponents("odata", new TestDataModel().GetEdmModel());
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Result of GET /odata/DynProp:
{
"#odata.context": "https://localhost:7171/odata/$metadata#DynProp",
"value": [
{
"Id": "TestId",
"key1": "value1",
"key2": "value2"
},
{
"Id": "TestId2",
"key1": "value3",
"key2": "value4"
}
]
}
This is basically the same question as How do I set or remove the Default Response Content Type Using SwashBuckle, but for .NET Core 3.0
By default in .NET Core 3.0, you configure a web api with services.AddControllers() and then you configure swagger with swashbuckle with services.AddSwaggerGen() + app.UseSwagger()
That is working fine, however the swagger.json contains multiple response content types for every operation (text/plain + application/json + text/json)
I know I can restrict these response content types by adding [Produces] and [Consumes] to my operations, but I'd like to avoid that for each and every operation (i.e I want to do that globally)
Please note that I preferably want to use System.Text.Json but if you have a solution that works only with Newtonsoft.JSON then it's better than nothing ;)
Swashbuckle.AspNetCore.SwaggerGen 5.0 uses the OpenApiOperation to describe API operations.
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class AssignContentTypeFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Responses.ContainsKey("200"))
{
operation.Responses.Clear();
}
var data = new OpenApiResponse
{
Description = "Ok",
Content = new Dictionary<string, OpenApiMediaType>
{
["application/json"] = new OpenApiMediaType(),
["application/xml"] = new OpenApiMediaType(),
}
};
operation.Responses.Add("200", data);
}
}
In Startup.cs
services.AddSwaggerGen(q =>
{
q.SwaggerDoc("v1", new OpenApiInfo
{
Title = "mytitle",
Version = "v1",
});
q.OperationFilter<AssignContentTypeFilter>();
});
You can create custom filter for swagger
internal class AssignContentTypeFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
operation.Consumes.Clear();
operation.Consumes.Add("application/json");
operation.Produces.Clear();
operation.Produces.Add("application/json");
}
}
then
services.AddSwaggerGen(cfg => cfg.OperationFilter<AssignContentTypeFilter>());
This is what worked for me in Swashbuckle.AspNetCore.SwaggerGen 5.0:
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
internal class ContentTypeOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.RequestBody == null)
{
return;
}
operation.RequestBody.Content = new Dictionary<string, OpenApiMediaType>
{
{ "application/json", new OpenApiMediaType() }
};
foreach (var response in operation.Responses)
{
response.Value.Content = new Dictionary<string, OpenApiMediaType>
{
{ "application/json", new OpenApiMediaType() }
};
}
}
}
Startup.cs (modified from sjokkogutten's answer):
services.AddSwaggerGen(q =>
{
q.SwaggerDoc("v1", new OpenApiInfo
{
Title = "mytitle",
Version = "v1",
});
q.OperationFilter<ContentTypeOperationFilter>();
});
The text/plain ends up in the generated swagger/openapi spec because by default API controllers have a StringOutputFormatter (from the Microsoft.AspNetCore.Mvc.Formatters namespace) available.
This MSDN goes into detail, but the crux of it is that by doing:
services.AddControllers(options =>
{
options.OutputFormatters.RemoveType<StringOutputFormatter>();
});
..(probably in your Startup/ConfigureServices) you remove the relevant formatter and text/plain no longer appears in the generated swagger doc
Note: remember to install/import Microsoft.AspNetCore.Mvc.Formatters
Version for Swashbuckle.AspNetCore.SwaggerGen 6+. Removes only text/plain content type. Works for string result in my case.
internal class RemoveTextContentOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
foreach (var (_, response) in operation.Responses)
{
if (response.Content.ContainsKey("text/plain"))
response.Content.Remove("text/plain");
}
}
}
Generated swagger for string operation:
"/api/news/{newsArticleId}/file-link": {
"get": {
"tags": [
"NewsApi"
],
"operationId": "NewsApi_GetUploadFileLink",
"parameters": [
{
"name": "newsArticleId",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"500": {
"description": "Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
Currently I'm having trouble with deploying an Azure VM from an ARM template using an Azure Function which is written in C#, whilst using a JObject, from the Newjonsoft.Json,Linq library, to provide parameters for the new VM.
The JObject.FromObject() method formulates parameters in the format "{"paramName": "paramValue"}", however I believe that it needs to be formulated as "{"paramName": { "value": "paramValue"}. I'm not sure if 'contentVersion' and '$schema' ARM Template parameters also need to be specified for this to work.
So far I have tried to formulate the object using a dynamic variable, which is then converted to string and parsed using JObject.Parse() method, however this only works to produce the same result as described before.
Azure Function code sample (not all code):
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Table;
using System.Threading.Tasks;
using System;
using Microsoft.Rest.Azure;
using Newtonsoft.Json.Linq;
// Authenticate with Azure
IAzure azure = await
Authentication.AuthenticateWithAzure(azureVmDeploymentRequest.SubscriptionId);
// Get current datetime
string Datetime = DateTime.Now.ToString("yyyy-MM-ddHHmmss");
log.LogInformation("Initiating VM ARM Template Deployment");
var parameters = azureVmDeploymentRequest.ToArmParameters(
subscriptionId: azureVmDeploymentRequest.SubscriptionId,
imageReferencePublisher: azureVmDeploymentRequest.ImageReferencePublisher
);
// AzNewVmRequestArmParametersMain is a custom object containing the
// parameters needed for the ARM template, constructed with GET SET
var parametersMain = new AzNewVmRequestArmParametersMain
{
parameters = parameters
};
var jParameters = JObject.FromObject(parameters);
// Deploy VM from ARM template if request is valid
var vmArmTemplateParams = new ARMTemplateDeploymentRequest
{
DeploymentName = "vmDeployTfLCP-" + Datetime,
ParametersObject = jParameters,
ResourceGroupName = azureVmDeploymentRequest.ResourceGroupName,
TemplateUri = Environment.GetEnvironmentVariable("VM_ARMTEMPLATE_URI"),
SasToken = Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_SASTOKEN")
};
ARM Template Deployment class code sample (not all code):
using Microsoft.Azure.Management.Fluent;
using System.Threading.Tasks;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Rest.Azure;
// Formulate ARM template URI
var ArmTemplatePath = ARMTemplateDeploymentRequest.TemplateUri + ARMTemplateDeploymentRequest.SasToken;
deployment = azure.Deployments.Define(ARMTemplateDeploymentRequest.DeploymentName)
.WithExistingResourceGroup(ARMTemplateDeploymentRequest.ResourceGroupName)
.WithTemplateLink(ArmTemplatePath, "1.0.0.0")
.WithParameters(ARMTemplateDeploymentRequest.ParametersObject)
.WithMode(Microsoft.Azure.Management.ResourceManager.Fluent.Models.DeploymentMode.Incremental)
.Create();
As an expected result, i'm expecting the code to simply initiate an ARM template deployment to a Azure Resource Group, however currently it is failing with the following message:
'The request content was invalid and could not be deserialized: 'Error
converting value "parameterValue" to type
'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Data.Definitions.DeploymentParameterDefinition'.
Path 'properties.parameters.vNetResourceGroup', line 8, position
48.'.'
According to my test, if you want to formulate the object using a dynamic variable, we need to create a new JObject. For example
My template.json
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": { "type": "string" },
"adminPassword": { "type": "securestring" }
},
"variables": {
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks','myVNet123456')]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/mySubnet')]"
},
"resources": [
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/publicIPAddresses",
"name": "myPublicIPAddress123456",
"location": "[resourceGroup().location]",
"properties": {
"publicIPAllocationMethod": "Dynamic",
"dnsSettings": {
"domainNameLabel": "myresourcegroupdns15896"
}
}
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/virtualNetworks",
"name": "myVNet123456",
"location": "[resourceGroup().location]",
"properties": {
"addressSpace": { "addressPrefixes": [ "10.0.0.0/16" ] },
"subnets": [
{
"name": "mySubnet",
"properties": { "addressPrefix": "10.0.0.0/24" }
}
]
}
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/networkInterfaces",
"name": "myNic562354",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses/', 'myPublicIPAddress123456')]",
"[resourceId('Microsoft.Network/virtualNetworks/', 'myVNet')]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses','myPublicIPAddress123456')]" },
"subnet": { "id": "[variables('subnetRef')]" }
}
}
]
}
},
{
"apiVersion": "2016-04-30-preview",
"type": "Microsoft.Compute/virtualMachines",
"name": "myVM",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Network/networkInterfaces/', 'myNic562354')]"
],
"properties": {
"hardwareProfile": { "vmSize": "Standard_DS1" },
"osProfile": {
"computerName": "myVM",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"storageProfile": {
"imageReference": {
"publisher": "MicrosoftWindowsServer",
"offer": "WindowsServer",
"sku": "2012-R2-Datacenter",
"version": "latest"
},
"osDisk": {
"name": "myManagedOSDisk",
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces','myNic562354')]"
}
]
}
}
}
]
}
My code
JObject parametesObjectv1 = new JObject(
new JProperty("adminUsername",
new JObject(
new JProperty("value", "azureuser")
)
),
new JProperty("adminPassword",
new JObject(
new JProperty("value", "Azure12345678")
)
)
);
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecret, tenantId, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(credentials)
.WithSubscription(subscriptionId);
if (azure.ResourceGroups.Contain(resourceGroupName) == false)
{
var resourceGroup = azure.ResourceGroups.Define(resourceGroupName)
.WithRegion(resourceGroupLocation)
.Create();
}
Console.WriteLine("start");
var deployment = azure.Deployments.Define(deploymentName)
.WithExistingResourceGroup(resourceGroupName)
.WithTemplateLink(pathToTemplateFile, "1.0.0.0")
.WithParameters(parametesObjectv1)
.WithMode(Microsoft.Azure.Management.ResourceManager.Fluent.Models.DeploymentMode.Incremental)
.Create();
Besdes, if you do not want to use dynamic variable, you can directly provide the url of your parameter.json and template.json to create resource
var deployment = azure.Deployments.Define(deploymentName)
.WithExistingResourceGroup(resourceGroupName)
.WithTemplateLink(pathToTemplateFile, "1.0.0.0")
.WithParametersLink(pathToJsonFile, "1.0.0.0")
.WithMode(Microsoft.Azure.Management.ResourceManager.Fluent.Models.DeploymentMode.Incremental)
.Create();
I was able to solve the problem by constructing parameter type based classes, and formulated a method to map out the parameter values to a ARM Parameter type.
ARM Template parameter classes extract:
namespace FuncApp.MSAzure.ARMTemplates.ARMParaneterTypes
{
public class ParameterValueString
{
public string Value { get; set; }
}
public class ParameterValueArray
{
public string[] Value { get; set; }
}
public class ParameterBoolValue
{
public bool Value { get; set; }
}
}
Mapping Class method extract:
public static AzNewVmRequestArmParameters ToArmParameters(
this AzNewVmRequest requestContent,
string adminUsername,
string adminPassword
)
{
return new AzNewVmRequestArmParameters
{
location = new ParameterValueString {
Value = requestContent.Location
},
adminUsername = new ParameterValueString
{
Value = adminUsername
},
adminPassword = new ParameterValueString
{
Value = adminPassword
},
};
}
'AzNewVmRequestArmParameters' Model class extract:
namespace FuncApp.MSAzure.VirtualMachines
{
public class AzNewVmRequestArmParameters
{
public ParameterValueString location { get; set; }
public ParameterValueString adminUsername { get; set; }
public ParameterValueString adminPassword { get; set; }
}
}
With these, i'm able to run the following code below (simplified) to formulate a valid jObject variable with the parameters that can be ready by the API:
var parameters = azureVmDeploymentRequest.ToArmParameters(
adminUsername: "azurevmadmin",
adminPassword: "P#ssword123!"
);
var jParameters = JObject.FromObject(parameters);
I have a custom Enricher: CorrelationIdsEnricher in order to write the CorrelationId and RequestId to log, and its constructor has an argument: ICorrelationContextProvider for passing the correlation context provider.
In my project, i config the serilog by reading the appsettings.json config file. And here's the config file:
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Common.Logging", "Common.Correlation" ],
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] [{EventId}] [{RequestId} {CorrelationId}] {Message}{NewLine}{Exception}",
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
}
}
],
"Enrich": [
"FromLogContext",
{
"Name": "WithCorrelationIds",
"Args": {
"provider": "Correlation.ServerContextProvider::Default, Common.Correlation"
}
}
],
}
}
However it can not set the CorrelationIdsEnricher correctly.
Does anyone know why?
The reason is that i forgot to add the WithCorrelationIds extension method. I thought implementing the CorrelationIdsEnricher is enough at first.
After looking into the source code ConfigurationReader.cs of serilog-settings-configuration, i found that i forget to implement extensions WithCorrelationIds.
That's to say, in order to support initializing serilog from configuration for our custom Enricher and Sink, we need to not only create the Enricher and Sink class but also implement their LoggerSinkConfiguration extensions.
Attached the CorrelationIdsEnricher implementation:
using Common.Correlation;
using Serilog.Core;
using Serilog.Events;
namespace Common.Logging.Enrichers
{
public class CorrelationIdsEnricher : ILogEventEnricher
{
private readonly ICorrelationContextProvider _provider;
public CorrelationIdsEnricher(ICorrelationContextProvider provider)
{
_provider = provider;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var (requestId, correlationId) = GetIds();
logEvent.AddPropertyIfAbsent(
propertyFactory.CreateProperty("RequestId", requestId, false));
logEvent.AddPropertyIfAbsent(
propertyFactory.CreateProperty("CorrelationId", correlationId, false));
}
private (string requestId, string correlationId) GetIds()
{
var ctx = _provider.Context;
return (ctx?.RequestId ?? string.Empty, ctx?.CorrelationId ?? string.Empty);
}
}
}
And LoggerEnrichmentConfiguration Extensions:
public static LoggerConfiguration WithCorrelationIds(
this LoggerEnrichmentConfiguration enrichmentConfiguration,
ICorrelationContextProvider provider)
{
return enrichmentConfiguration.With(new CorrelationIdsEnricher(provider));
}
And here's a mocked correlation provider:
public class CorrelationContextProvider : ICorrelationContextProvider
{
public static ICorrelationContextProvider Default { get; } = new CorrelationContextProvider();
public ICorrelationContext Context => new CorrelationContext();
}