Dynamically generates a generic CRUD API implementation backed with Entity Framework Core and Minimal API. This can be used to create a quick backend for prototyping apps that use CRUD operations.
- Realtime support using SignalR learn more...
- Advanced querying with filtering, ordering, and pagination learn more...
- Batch endpoints for adding, updating, and deleting multiple entities learn more...
- Quick API prototyping
- Small projects that only require CRUD functionality
- Frontend Testing (if a backend API is needed)
To install the Russkyc.MinimalApi.Framework
package, you can use the NuGet Package Manager or the .NET CLI.
Run the following command in your terminal:
dotnet add package Russkyc.MinimalApi.Framework
Follow these steps to set up the Russkyc.MinimalApi.Framework
in your project.
-
Create a new ASP.NET Core Web API project if you don't already have one.
-
Add the required services and API endpoint mappings in the
Program.cs
file:using System.Reflection; using Microsoft.EntityFrameworkCore; using Russkyc.MinimalApi.Framework; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var assembly = Assembly.GetExecutingAssembly(); // Add Entity Context Services builder.Services.AddDbContextService(assembly, options => options.UseInMemoryDatabase("sample")); // Uncomment to enable realtime events // by default the endpoint used is "/crud-events" // this can be changed by providing a string parameter, eg; `MapRealtimeHub("/api-events")` builder.Services.AddRealtimeService(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // Map Entity CRUD Endpoints app.MapGroup("api") .MapAllEntityEndpoints<int>(assembly); // Map Realtime Hub app.MapRealtimeHub(); app.Run();
All entity classes should inherit from the DbEntity abstract class.
Where TKeyType
is the Id type of the entity.
SampleEntity Class
public class SampleEntity : DbEntity<int>
{
public string Property { get; set; }
public virtual SampleEmbeddedEntity EmbeddedEntity { get; set; }
}
SampleEmbeddedEntity Class
public class SampleEmbeddedEntity : DbEntity<int>
{
public string Property2 { get; set; }
}
You now have a fully working EntityFrameworkCore backed MinimalApi CRUD project.
To support database migrations, we can create a custom dbcontext class that inherits from BaseDbContext
using Microsoft.EntityFrameworkCore;
using Russkyc.MinimalApi.Framework;
namespace SampleProject;
public class CustomDbContext : BaseDbContext
{
public CustomDbContext(DbContextOptions options) : base(options)
{
}
// Entity collections are required to be defined
// using the naming convention `<ClassName>Collection`
public DbSet<SampleEmbeddedEntity> SampleEmbeddedEntityCollection { get; set; }
public DbSet<SampleEntity> SampleEntityCollection { get; set; }
}
These are different database action types that can be defined in the AddAllEntityServices
or AddEntityServices
extensions using the databaseAction
parameter:
- None - Default Action
- DatabaseAction.EnsureCreated - Run
Database.EnsureCreated()
- DatabaseAction.DeleteAndCreate - Run
Database.EnsureDeleted()
beforeDatabase.EnsureCreated()
As an example:
builder.Services.AddDbContextService(assembly,
options => options.UseSqlite("Data Source=test.sqlite"),
databaseAction: DatabaseAction.DeleteAndCreate);
This ensures that a fresh database is used in every run. If you only need
to set the database once, DatabaseAction.EnsureCreated
is the recommended option
You can modify the endpoint options using the routeOptionsAction
parameter. For example, to require authorization for
all endpoints:
app.MapGroup("api")
.MapAllEntityEndpoints<int>(assembly, routeOptions => routeOptions.RequireAuthorization());
Apart from the standard CRUD api functionality, there is also some support for advanced querying.
If you do a get request to the endpoint /api/sampleentity
you will
receive a response that looks like this:
[
{
"id": 1,
"property": "Entity 1",
"embeddedEntity": null
},
{
"id": 2,
"property": "Entity 2",
"embeddedEntity": null
},
{
"id": 3,
"property": "Entity 3",
"embeddedEntity": null
},
{
"id": 4,
"property": "Entity 4",
"embeddedEntity": null
}
]
This is because navigation properties for referenced entities are not
automatically included (for performance purposes). you can use the include
query parameter to include the referenced entity when needed.
GET /api/sampleentity?include=embeddedentity
Then you will have this result:
[
{
"id": 1,
"property": "Entity 1",
"embeddedEntity": {
"id": 1,
"property2": "Embedded Entity 1"
}
},
{
"id": 2,
"property": "Entity 2",
"embeddedEntity": {
"id": 2,
"property2": "Embedded Entity 2"
}
},
{
"id": 3,
"property": "Entity 3",
"embeddedEntity": {
"id": 3,
"property2": "Embedded Entity 3"
}
},
{
"id": 4,
"property": "Entity 4",
"embeddedEntity": {
"id": 4,
"property2": "Embedded Entity 4"
}
}
]
Entities can now be filtered with the filter
queryParam and supports standard expressions. Parameters should be
prefixed with @
in order to be valid, eg; a parameter named Content
should be used as @Content
. Here are a few
examples:
GET /api/sampleentity?filter=@Content.StartsWith("hello")
GET /api/sampleentity?filter=@Content.StartsWith("hi") && !@Content.Contains("user")
GET /api/sampleentity?filter=@Count == 1 || @Count > 8
GET /api/sampleentity?filter=@ContactPerson != null
These are visualized for readability, in actual use, the filter value should be URL Encoded.
Entities can now be ordered using the orderBy
and orderByDescending
query parameters. Multiple properties can be specified for ordering, separated by commas. The first property will be ordered using OrderBy
or OrderByDescending
, and subsequent properties will be ordered using ThenBy
or ThenByDescending
.
GET /api/sampleentity?orderBy=property,embeddedEntity.property2&orderByDescending=true
the orderBy
query param will define what properties are taken into consideration in ordering.
the orderByDescending
query param is a bool property that changes the behavior to descending when set to true.
By default, pagination is disabled and the query collection response returns something like this
[
{
"id": 1,
"property": "Entity 1",
"embeddedEntity": {
"id": 1,
"property2": "Embedded Entity 1"
}
},
{
"id": 2,
"property": "Entity 2",
"embeddedEntity": {
"id": 2,
"property2": "Embedded Entity 2"
}
}
]
To enable pagination, set the paginate
query param to true
and set the page
, pageSize
query params as needed. as an example:
GET /api/sampleentity?paginate=true&page=1&pageSize=1
This will now return a PaginatedCollection
object with this JSON schema:
{
"data": [
{
"property": "Entity 1",
"embeddedEntity": null,
"id": "84e93f60-b2bc-4303-af0a-c51c205addb9"
}
],
"page": 1,
"pageSize": 1,
"totalRecords": 2,
"totalPages": 2
}
Batch endpoints are supported for adding, updating, and deleting multiple entities at once.
POST /api/sampleentity/batch
Content-Type: application/json
[
{
"id": 1,
"property": "Entity 1",
"embeddedEntity": null
},
{
"id": 2,
"property": "Entity 2",
"embeddedEntity": null
}
]
PUT /api/sampleentity/batch
Content-Type: application/json
[
{
"id": 1,
"property": "Updated Entity 1",
"embeddedEntity": null
},
{
"id": 2,
"property": "Updated Entity 2",
"embeddedEntity": null
}
]
PATCH /api/sampleentity/batch?filter=@property.Contains("Old")
Content-Type: application/json
{
"property": "Updated Value"
}
DELETE /api/sampleentity/batch?filter=@Count > 8
To enable realtime events support, add the AddRealtimeService
method to your service collection and map the realtime hub using MapRealtimeHub
in your Program.cs
file:
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Russkyc.MinimalApi.Framework;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var assembly = Assembly.GetExecutingAssembly();
// Add Entity Context Services
builder.Services.AddDbContextService(assembly, options => options.UseInMemoryDatabase("sample"));
// Add Realtime Service
builder.Services.AddRealtimeService();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// Map Entity CRUD Endpoints
app.MapGroup("api")
.MapAllEntityEndpoints<int>(assembly);
// Map Realtime Hub
app.MapRealtimeHub();
app.Run();
The default websocket endpoint is /crud-events
but can be changed by
adding a string parameter of the desired endpint in the MapRealtimeHub
method, eg; .MapRealtimeHub("/api-events")
.
Each event returned is an EntityEvent<T>
type, where T is the type of data
returned by the resource event that triggered the websocket message. Eg; when
creating a SampleEntity
using post
the EntityEvent
being sent in realtime is of
type EntityEvent<SampleEntity>
.
public class EntityEvent<T>
{
public string Resource { get; set; }
public string Type { get; set; }
public T? Data { get; set; }
}
Schema Information
- The
Resource
property will be the name of the entity in lowercase, eg; "sampleentity". - The
Type
property will be the type of event, eg;created
,updated
,deleted
,batch-created
,batch-updated
,batch-deleted
- The
Data
property contains the data being returned by that resource method.
- When using generic implementations like this on the server side, business logic is now moved into the client and becomes a client concern.
- If your API needs to do complex business logic over the CRUD functionality, please consider implementing custom endpoints instead of using generic endpoints such as this.
- There is currently no implementation for validation and DTO mapping, this can be added later as the project updates.