Migration to Minimal API - Test Settings Json not overriding Program - asp.net-core

Thanks to this answer: Integration test and hosting ASP.NET Core 6.0 without Startup class
I have been able to perform integration tests with API.
WebApplicationFactory<Program>? app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
});
});
HttpClient? client = app.CreateClient();
This has worked using the appsettings.json from the API project. Am now trying to use integrationtestsettings.json instead using:
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(ProjectDirectoryLocator.GetProjectDirectory())
.AddJsonFile("integrationtestsettings.json")
.Build();
WebApplicationFactory<Program>? app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(configuration));
builder.ConfigureServices(services =>
{
});
});
_httpClient = app.CreateClient();
I have inspected the configuration variable and can see the properties loaded from my integrartiontestsettings.json file. However, the host is still running using the appsettings.json from the server project.
Previously, in .Net5, I was using WebHostBuilder and the settings were overridden by test settings.
WebHostBuilder webHostBuilder = new();
webHostBuilder.UseStartup<Startup>();
webHostBuilder.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(_configuration));
But cannot get the test settings to apply using the WebApplicationFactory.

It seems the method has changed.
Changing:
builder.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(configuration));
To:
builder.UseConfiguraton(configuration);
has done the trick.

builder.ConfigureAppConfiguration, now it's configuring the app (after your WebApplicationBuilder.Build() is called) and your WebApplication is created.
You need to "inject" your configurations before the .Build() is done. This is why you need to call UseConfiguraton instead of ConfigureAppConfiguration.

Related

API Gateway Ocelot .Net Core 6.1 Setup

The .Net 6 have removed the Start up Class and i am not able to find out how to configure Ocelot in new .Net 6 structure. I have found two methos
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddOcelot()// 1.ocelot.json goes where?
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOcelot(); // 2.what is the use of this
Let me know Please
Add json file called ocelot.json in your project.
Then do configure like this in Program.cs:
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("ocelot.json")
.Build();
var builder = WebApplication.CreateBuilder(args);
//.....
builder.Services.AddOcelot(configuration);
var app = builder.Build();
//........
app.UseOcelot();
//......
You probably already have solved this, so this is meant for all the other developers looking for a solution to this. Below are two ways of adding the Ocelot configuration.
Add a new JSON file in your project named ocelot.json and add your configuration for ocelot inside.
The file ocelot.json has to be registered in Program.cs in order for Ocelot to load the configuration for your API Gateway.
Below are two examples of how you can register your Ocelot configuration.
1. Adding Ocelot configuration without environment check
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("ocelot.json")
.Build();
builder.Services.AddOcelot(configuration);
var app = builder.Build();
await app.UseOcelot();
app.MapGet("/", () => "Hello World!");
app.Run();
As you can see we load the configuration from ocelot.json by using .ConfigurationBuilder(). We then parse the configuration to the method for adding Ocelot to the service container before registering it's middleware.
2. Add Ocelot configuration for the current environment
I tend to have multiple environments for production, testing, local development, etc... instead of re-writing/updating the configuration loader with the specific configuration file for Ocelot, we can do it by checking what environment we are running.
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile($"ocelot.{builder.Environment.EnvironmentName}.json", true, true)
.Build();
builder.Services.AddOcelot(configuration);
var app = builder.Build();
await app.UseOcelot();
app.MapGet("/", () => "Hello World!");
app.Run();
In the code above we use IHostEnvironment to get the current environment name. We can then use string interpolation to dynamically insert the environment name into the string of our ocelot configuration file.
For this to work, you would have to add a new configuration file for each environment like this:
ocelot.json
├─ ocelot.Development.json
├─ ocelot.Local.json
├─ ocelot.Test.json
You need to declare direct from your program.cs you add your Ocelot json file in bulder.configuration, than in services add the Ocelot reference, and in the end start the intance app.Ocelot().wait();
Here is an example, hope it helps
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("ocelot.json");
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOcelot();
var app = builder.Build();
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
app.UseHttpsRedirection();
app.UseOcelot().Wait();
app.UseAuthorization();
app.MapControllers();
app.Run();

Integration Testing Minimal API .NET 6 - nUnit

With .NET 5 I used the following in my nUnit [SetUp] for each test. This created a host and a client with which I could call my API with full integrations as defined in the Startup:
Microsoft.AspNetCore.TestHost.TestServer _testServer;
HttpClient _testClient;
[SetUp]
public async Task SetUp()
{
// Load up test configuration
IConfiguration config = new ConfigurationBuilder()
.SetBasePath("mypath")
.AddJsonFile("integrationsettings.json")
.Build();
// Create the host (using startup)
WebHostBuilder builder = new WebHostBuilder()
// Use startup from WebApp Server project
.UserStartup<MyWebApp.Startup>()
// Configure logging from integrationsettings.json
.ConfigureLogging((hostingContext, logging) =>
logging.AddConfiguration(config.GetSection("Logging"))
// Set core app configuration to use integrationsettings.json
.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(config))
// Add any additional services not loaded by startup
.ConfigureServices(services => // add additional services not laoded in Startup);
// Create the Server
_testServer = new TestServer(builder);
// Create the Client
_testClient = _testServer.CreateClient();
}
I could then use testClient HttpClient to work directly with my API.
Life was sweet.
I know I could still use the old model with .NET 6 (Program.cs + Startup.cs), but if I'm going to go with the flow, now that we have minimal API with just the Program.cs, how do I replicate the above?
From what I can gather, it looks like WebApplicationFactory is the key, but have not found anything that gets me over the line.
Is it as simple as adding the assembly that contains WebApplication and just build the app in test SetUp in the same way as I do in Program.cs on the server?
Is there a way to encapsulate the build logic (much like the old Startup.cs) so I do not need to duplicate the configuration used on the server in my tests?

Adding SignalR service as a singleton and then adding redis to it

Hello i have an app up and running using orleans and signalR and i use a HubConnectionBuilder to initialize my SignalRClient like this
public async Task<HubConnection> InitSignalRCLient()
{
Program.WriteConsole("Starting SignalR Client...");
var connection = new HubConnectionBuilder()
.ConfigureLogging(logging =>
logging
.AddProvider(new LogProvider(Log.logger, new LogProviderConfiguration
{
Category = LogCategory.SignalR,
Level = LogLevel.Warning
}))
)
.WithUrl(Configuration.GetConnectionString("SignalRInterface"))
.Build();
And then i add the service as a singleton in the configure service
services.AddSingleton(SignalRClient)
The problem is now that i want to use redis as a backplane to this and i am having issues adding the redis service to my current way of using SignalR
like this doesn't work
services.AddSingleton(SignalRClient).AddStackExchangeRedis();
according to the documentation https://learn.microsoft.com/en-us/aspnet/core/signalr/redis-backplane?view=aspnetcore-2.2 it wants you to add it like
services.AddSignalR().AddStackExchangeRedis("<your_Redis_connection_string>");
but that doesn't work with how i use SignalR. Is there anyway to get my implementation to work or anyone got any advice on how to tackle this?
Try to add in ConfigureServices this:
services.AddDistributedRedisCache(option =>
{
option.Configuration = Configuration.GetConnectionString(<your_Redis_connection_string>);
});
services.AddSignalR().AddStackExchangeRedis(Configuration.GetConnectionString(<your_Redis_connection_string>));
Also add this in Configure
app.UseSignalR(routes =>
{
routes.MapHub<your_Hub>("/yourHub");
});
And don't forget add abortConnect=False in connectionStrings

Swagger .Net Core

I am trying to configure swagger for my .Netcore App (1.1) and couldnt generate the docs.
Here is my configuration
public void ConfigureServices(IServiceCollection services) {
services.AddMvcCore().AddVersionedApiExplorer(o => o.GroupNameFormat = "1.0");
services.AddMvc();
services.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new HeaderApiVersionReader("api-version");
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.ReportApiVersions = true;
opt.AssumeDefaultVersionWhenUnspecified = true;
});
services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("1.0",new Info {Contact = new Contact() {Name="Admin" } });
// add a custom operation filter which sets default values
options.OperationFilter<SwaggerDefaultValues>();
});
}
In the Configure Method
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=User}/{action=Get}/{requestString?}");
});
app.UseSwagger(o=>
{
o.RouteTemplate = "docs/{documentName}/swagger.json";
});
app.UseSwaggerUI(
options =>
{
options.SwaggerEndpoint("/docs/1.0/swagger.json", "1.0");
});
When I run the application,
http://localhost:5000/docs/1.0/swagger.json
I am getting the below methods, None of my API's are discovered.
{"swagger":"2.0","info":{"contact":{"name":"Admin"}},"basePath":"/","paths":{},"definitions":{},"securityDefinitions":{}}
OK, I have reproduced your problem and found that the reason is the value in GroupNameFormat option.
If quickly, instead of const version string you should specify version format. As you want to have version in url as 1.0 you may use:
services.AddMvcCore().AddVersionedApiExplorer( o => o.GroupNameFormat = "VVVV" );
From Version Format section in Documentation:
Format Specifier: VVVV
Description: Major, minor version, and status
Examples: 1-RC -> 1.0-RC, 1.1 -> 1.1, 1 -> 1.0
Regarding AddMvcCore() vs AddMvc():
From the swagger docs at https://github.com/domaindrivendev/Swashbuckle.AspNetCore
Swashbuckle relies heavily on ApiExplorer, the API metadata layer that ships with ASP.NET Core. If you're using the AddMvc helper to bootstrap the MVC stack, then ApiExplorer will be automatically registered and SB will work without issue. However, if you're using AddMvcCore for a more paired-down MVC stack, you'll need to explicitly add the Api Explorer service:
services.AddMvcCore().AddApiExplorer();
If you also want AddVersionedApiExplorer(), chain that after AddApiExplorer()

How to cache static content using ASP.NET 5 and MVC 6?

This was previously achieved by adding some configuration to the web.config file, but now this file is to be extinguished.
I was expecting to find some methods or properties in the middleware declaration, but I haven't found:
app.UseStaticFiles();
So, which is now the procedure to cache static content as images, scripts, etc.?
Is there another middleware to do this or is this feature not implemented yet in MVC 6?
I'm looking for a way to add the cache-control, expires, etc. headers to the static content.
It is all about Middleware with AspNet Core;
Add the following to your Configure method in the Startup.cs file
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-encoding", "gzip");
context.Response.Body = new System.IO.Compression.GZipStream(context.Response.Body,
System.IO.Compression.CompressionMode.Compress);
await next();
await context.Response.Body.FlushAsync();
});
By the way for caching you would add this to the ConfigureServices method
services.AddMvc(options =>
{
options.CacheProfiles.Add("Default",
new CacheProfile()
{
Duration = 60
});
options.CacheProfiles.Add("Never",
new CacheProfile()
{
Location = ResponseCacheLocation.None,
NoStore = true
});
});
And decorate the control with
[ResponseCache(CacheProfileName = "Default")]
public class HomeController : Controller
{
...
Your title says compress, but your question body says cache. I'll assume you mean both.
Minification of css/javascript is already handled by the grunt task runner on publish. Caching and compression outside this seem like something a webserver is more suited to, rather than the application layer, so here's a great article that details the config for nginx to manage caching and compression for kestrel.
If you're using IIS, you can configure caching and compression directly on it, here's a tutorial. Considering the previous versions of MVC configured this functionality in web.config\system.Webserver which basically sets IIS config values, you can likely still use a web.config for the purposes of configuring IIS (only).