Giter Site home page Giter Site logo

instantapis's Introduction

InstantAPIs

Nuget Instant APIs Documentation GitHub last commit GitHub contributors

This article contains two different ways to get an instant API:

  • An API based on a DbContext, it will generate the routes it needs given a database class. There are two implementations of this: Reflection and a source generator.
  • An API based on a JSON file.

DbContext based API

A proof-of-concept library that generates Minimal API endpoints for an Entity Framework context. Right now, there are two implementations of this: one that uses Reflection (this is the one currently in the NuGet package), the other uses a source generator. Let's see how both of them work.

For a given Entity Framework context, MyContext

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

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

}

We can generate all of the standard CRUD API endpoints with the Reflection approach using this syntax in Program.cs

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.MapInstantAPIs<MyContext>();

app.Run();

Now we can navigate to /api/Contacts and see all of the Contacts in the database. We can filter for a specific Contact by navigating to /api/Contacts/1 to get just the first contact returned. We can also post to /api/Contacts and add a new Contact to the database. Since there are multiple DbSet, you can make the same calls to /api/Addresses.

You can also customize the APIs if you want

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

This specifies that the all of the CRUD methods should be created for the Contacts table, and it prepends the routes with addressBook.

The source generator approach has an example in the WorkingApi.Generators project (at the moment a NuGet package hasn't been created for this implementation). You specify which DbContext classes you want to map with the InstantAPIsForDbContextAttribute, For each context, an extension method named Map{DbContextName}ToAPIs is created. The end result is similar to the Reflection approach:

[assembly: InstantAPIsForDbContext(typeof(MyContext))]

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.MapMyContextToAPIs();

app.Run();

You can also do customization as well

app.MapMyContextToAPIs(options =>
	options.Include(MyContext.Contacts, "addressBook", ApisToGenerate.Get));

Feel free to try both approaches and let Fritz know if you have any issues with either one of them. The intent is to keep feature parity between the two for the forseable future.

Demo

Check out Fritz giving a demo, showing the advantage of InstantAPIs on YouTube: https://youtu.be/vCSWXAOEpBo

A JSON based API

An API will be generated based on JSON file, for now it needs to be named mock.json.

A typical content in mock.json looks like so:

{
  "products" : [{
    "id": 1,
    "name": "pizza"
  }, {
    "id": 2,
    "name": "pineapple pizza"
  }, {
    "id": 3,
    "name": "meat pizza"
  }],
  "customers" : [{
    "id": 1,
    "name": "customer1"
  }]
  
}

The above JSON will create the following routes:

HTTP Verb Endpoint
GET /products
GET /products/{id}
POST /products
DELETE /products/{id}
GET /customers
GET /customers/{id}
POST /customers
DELETE /customers/{id}

Demo

To use this, do the following:

  1. Create a new minimal API, if you don't already have one:

    dotnet new web -o DemoApi -f net6.0
    cd DemoApi 
  2. Add the NuGet package for InstantAPIs:

    dotnet add package InstantAPIs --prerelease
  3. In Program.cs, add the following namespace:

    using Mock;
  4. Create a file mock.json and give it for example the following content:

    {
       "products" : [{
         "id": 1,
         "name": "pizza"
       }]
     }
  5. Now add the following code for the routes to be created:

    app.UseJsonRoutes();

Here's an example program:

using Mock;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.UseJsonRoutes();
app.Run();

Coming features

Support for:

Community

This project is covered by a code of conduct that all contributors must abide by. Contributions are welcome and encouraged..

instantapis's People

Contributors

anuraj avatar christiannagel avatar csharpfritz avatar davidbuckleyni avatar jamesmontemagno avatar jasonbock avatar mkdotcs avatar mojjozz avatar mpaulosky avatar scottkane avatar softchris avatar timheuer avatar verbedr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

instantapis's Issues

Custom API Endpoints

As of now, (correct me if I'm wrong) InstantAPIs supports creating all API endpoints (HTTP GET, GET by ID, POST, PATCH, PUT, and DELETE) or just one. It might be nice if it allowed the user to customize which ones they create. For example, a user might only want HTTP GET and DELETE endpoints.

Validation layer

It would be nice if this whole process could be like a pluggable pipeline (kind of like middleware) where I can just slot in additional functionality. So initial example would just be to add a fluent validator based on each db model and use attributes like [Required] or [MaxLength(500)] to hint to the validation rules that will apply for that object.

public class MyClass
{
    [Required]
    public string Name { get; set; }
    [MaxLength(500)]
    public string Description { get; set; }
}

public class MyClassValidator : AbstractValidator<MyClass>
{
    public MyClassValidator()
    {
        RuleFor(request => request.Name)
            .Must(x => !string.IsNullOrWhiteSpace(x))
            .WithMessage(x => "Name is required!");
        RuleFor(request => request.Description)
                .MaximumLength(500)
                .WithMessage(x => "Description must be less than 500 characters!");
    }
}

IncludeTable/ExcludeTable - appropriate noun?

Using EntityFrameworkCore we can map an entity to multiple tables or multiple entities to a single table (TPT, TPH etc)

This made me wonder about the usage of nouns like “table” and “column” (when configuring IncludeTable/ExcludeTable). Perhaps a better and more consistent fit with EFCore domain language would be “entity” (or “entity type”) and “property”.

Hence IncludeEntity/ExcludeEntity instead…?

There is not always a one to one mapping regards entity to table.

Need to have ability to exclude specific entities or properties

I love the idea behind this project. This is great and I am looking forward to this becoming more mature, stable and famous!

The following configuration options might be required in a lot of cases:

  1. Exclude specific entities: I might be fine with exposing a 100 other entities within my DbContext in this way. But I need the ability to exclude some (for example User entity), so that I can create my own endpoints with my own business logic for it.
  2. Exclude specific properties of an entity: In some cases, there may be properties that are not meant to be modified directly by a PUT or a POST request. For example, columns that are updated behind the scenes via some business logic.

Thanks,
Raghu

Allow authentication/authorization to be configured

We need to allow authentication and authorization to be configured.

  • allow authorization for the generated APIs and be able to specify authenticated users, required policies, etc...
  • allow Swagger authentication to be configured

IModel

It might be nice if the user was allowed to inherit their model from an IModel interface that looked something like this:

interface IModel
{
    int/Guid Id { get; set;}
}

This would allow the code not to have to use the Type.GetProperty() method and provide more IntelliSense.

Return HTTP results

Methods should return appropriate 200, 201, 404, etc HTTP status codes for their interactions

MapInstantApis fails when key is not named id when using fluent interface [Bug]

When using the fluent interface and not marking your keys with the [Key] annotation and those keys are not named "id" then the _IdLookup dictionary will not be filled with the keys for the entity.

This is due to MapApiExtensions having id hard coded into it. I wonder if it would be easy to add a configuration option to define how the property is matched? For example {ClassName}Id or {ClassName}_Id

I did a quick proof of concept by adding the following code in line 26 of MapApiExtensions:

var theType = typeof(C);
var keyName = $"{theType.Name}Id";
var idProp = theType.GetProperty("id", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ??
           theType.GetProperty(keyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ??
	   theType.GetProperties().FirstOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute)));

OpenAPI bindings

It would be great to be able to enable OpenAPI configuration for the APIs using the InstantAPI configuration

Design to come

Allow custom Dtos for endpoints

Suppose I have this class

`
public class Food : Record
{

    public string Name { get; set; }
    public decimal Portion { get; set; }
    public decimal Calories { get; set; }
    public decimal Carbohydrates { get; set; }
    public decimal Lipids { get; set; }
    public decimal Proteins { get; set; }
    public decimal Fiber { get; set; }
    
    public Guid SourceId { get; set; }
    public Source Source { get; set; }

    public Classification Classification { get; set; }
    public NewClassification NewClassification { get; set; }

    public ICollection<FoodRecipes> FoodRecipes { get; set; }

}
`

When using InstantAPIs, the following swagger body model is created

{ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "registration": "2023-09-04T02:26:10.760Z", "update": "2023-09-04T02:26:10.760Z", "deletion": "2023-09-04T02:26:10.760Z", "name": "string", "portion": 0, "calories": 0, "carbohydrates": 0, "lipids": 0, "proteins": 0, "fiber": 0, "sourceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "source": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "registration": "2023-09-04T02:26:10.760Z", "update": "2023-09-04T02:26:10.760Z", "deletion": "2023-09-04T02:26:10.760Z", "name": "string" }, "classification": 0, "newClassification": 0, "foodRecipes": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "registration": "2023-09-04T02:26:10.760Z", "update": "2023-09-04T02:26:10.760Z", "deletion": "2023-09-04T02:26:10.760Z", "recipeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "recipe": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "registration": "2023-09-04T02:26:10.760Z", "update": "2023-09-04T02:26:10.760Z", "deletion": "2023-09-04T02:26:10.760Z", "name": "string", "foodRecipes": [ "string" ] }, "foodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "food": "string" } ] }

I wish there would be a way to provide custom DTOs and maybe using AutoMapper to transforming to entity class, like

app.MapInstantAPIs<EuNutroContext>(config => { config.ExcludeTable(db => db.AnyTable); config.UseCustomDto(db => db.Food, typeof(AddFoodDto), ApiMethodsToGenerate.Insert); // like this });

.WithTags("Nome of the API group");

This version can separate the apis by group? As when we use the .WithTags() parameter in out minimal apis, then Swagger group the routes above a title inside the .WithTags().

e.g:
app.MapInstantAPIs(config =>
{
config.IncludeTable(db => db.TABLE1, ApiMethodsToGenerate.All, "TABLE1").WithTags("GROUP TABLE 1");
});

app.MapInstantAPIs(config =>
{
config.IncludeTable(db => db.TABLE2, ApiMethodsToGenerate.All, "TABLE2").WithTags("GROUP TABLE 2");
});

Swagger will show something like this:

GROUP TABLE 1
[GET] /TABLE1
[POST] /TABLE1
[GET] /TABLE1/{id}
[PUT] /TABLE1/{id}
[DELETE] /TABLE1/{id}

GROUP TABLE 2
[GET] /TABLE2
[POST] /TABLE2
[GET] /TABLE2/{id}
[PUT] /TABLE2/{id}
[DELETE] /TABLE2/{id}

MapInstantAPIs fails when Dbset properties with [Keyless] attribute

I was trying to generate Minimal APIs for the Northwind database. I generated the entities with dotnet ef scaffold command with data annotation support. After scaffolding some entities generated with Keyless attribute.

[Keyless]
public partial class CategorySalesFor1997
{
    [StringLength(15)]
    public string CategoryName { get; set; } = null!;
    [Column(TypeName = "money")]
    public decimal? CategorySales { get; set; }
}

When I run the application - MapInstantAPIs failed with KeyNotFoundException.

image

I think we should skip the tables with the Keyless attribute.

Enhance ConfigBuilder

  • Strongly-typed references to the tables to Include / Exclude ( see #2 )
  • Inject validation or other logic OnBegin and OnAfter
  • Map to a ViewModel?
  • Allow definition of alternate URLs

Authorization

We should enable Authorization with optional entries in the InstantAPI configuration

_from @bravecobra's post on #49
[ ] allow authorization for the generated APIs and be able to specify authenticated users, required policies, etc...

  • allow Swagger authentication to be configured

NuGet version for prerelease

It's great to have it already available on NuGet :-)

I think the version number should have a text included, such as preview, beta, alpha, such as *0.1.0-preview.1.4242.
With a text in the version number, the package is only shown when selecting include prerelease, and not leads to production quality.

Model Binding

Validation and model binding for the POST and PUT operations are needed

GraphQL support

We should enable optional GraphQL support with an entry in the InstantAPI configuration

Add logging for the Map calls executed

Add entries to the standard ASP.NET Core log that reflect the Map methods created and their URL endpoints.

Perhaps a verbose logging experience should also declare the data access method attached to the URL

KeyNotFoundException: The given key 'xxxx.DataModel.Models.AspNetUserLogin' was not present in the dictionary.

Anyone can help find out why im getting this error?

KeyNotFoundException: The given key 'dbContext.DataModel.Models.AspNetUserLogin' was not present in the dictionary.

Snipaste_2023-08-05_23-57-18

Here is part of my Program.cs

builder.Services.AddSqlServer<dbContext>(builder.Configuration.GetConnectionString("DefaultConnectionLocal"));
 
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddApplicationInsightsTelemetry();

builder.Services.AddInstantAPIs();

var app = builder.Build();

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

app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();

// Use CORS
app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.MapInstantAPIs<dbContext>();

app.Run();

Spaces vs. Tabs

I have looked at several .csproj files and they all use Spaces for indentation.

It might be a good idea to put an .editorconfig file in that ensures that only Tabs instead of Spaces are used for indentations.

Source Generator?

I think the MapInstantAPIs method could be implemented with a source generator. This might also make it easier to create customizations with partial methods.

What do you think?

Docs site

Let's put together a docs folder along with a GitHub action to generate a docs site to live on the ghpages branch and GitHub static site that goes with this repository.

We should also add a link/shield to the README that points to the docs site

Allow injection of business logic

Introduce Action parameters on configuration that optionally allow injection of methods to be triggered before the data interaction and after the data interaction

  • OnBefore delegate property added
  • OnAfter delegate property added

Support mapping an IRepository

Define an IRepository interface and support mapping a concrete instance of that to APIs

This will help support other data providers

gRPC bindings

We should enable optional gRPC bindings with an entry in the InstantAPI configuration

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.