Hejle
Hejle
CC#
Created by Hejle on 5/9/2024 in #help
MinimalApi vs Mvc - ValidationOfHeaders
I have been playing around with MinimalApi, and was surprised to see that MinimalApi could not handle validating Required HttpHeaders in the same way that MvcControllers can: MinimalApi Code
app.MapGet("/weatherforecast", ([FromHeader]string TraceId) =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.MapGet("/weatherforecast", ([FromHeader]string TraceId) =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
Mvc Code
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get([FromHeader]string TraceId)
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get([FromHeader]string TraceId)
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
The Mvc Examples returns a ValidationProblemDetails-based error if TraceId while MinimalApi just retuns 400. Does anyone know, how I can get MinimalApi to validate headers and send a meaningful error back (hopefully a ValidationProblemDetails).
7 replies
CC#
Created by Hejle on 12/12/2023 in #help
Docker - NanoServer - Dotnet with PowershellCore
I have created a DockerFile with Dotnet running in NanoServer, and got a bit annoyed at the CMD in there. So I have spent hours trying to add PowerShellCore to it. The closest, I think, is this post: https://stackoverflow.com/questions/65104246/how-to-install-powershell-core-in-aspnet-nanoserver-docker-container From there I have been look at this DockerFile: https://github.com/PowerShell/PowerShell-Docker/blob/master/release/7-3/nanoserver2022/docker/Dockerfile So, I have tried to run it with the DockerFile i have added. Running this, my Container is created and everything works, but Powershell has not been added. From the example in Github, the escapecharacter ` is used:
# escape=`
# Copyright (c) Microsoft Corporation.
# escape=`
# Copyright (c) Microsoft Corporation.
But this fails with: "Syntax error - can't find = in "`". Must be of the form: name=value" so I have changed that to:
# escape=\
# Copyright (c) Microsoft Corporation.
# escape=\
# Copyright (c) Microsoft Corporation.
My DockerFile can be seen here:
27 replies
CC#
Created by Hejle on 11/21/2023 in #help
Manual setup of OpenTelemetry with/without Microsoft.Extensions.Hosting in a Console application
After seeing Aspire I have decided to see how OpenTelemetry, and i first created a console application following the docs on https://opentelemetry.io/docs/instrumentation/net/manual/. However, when I tried to move it into an easier setup using the extensionsmethod available in OpenTelemetry.Extensions.Hosting i could not get it to work. I think I might just misunderstand something about how it works. So I have this working console application, which outputs the Actitivy in the console:
public static class Program
{
public const string SERVICE_NAME = "MyService";
public const string SERVICE_VERSION = "1.0.0";

public static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection();

serviceCollection.AddSingleton(new ActivitySource(SERVICE_NAME));
serviceCollection.AddSingleton<TestLogic>();

var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(SERVICE_NAME)
.ConfigureResource(resource => resource.AddService(serviceName: SERVICE_NAME, serviceVersion: SERVICE_VERSION))
.AddConsoleExporter()
.Build();
serviceCollection.AddSingleton(tracerProvider);

var services = serviceCollection.BuildServiceProvider();

var helloWorld = await services.GetRequiredService<TestLogic>().HelloWorldMethod();
Console.WriteLine(helloWorld);
Console.WriteLine("Program ended");
}
}

public class TestLogic(ActivitySource activitySource)
{
private readonly ActivitySource _activitySource = activitySource;

public async Task<string> HelloWorldMethod()
{
using var activity = _activitySource.StartActivity("HelloWorldMethod");
await Task.Delay(1000);
return "Hello World";
}
}
public static class Program
{
public const string SERVICE_NAME = "MyService";
public const string SERVICE_VERSION = "1.0.0";

public static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection();

serviceCollection.AddSingleton(new ActivitySource(SERVICE_NAME));
serviceCollection.AddSingleton<TestLogic>();

var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(SERVICE_NAME)
.ConfigureResource(resource => resource.AddService(serviceName: SERVICE_NAME, serviceVersion: SERVICE_VERSION))
.AddConsoleExporter()
.Build();
serviceCollection.AddSingleton(tracerProvider);

var services = serviceCollection.BuildServiceProvider();

var helloWorld = await services.GetRequiredService<TestLogic>().HelloWorldMethod();
Console.WriteLine(helloWorld);
Console.WriteLine("Program ended");
}
}

public class TestLogic(ActivitySource activitySource)
{
private readonly ActivitySource _activitySource = activitySource;

public async Task<string> HelloWorldMethod()
{
using var activity = _activitySource.StartActivity("HelloWorldMethod");
await Task.Delay(1000);
return "Hello World";
}
}
I then tried using the Generic Host builder and it does not write the activity to the console:
public static class Program
{
public const string SERVICE_NAME = "MyService";
public const string SERVICE_VERSION = "1.0.0";

public static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder();
builder.Services.AddSingleton<TestLogic>();
builder.Services.AddOpenTelemetry()
.ConfigureResource(resourceBuilder => resourceBuilder.AddService(serviceName: SERVICE_NAME, serviceVersion: SERVICE_VERSION))
.WithTracing(tracerProviderBuilder => tracerProviderBuilder.AddConsoleExporter());
builder.Services.AddSingleton(new ActivitySource(SERVICE_NAME));

var host = builder.Build();
await host.StartAsync(); //Used as OpenTelemetry adds the HostedService "TelemetryHostedService"

var helloWorld = await host.Services.GetRequiredService<TestLogic>().HelloWorldMethod();
Console.WriteLine(helloWorld);
Console.WriteLine("Program ended");
await host.StopAsync();
}
}
public class TestLogic(ActivitySource activitySource)
{
private readonly ActivitySource _activitySource = activitySource;

public async Task<string> HelloWorldMethod()
{
using var activity = _activitySource.StartActivity("HelloWorldMethod");
await Task.Delay(1000);
return "Hello World";
}
}
public static class Program
{
public const string SERVICE_NAME = "MyService";
public const string SERVICE_VERSION = "1.0.0";

public static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder();
builder.Services.AddSingleton<TestLogic>();
builder.Services.AddOpenTelemetry()
.ConfigureResource(resourceBuilder => resourceBuilder.AddService(serviceName: SERVICE_NAME, serviceVersion: SERVICE_VERSION))
.WithTracing(tracerProviderBuilder => tracerProviderBuilder.AddConsoleExporter());
builder.Services.AddSingleton(new ActivitySource(SERVICE_NAME));

var host = builder.Build();
await host.StartAsync(); //Used as OpenTelemetry adds the HostedService "TelemetryHostedService"

var helloWorld = await host.Services.GetRequiredService<TestLogic>().HelloWorldMethod();
Console.WriteLine(helloWorld);
Console.WriteLine("Program ended");
await host.StopAsync();
}
}
public class TestLogic(ActivitySource activitySource)
{
private readonly ActivitySource _activitySource = activitySource;

public async Task<string> HelloWorldMethod()
{
using var activity = _activitySource.StartActivity("HelloWorldMethod");
await Task.Delay(1000);
return "Hello World";
}
}
Does anyone know why I don't see the Activity in the console output? Or maybe know how I should set the Generic Host example up?
4 replies
CC#
Created by Hejle on 6/2/2023 in #help
.NET Generic Host Writing "Application Started" when its done with its work
So I have been playing around with the Generic Host (Microsoft.Hosting.Extensions and HostedServices. So I have this output, where "Application started" comes after my service have stopped working. I am not sure about how, I should structure the code to get the "Application Started" before my service runs. I am currently using StartAsync, but gets the same behaviour with RunAsync StartAsync can be seen here: https://source.dot.net/#Microsoft.Extensions.Hosting/Internal/Host.cs,57 My Output
info: MyService[0]
Service started
info: MyService[0]
Service done
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\JOAH\source\repos\GenericHost\GenericHost\bin\Debug\net7.0
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: MyService[0]
Service stopped
info: MyService[0]
Service started
info: MyService[0]
Service done
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\JOAH\source\repos\GenericHost\GenericHost\bin\Debug\net7.0
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: MyService[0]
Service stopped
My Code
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = Host.CreateDefaultBuilder()
.UseConsoleLifetime()
.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
}).ConfigureLogging(loggingBuilder => loggingBuilder.AddConsole())
.Build();
var cts = new CancellationTokenSource();

await host.StartAsync(cts.Token);
await host.StopAsync();

class MyService : IHostedService
{
private readonly ILogger<MyService> _logger;

public MyService(ILogger<MyService> logger)
{
_logger = logger;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service started");
await Task.Delay(1000); //Important work, probably
_logger.LogInformation("Service done");
return;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service stopped");
return Task.CompletedTask;
}
}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = Host.CreateDefaultBuilder()
.UseConsoleLifetime()
.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
}).ConfigureLogging(loggingBuilder => loggingBuilder.AddConsole())
.Build();
var cts = new CancellationTokenSource();

await host.StartAsync(cts.Token);
await host.StopAsync();

class MyService : IHostedService
{
private readonly ILogger<MyService> _logger;

public MyService(ILogger<MyService> logger)
{
_logger = logger;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service started");
await Task.Delay(1000); //Important work, probably
_logger.LogInformation("Service done");
return;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service stopped");
return Task.CompletedTask;
}
}
26 replies
CC#
Created by Hejle on 1/21/2023 in #help
Certificate Authorization using MinimalAPI
Hi, I have been trying to authorize calls to a localhost service using certificates. Using the certificates, I can then add Clains to the client, that can be used to define what can be accessed and not accessed. This all works, and I can check the claims by accessing User from the HttpContext. However, I would like to use the RequireAuthorization method instead, but when I enable it, it looks like my certificate is never validated.
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
builder.Services.Configure<KestrelServerOptions>(kestrelServerOptions =>
{
kestrelServerOptions.ConfigureHttpsDefaults(httpsConnectionAdapterOptions =>
{
httpsConnectionAdapterOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsConnectionAdapterOptions.AllowAnyClientCertificate();
});
});
builder.Services.AddScoped<ICertificateValidationService, X509CertificateValidationService>();

builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(ValidateCertificateHandlerMethod());
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("HasAccesPolicy", policy =>
policy.RequireClaim("Access", "HasAccess"));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthentication();

app.MapGet("/", (ClaimsPrincipal user) => user.Claims.Select(x => new {x.Type, x.Value}));

app.MapGet("/SecureService", (HttpContext context) =>
{
var claims = context.User.Claims;
if (claims.FirstOrDefault(x => x.Type == "Access" && x.Value == "HasAccess") == null)
{
context.Response.StatusCode = 403;
return "";
}
return "Hello from secure service";
}).RequireAuthorization("HasAccesPolicy");
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
builder.Services.Configure<KestrelServerOptions>(kestrelServerOptions =>
{
kestrelServerOptions.ConfigureHttpsDefaults(httpsConnectionAdapterOptions =>
{
httpsConnectionAdapterOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsConnectionAdapterOptions.AllowAnyClientCertificate();
});
});
builder.Services.AddScoped<ICertificateValidationService, X509CertificateValidationService>();

builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(ValidateCertificateHandlerMethod());
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("HasAccesPolicy", policy =>
policy.RequireClaim("Access", "HasAccess"));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthentication();

app.MapGet("/", (ClaimsPrincipal user) => user.Claims.Select(x => new {x.Type, x.Value}));

app.MapGet("/SecureService", (HttpContext context) =>
{
var claims = context.User.Claims;
if (claims.FirstOrDefault(x => x.Type == "Access" && x.Value == "HasAccess") == null)
{
context.Response.StatusCode = 403;
return "";
}
return "Hello from secure service";
}).RequireAuthorization("HasAccesPolicy");
So my authentication works when I remove the "RequireAuthorization", but I would rather use that method than getting claims from the httpcontext.
6 replies