InstantAPIs icon indicating copy to clipboard operation
InstantAPIs copied to clipboard

Support for Startup.cs - ConfigureServices and Configure

Open Misiu opened this issue 2 years ago • 8 comments

I have a .NET 6 project, but I'm using Program.cs and 'Startup.cs' approach.

There is a method to add InstantAPI using services.AddInstantAPIs();, but no way to configure it inside Configure(IApplicationBuilder app)

https://github.com/csharpfritz/InstantAPIs/blob/main/Fritz.InstantAPIs/WebApplicationExtensions.cs#L18 supports IEndpointRouteBuilder but a method that supports IApplicationBuilder is missing.

Any plans to add support for it?

Misiu avatar Apr 28 '22 07:04 Misiu

Don't you use the app.UseEndpoints? If you are, you can do.

app.UseEndpoints(x =>
{
	x.MapInstantAPIs<MyContext>(config =>
	{
		config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.All, "addressBook");
	});
	x.MapControllers(); // example of what you are already mapping. 
});

verbedr avatar Apr 28 '22 15:04 verbedr

@verbedr thank you for the reply. Will try that ASAP

Misiu avatar Apr 29 '22 11:04 Misiu

@verbedr this works well. It would be useful to add this example to the readme. What do you think?

Misiu avatar May 04 '22 09:05 Misiu

@verbedr quick update: this works partially. When I create a new project using .NET 6 and add everything as shown in readme, I'm able to filter DBSets, using below snippet:

app.MapInstantAPIs<MyContext>(config =>
{
    config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.All, "addressBook");
});

but when I do the same in Progam.cs and Startup.cs approach (a .NET 3.1 project which is converted to .NET 6), nothing is filtered out - I get all the DBSets.

Here is my entire Startup.cs file:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Fritz.InstantAPIs;

namespace Test2
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "Test2", Version = "v1" });
            });

            services.AddInstantAPIs();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Test2 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();

                endpoints.MapInstantAPIs<MyContext>(config =>
                {
                    config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.Get, "addressBook");
                });
            });
        }
    }
}

MyContext.cs

using Microsoft.EntityFrameworkCore;

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options) { }

    public DbSet<Contact> Contacts => Set<Contact>();
    public DbSet<Address> Addresses => Set<Address>();

}

public class Contact
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

Quick way to reproduce:

  1. Create a .NET 5 ASP.NET Core Web API
  2. Change TargetFramework to net6.0
  3. Update Swashbuckle.AspNetCore to 6.3.1
  4. Add Fritz.InstantAPIs
  5. Follow steps in readme

You will get this result: image Instead of: image when running from Program.cs approach:

using Fritz.InstantAPIs;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddSqlite<MyContext>("Data Source=contacts.db");
builder.Services.AddInstantAPIs();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.MapInstantAPIs<MyContext>(config =>
{
    config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.Get, "addressBook");
});

app.Run();

Misiu avatar May 04 '22 11:05 Misiu

Yes, let’s get the documentation started and add samples like this

Jeff

On May 4, 2022, at 07:05, Tomasz @.***> wrote:

 @verbedr quick update: this works partially. When I create a new project using .NET 6 and add everything as shown in readme, I'm able to filter DBSets, using below snippet:

app.MapInstantAPIs<MyContext>(config => { config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.All, "addressBook"); }); but when I do the same in Progam.cs and Startup.cs approach (a .NET 3.1 project which is converted to .NET 6), nothing is filtered out - I get all the DBSets.

Here is my entire Startup.cs file:

using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; using Fritz.InstantAPIs;

namespace Test2 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "Test2", Version = "v1" });
        });

        services.AddInstantAPIs();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Test2 v1"));
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();

            endpoints.MapInstantAPIs<MyContext>(config =>
            {
                config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.Get, "addressBook");
            });
        });
    }
}

} MyContext.cs

using Microsoft.EntityFrameworkCore;

public class MyContext : DbContext { public MyContext(DbContextOptions<MyContext> options) : base(options) { }

public DbSet<Contact> Contacts => Set<Contact>();
public DbSet<Address> Addresses => Set<Address>();

}

public class Contact { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set; } }

public class Address { public int Id { get; set; } public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } } Quick way to reproduce:

Create a .NET 5 ASP.NET Core Web API Change TargetFramework to net6.0 Update Swashbuckle.AspNetCore to 6.3.1 Add Fritz.InstantAPIs Follow steps in readme You will get this result:

Instead of:

when running from Program.cs approach:

using Fritz.InstantAPIs;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen();

builder.Services.AddSqlite<MyContext>("Data Source=contacts.db"); builder.Services.AddInstantAPIs();

var app = builder.Build();

// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.MapInstantAPIs<MyContext>(config => { config.IncludeTable(db => db.Contacts, ApiMethodsToGenerate.Get, "addressBook"); });

app.Run(); — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you are subscribed to this thread.

csharpfritz avatar May 04 '22 11:05 csharpfritz

@csharpfritz I agree on that, but with Progam.cs and Startup.cs approach the filtering (IncludeTable and ExcludeTable) isn't working.

Misiu avatar May 04 '22 11:05 Misiu

In WebApplicationExtensions.cs I've changed

public static IEndpointRouteBuilder MapInstantAPIs<D>(this IEndpointRouteBuilder app, Action<InstantAPIsConfigBuilder<D>> options = null) where D : DbContext
{
	switch (app)
	{
		case IApplicationBuilder applicationBuilder:
			AddOpenAPIConfiguration(app, options, applicationBuilder);
			break;
		case IEndpointRouteBuilder routeBuilder:
			AddOpenAPIConfiguration(routeBuilder, options);
			break;
	}

	// Get the tables on the DbContext
	var dbTables = GetDbTablesForContext<D>();

	var requestedTables = !Configuration.Tables.Any() ?
			dbTables :
			Configuration.Tables.Where(t => dbTables.Any(db => db.Name.Equals(t.Name, StringComparison.OrdinalIgnoreCase))).ToArray();

	MapInstantAPIsUsingReflection<D>(app, requestedTables);

	return app;
}

and added:

private static void AddOpenAPIConfiguration<D>(IEndpointRouteBuilder routeBuilder, Action<InstantAPIsConfigBuilder<D>> options) where D : DbContext
{
	// Check if AddInstantAPIs was called by getting the service options and evaluate EnableSwagger property
	var serviceOptions = routeBuilder.ServiceProvider.GetRequiredService<IOptions<InstantAPIsServiceOptions>>().Value;
	if (serviceOptions == null || serviceOptions.EnableSwagger == null)
	{
		throw new ArgumentException("Call builder.Services.AddInstantAPIs(options) before MapInstantAPIs.");
	}

	//var webApp = (WebApplication)app;
	if (serviceOptions.EnableSwagger == EnableSwagger.Always || (serviceOptions.EnableSwagger == EnableSwagger.DevelopmentOnly /*&& webApp.Environment.IsDevelopment()*/))
	{
		//routeBuilder.UseSwagger();
		//routeBuilder.UseSwaggerUI();
	}

	var ctx = routeBuilder.ServiceProvider.CreateScope().ServiceProvider.GetService(typeof(D)) as D;
	var builder = new InstantAPIsConfigBuilder<D>(ctx);
	if (options != null)
	{
		options(builder);
		Configuration = builder.Build();
	}
}

Things that need to be added: -checking if IsDevelopment() -enabling SwaggerUI.

I'm not sure if InstantAPIs should enable and configure Swagger. It's up to the developer to setup it, besides that, it's enabled by default in new projects.

Misiu avatar May 04 '22 12:05 Misiu

@verbedr @csharpfritz thoughts on this? I can create PR showing my changes. Not everyone (including me) can migrate Startup and Program to .NET 6 preferred approach.

As I mentioned before when creating new projects Swagger UI is enabled by default (unless we uncheck a checkbox during project creation in VS2022), so we should only detect if it is enabled and add the correct endpoints.

Ideally Swashbuckle dependency should be removed, because some may prefer and use NSwag (ref: https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-6.0) But this would require adding two more packages - one for Swagger and one for NSwag.

Misiu avatar May 06 '22 09:05 Misiu