ASP.NET.core swagger generate swagger file programatically - c#

I have an asp.net core web api that I created from the wizard - and it's generated code like this:
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthorization();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
...
And if I run it, I get a swagger url, and I can go to
https://localhost:7100/swagger/v1/swagger.yaml
and get a yaml file... that's all good.
The Question
However, I need that yaml file in the CI/CD process for various reasons, so I want to get it dynamically after building, but starting a web server, calling it, and bringing it down has a lot of ways it can go wrong. What I'd like to do is just make a command line option to get the swagger file that never starts something listening on http. so I can just run "dotnet run myApi generate_swagger_yaml >swagger.yaml" Something like:
public static void Main(string[] args)
{
if (args[0] == "generate_swagger_yaml")
{
var yamlFile = ....somehow get the swagger yaml file...
Console.Writeline( yamlfile );
return 0;
}
...
but I can't figure out how to do it.

I would actually recommend not building this into your application directly. Swashbuckle (the library which actually generates the swagger doc at runtime) offers a CLI tool which can take an assembly DLL and spit out the swagger all in one command.
Check it out here: https://github.com/domaindrivendev/Swashbuckle.AspNetCore#swashbuckleaspnetcorecli
This can be used by your CI/CD to output the Swagger/OpenAPI document to a file .

Since my last answer, I've had to experiment more with Swashbuckle and Swagger generation and I've found the actual answer to your original question.
Once you create your builder:
var app = builder.Build();
You need to fetch the IServiceProvider from it and use it to resolve an instance of ISwaggerProvider. With this, you can get the swagger document and render it to Json or Yaml.
var swaggerProvider = app.Services.GetRequiredService<ISwaggerProvider>();
var swagger = swaggerProvider.GetSwagger("v1");
var stringWriter = new StringWriter();
swagger.SerializeAsV3(new OpenApiYamlWriter(stringWriter));
var swaggerYaml = stringWriter.ToString();
Console.WriteLine(swaggerYaml);
Where "v1" should be the default name of your swagger doc which you can specify when setting it up like so:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v7", null);
});

Related

Set Enviroment in WebApplication.CreateBuilder from configuration

So I'm trying to setup the webapp enviroment name according to the company deployment policy based on transformations of appsettings file during the pipeline deployment. We are using IWebHostEnvironment for reading the env and rootpath later in startup process.
But I'm running into the issue that I don't know how properly resolve. Is there any option to prebuild the configuration so that I can read value from it before creating new builder or this is the 'way' how to do it. To have one default pre-builder for configuration and than create regular one for the normal app.
It looks like chicken-egg problem to me.
Other solution would be to read 'environment' from the configuration directly but doesn't look clean to me.
var configBuilder = WebApplication.CreateBuilder(args);
configBuilder.Configuration.SetupConfiguration(args);
var section = configBuilder.Configuration.GetSection("Hosting")["Environment"];
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = section
});
By default environment comes from environment variables (see the docs). If you really need to read the environment from config file then the approach with WebApplicationOptions is the way to go. You can improve it a bit by just reading the config, not using WebApplication.CreateBuilder for that:
var cfgBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(...); // setup your config probably call cfgBuilder.SetupConfiguration(args)
var cfg = cfgBuildeR.Build();
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = cfg.GetSection("Hosting")["Environment"]
});

How to configure Middleware to Blazor app

I there,
I've a Blazor app here that I would like to add Middleware class to it. but I can't find the startup.cs/IApplicationBuilder to add it.
My project only have a Program.cs class with a void Main method.
So how to configure a Middleware? Just adding a class named Startup.cs did not do the trick.
VS 2022/ .Net 6.0
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Mvc.Infrastructure;
// [other using]
public class Program
{
public static void Main(string[] args)
{
var builder = WebAssemblyHostBuilder
.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services
.AddTransient<DevicePresentationService>()
// [other Service registration]
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Auth0", options.ProviderOptions);
options.ProviderOptions.ResponseType = "code";
});
var webAssemblyHost = builder.Build();
webAssemblyHost.RunAsync();
}
}
So how to configure a Middleware?
Middleware runs on the server. You posted the startup code for the Client.
Middleware is 'not applicable' in a Browser based app.
This is the new structure in .Net 6. You need to add all previous Startup.cs logic to Program.cs
To add your middleware, add:
var app = builder.Build();
and
app.MyMiddleware();
Make sure the entire namespace of the Middleware is added to your usings at the top of your file, or in global usings.
Documentation
EDIT: I didn't see that this a web assembly Blazor application. #Henk Holterman is correct, you cannot use middleware on a client only application. You will need to add the middleware to your Server.

How to use settings from integration testing project in the Startup class of api project?

I'm trying to write integration tests for web api, I have two appsettings.json files one for api project and other for integration tests. I want to use the values(azure storage connection strings) from integration testing project in Startup class of web api.
I have tried creating CustomWebApplicatonFactory it didn't work since the Startup class of web api gets settings like shown below.
//Configure services method in Startup class
public virtual void ConfigureServices(IServiceCollection services)
{
var settings = ConfigurationProvider.GetConfiguration();
services.TryAddSingleton(settings);
services.AddHttpClient();
var azureTableStorageConnectionString =
settings["AzureMainStoreConnectionStringSecretName"];
//Other Startup related code
}
I want to change the value of "azureTableStorageConnectionString" from my integration test project. Help and suggestions are much appreciated.
First of all, instead of using the static function ConfigurationProvider.GetConfiguration() inject IConfiguration into your Startup class. The host that is usually defined in the Program class builds that for you so you can inject it.
Then instead of using WebApplicationFactory<TStartup> you can build a test-host yourself like this.
[Fact]
public async Task LoadWeatherForecastAsync()
{
var webHostBuilder = new WebHostBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
builder.SetBasePath(AppContext.BaseDirectory);
// this is an appsettings file located in the test-project!
builder.AddJsonFile("appsettings.Testing.json", false);
})
.UseStartup<Startup>();
var host = new TestServer(webHostBuilder);
var response = await host.CreateClient().GetAsync("weatherforecast");
Assert.True(response.IsSuccessStatusCode);
}
I've created a sample on github. You can clone it and try it out.

How to fix 500 Internal Server Error for POST integration tests using TestServer and Antiforgery? ASP.NET Core

I've got a working ASP.NET Core 2.2 implementation that utilizes both MVC and API controllers, and I'm putting together an integration test project to cover everything that has already been tested manually - the basic crud, mostly. Everything works except the tests that use PostAsync to POST data. These tests always get a 500 Internal Server Error as a response from the client, and I cannot for the life of me figure out why. Oi!
The TestServer setup is a pretty standard approach that I've seen on many different blogs and articles. A TestStartup class extends the standard Startup, and overrides the configuration to use in-memory database with seed data. My test fixture base then uses the TestStartup to create a server, and a client, and has a method, which I know works fine, to extract the antiforgery token and add it to forms and headers. All other tests verifying all other aspects of CRUD are working, proving that the seeded data can be retrieved, via both MVC and API calls.
The POST calls that eventually fail with a 500 Internal Server Error do make it into the controller, and the subsequent repository, just fine. With all these aspects in place, I've yet to be able to see the source of the 500.
[Fact]
public async void CreatePost_ShouldReturnViewWithNewlyCreatedLabelData()
{
// Arrange
var formData = await EnsureAntiForgeryTokenOnForm(new Dictionary<string, string>()
{
{ "Name", TestDataGraph.Labels.LabelNew.Name },
{ "WebsiteUrl", TestDataGraph.Labels.LabelNew.WebsiteUrl }
});
// Act
var response = await Client.PostAsync("/labels/create", new FormUrlEncodedContent(formData));
// Assert
Assert.Equal(HttpStatusCode.Found, response.StatusCode);
Assert.Equal("/Labels", response.Headers.Location.ToString());
}
This is a simple example test in Xunit that attempts to validate the creation of a new simple object, type Label, via MVC route, which follows the standard path format, having been scaffolded. This test will make it into the controller, and its repository, but the response will be a 500 Internal Server Error.
Could I have missed something important in Startup? Any ideas for finding further details about this failure? Thanks in advance! I can post more code or details if they will be helpful.
Try adding trace logging... Trace logging will display activity in the .Net Core framework.
...
public static ILogger<ConsoleLoggerProvider> AppLogger = null;
public static ILoggerFactory loggerFactory = null;
//
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder => builder
.AddConsole()
.AddFilter(level => level >= LogLevel.Trace)
);
loggerFactory = services.BuildServiceProvider().GetService<ILoggerFactory>();
AppLogger = loggerFactory.CreateLogger<ConsoleLoggerProvider>();
...
An example trace log:
trce: Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler[7]
Could not find a file for view at path '/Views/Home/_Layout.cshtml'.
After you have resolved the issue, change the LogLevel to a more appropriate value.

What is the proper place for db seeding from json files in ASP.NET Core 2.0?

In my Core 1.0 app, Startup ctor loaded several extra json files into the configuration...
public Startup(IHostingEnvironment env) {
Environment = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json")
.AddJsonFile($"activityGroups.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Then in ConfigureServices, I grabbed the data from these out of the config into strongly typed lists...
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<Models.DbContext>(
options => options.UseSqlServer(
Configuration["Data:DefaultConnection:ConnectionString"]
)
);
services.AddScoped<Repository>();
services.AddTransient<DbSeed>();
services.AddOptions();
services.Configure<List<Models.Task>>(Configuration.GetSection("ActivityGroups"));
}
Then, in Configure, the strongly typed list was injected and use for db init..
public void Configure(
IApplicationBuilder app,
DbSeed seed,
ILoggerFactory logging,
IOptions<List<ActivityGroup>> activityGroupConfig
) {
app.UseAuthentication();
app.UseMvc();
bool seedDb;
bool.TryParse(Configuration.GetSection("SeedDb").Value, out seedDb);
if (seedDb) {
seed.EnsureSeeded(activityGroupConfig.Value);
}
}
However...
In 2.0 projects, move the SeedData.Initialize call to the Main method
of Program.cs...
As of 2.0, it's bad practice to do anything in BuildWebHost except
build and configure the web host. [configure web host includes data seeding?] Anything that's about running the
application should be handled outside of BuildWebHost — typically in
the Main method of Program.cs.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code
Yet, in "new" Main(string[] args) we don't have access to the dbcontext (until Startup.ConfigureServices). Even if in the SeedData.Initialize(services); call as above in the MS documentation, how would SeedData have access to the connection string that is not defined until later in configure services?
The adoption of this new 2.0 pattern is highly recommended and is
required for product features like Entity Framework (EF) Core
Migrations to work.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#update-main-method-in-programcs
I've been googling and digging through docs and samples (that example seeds with hardcoded values) without getting a clear picture (yet) of the expectations or recommended design (beyond, "dont do what you did previously".)
Anyone here more familiar or experienced with how to do database seeding from json on disk in ASP.NET Core 2.0?
So, with help from this SO question, I got the gist of getting the services from within Program.cs
public class Program {
public static void Main(string[] args) {
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILoggerFactory>();
var ctx = services.GetRequiredService<DataContext>();
// Now I can access the DbContext, create Repository, SeedData, etc!
Now, the problem is in trying to figure out how to access values from appsettings.json, new SO question here.

Categories