Skip to content

Commit 6a5554e

Browse files
author
Bart Koelman
committed
Use Minimal Hosting APIs
1 parent 6c84601 commit 6a5554e

File tree

32 files changed

+515
-647
lines changed

32 files changed

+515
-647
lines changed

README.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,15 @@ public class Article : Identifiable<int>
5656
### Middleware
5757

5858
```c#
59-
public class Startup
60-
{
61-
public IServiceProvider ConfigureServices(IServiceCollection services)
62-
{
63-
services.AddJsonApi<AppDbContext>();
64-
}
65-
66-
public void Configure(IApplicationBuilder app)
67-
{
68-
app.UseRouting();
69-
app.UseJsonApi();
70-
app.UseEndpoints(endpoints => endpoints.MapControllers());
71-
}
72-
}
59+
// Program.cs
60+
61+
builder.Services.AddJsonApi<AppDbContext>();
62+
63+
// ...
64+
65+
app.UseRouting();
66+
app.UseJsonApi();
67+
app.MapControllers();
7368
```
7469

7570
## Compatibility

docs/getting-started/step-by-step.md

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33
The most basic use case leverages Entity Framework Core.
44
The shortest path to a running API looks like:
55

6-
- Create a new web app
6+
- Create a new API project
77
- Install
88
- Define models
99
- Define the DbContext
10-
- Add Middleware and Services
10+
- Add services and middleware
1111
- Seed the database
12-
- Start the app
12+
- Start the API
1313

1414
This page will walk you through the **simplest** use case. More detailed examples can be found in the detailed usage subsections.
1515

16-
### Create A New Web App
16+
### Create a new API project
1717

1818
```
19-
mkdir MyApp
20-
cd MyApp
19+
mkdir MyApi
20+
cd MyApi
2121
dotnet new webapi
2222
```
2323

@@ -31,7 +31,7 @@ dotnet add package JsonApiDotNetCore
3131
Install-Package JsonApiDotNetCore
3232
```
3333

34-
### Define Models
34+
### Define models
3535

3636
Define your domain models such that they implement `IIdentifiable<TId>`.
3737
The easiest way to do this is to inherit from `Identifiable<TId>`.
@@ -47,7 +47,7 @@ public class Person : Identifiable<int>
4747
}
4848
```
4949

50-
### Define DbContext
50+
### Define the DbContext
5151

5252
Nothing special here, just an ordinary `DbContext`.
5353

@@ -63,46 +63,56 @@ public class AppDbContext : DbContext
6363
}
6464
```
6565

66-
### Middleware and Services
66+
### Add services and middleware
6767

68-
Finally, add the services by adding the following to your Startup.ConfigureServices:
68+
Finally, register the services and middleware by adding them to your Program.cs:
6969

7070
```c#
71-
// This method gets called by the runtime. Use this method to add services to the container.
72-
public void ConfigureServices(IServiceCollection services)
71+
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
72+
73+
// Add services to the container.
74+
75+
// Add the Entity Framework Core DbContext like you normally would.
76+
builder.Services.AddDbContext<AppDbContext>(options =>
7377
{
74-
// Add the Entity Framework Core DbContext like you normally would
75-
services.AddDbContext<AppDbContext>(options =>
76-
{
77-
// Use whatever provider you want, this is just an example
78-
options.UseNpgsql(GetDbConnectionString());
79-
});
78+
string connectionString = GetConnectionString();
8079

81-
// Add JsonApiDotNetCore
82-
services.AddJsonApi<AppDbContext>();
83-
}
84-
```
80+
// Use whatever provider you want, this is just an example.
81+
options.UseNpgsql(connectionString);
82+
});
8583

86-
Add the middleware to the Startup.Configure method.
84+
// Add JsonApiDotNetCore services.
85+
builder.Services.AddJsonApi<AppDbContext>();
8786

88-
```c#
89-
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
90-
public void Configure(IApplicationBuilder app)
91-
{
92-
app.UseRouting();
93-
app.UseJsonApi();
94-
app.UseEndpoints(endpoints => endpoints.MapControllers());
95-
}
87+
WebApplication app = builder.Build();
88+
89+
// Configure the HTTP request pipeline.
90+
91+
app.UseRouting();
92+
93+
// Add JsonApiDotNetCore middleware.
94+
app.UseJsonApi();
95+
96+
app.MapControllers();
97+
98+
app.Run();
9699
```
97100

98-
### Seeding the Database
101+
### Seed the database
99102

100-
One way to seed the database is in your Configure method:
103+
One way to seed the database is from your Program.cs:
101104

102105
```c#
103-
public void Configure(IApplicationBuilder app, AppDbContext dbContext)
106+
await CreateDatabaseAsync(app.Services);
107+
108+
app.Run();
109+
110+
static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
104111
{
105-
dbContext.Database.EnsureCreated();
112+
await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();
113+
114+
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
115+
await dbContext.Database.EnsureCreatedAsync();
106116

107117
if (!dbContext.People.Any())
108118
{
@@ -111,16 +121,12 @@ public void Configure(IApplicationBuilder app, AppDbContext dbContext)
111121
Name = "John Doe"
112122
});
113123

114-
dbContext.SaveChanges();
124+
await dbContext.SaveChangesAsync();
115125
}
116-
117-
app.UseRouting();
118-
app.UseJsonApi();
119-
app.UseEndpoints(endpoints => endpoints.MapControllers());
120126
}
121127
```
122128

123-
### Start the App
129+
### Start the API
124130

125131
```
126132
dotnet run

docs/usage/errors.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,6 @@ public class CustomExceptionHandler : ExceptionHandler
8888
}
8989
}
9090

91-
public class Startup
92-
{
93-
public void ConfigureServices(IServiceCollection services)
94-
{
95-
services.AddScoped<IExceptionHandler, CustomExceptionHandler>();
96-
}
97-
}
91+
// Program.cs
92+
builder.Services.AddScoped<IExceptionHandler, CustomExceptionHandler>();
9893
```

docs/usage/extensibility/layer-overview.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,15 @@ on your needs, you may want to replace other parts by deriving from the built-in
2525

2626
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), then resource services, repositories and resource definitions will be automatically registered for you.
2727

28-
Replacing built-in services is done on a per-resource basis and can be done through dependency injection in your Startup.cs file.
28+
Replacing built-in services is done on a per-resource basis and can be done at startup.
2929
For convenience, extension methods are provided to register layers on all their implemented interfaces.
3030

3131
```c#
32-
// Startup.cs
33-
public void ConfigureServices(IServiceCollection services)
34-
{
35-
services.AddResourceService<ProductService>();
36-
services.AddResourceRepository<ProductRepository>();
37-
services.AddResourceDefinition<ProductDefinition>();
38-
39-
services.AddScoped<IResourceFactory, CustomResourceFactory>();
40-
services.AddScoped<IJsonApiSerializerFactory, CustomResponseSerializerFactory>();
41-
}
32+
// Program.cs
33+
builder.Services.AddResourceService<ProductService>();
34+
builder.Services.AddResourceRepository<ProductRepository>();
35+
builder.Services.AddResourceDefinition<ProductDefinition>();
36+
37+
builder.Services.AddScoped<IResourceFactory, CustomResourceFactory>();
38+
builder.Services.AddScoped<IResponseModelAdapter, CustomResponseModelAdapter>();
4239
```

docs/usage/extensibility/middleware.md

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,54 @@ It is possible to replace the built-in middleware components by configuring the
1010
The following example replaces the internal exception filter with a custom implementation.
1111

1212
```c#
13-
/// In Startup.ConfigureServices
14-
services.AddService<IAsyncJsonApiExceptionFilter, CustomAsyncExceptionFilter>();
13+
// Program.cs
14+
builder.Services.AddService<IAsyncJsonApiExceptionFilter, CustomAsyncExceptionFilter>();
1515
```
1616

1717
## Configuring `MvcOptions`
1818

19-
The following example replaces all internal filters with a custom filter.
19+
The following example replaces the built-in query string action filter with a custom filter.
2020

2121
```c#
22-
public class Startup
22+
// Program.cs
23+
24+
// Add services to the container.
25+
26+
builder.Services.AddScoped<CustomAsyncQueryStringActionFilter>();
27+
28+
IMvcCoreBuilder mvcCoreBuilder = builder.Services.AddMvcCore();
29+
builder.Services.AddJsonApi<AppDbContext>(mvcBuilder: mvcCoreBuilder);
30+
31+
Action<MvcOptions>? postConfigureMvcOptions = null;
32+
33+
// Ensure this is placed after the AddJsonApi() call.
34+
mvcCoreBuilder.AddMvcOptions(mvcOptions =>
35+
{
36+
postConfigureMvcOptions?.Invoke(mvcOptions);
37+
});
38+
39+
// Configure the HTTP request pipeline.
40+
41+
// Ensure this is placed before the MapControllers() call.
42+
postConfigureMvcOptions = mvcOptions =>
2343
{
24-
private Action<MvcOptions> _postConfigureMvcOptions;
25-
26-
public void ConfigureServices(IServiceCollection services)
27-
{
28-
services.AddSingleton<CustomAsyncQueryStringActionFilter>();
29-
30-
IMvcCoreBuilder builder = services.AddMvcCore();
31-
services.AddJsonApi<AppDbContext>(mvcBuilder: builder);
32-
33-
// Ensure this call is placed after the AddJsonApi call.
34-
builder.AddMvcOptions(mvcOptions =>
35-
{
36-
_postConfigureMvcOptions.Invoke(mvcOptions);
37-
});
38-
}
39-
40-
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
41-
{
42-
// Ensure this call is placed before the UseEndpoints call.
43-
_postConfigureMvcOptions = mvcOptions =>
44-
{
45-
mvcOptions.Filters.Clear();
46-
mvcOptions.Filters.Insert(0,
47-
app.ApplicationServices.GetService<CustomAsyncQueryStringActionFilter>());
48-
};
49-
50-
app.UseRouting();
51-
app.UseJsonApi();
52-
app.UseEndpoints(endpoints => endpoints.MapControllers());
53-
}
54-
}
44+
IFilterMetadata existingFilter = mvcOptions.Filters.Single(filter =>
45+
filter is ServiceFilterAttribute serviceFilter &&
46+
serviceFilter.ServiceType == typeof(IAsyncQueryStringActionFilter));
47+
48+
mvcOptions.Filters.Remove(existingFilter);
49+
50+
using IServiceScope scope = app.Services.CreateScope();
51+
52+
var newFilter =
53+
scope.ServiceProvider.GetRequiredService<CustomAsyncQueryStringActionFilter>();
54+
55+
mvcOptions.Filters.Insert(0, newFilter);
56+
};
57+
58+
app.UseRouting();
59+
app.UseJsonApi();
60+
app.MapControllers();
61+
62+
app.Run();
5563
```

docs/usage/extensibility/query-strings.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ See [here](~/usage/extensibility/resource-definitions.md#custom-query-string-par
2424
In order to add parsing of custom query string parameters, you can implement the `IQueryStringParameterReader` interface and register your reader.
2525

2626
```c#
27-
public class YourQueryStringParameterReader : IQueryStringParameterReader
27+
public class CustomQueryStringParameterReader : IQueryStringParameterReader
2828
{
2929
// ...
3030
}
3131
```
3232

3333
```c#
34-
services.AddScoped<YourQueryStringParameterReader>();
35-
services.AddScoped<IQueryStringParameterReader>(sp => sp.GetService<YourQueryStringParameterReader>());
34+
// Program.cs
35+
builder.Services.AddScoped<CustomQueryStringParameterReader>();
36+
builder.Services.AddScoped<IQueryStringParameterReader>(serviceProvider =>
37+
serviceProvider.GetRequiredService<CustomQueryStringParameterReader>());
3638
```
3739

3840
Now you can inject your custom reader in resource services, repositories, resource definitions etc.

docs/usage/extensibility/repositories.md

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@
33
If you want to use a data access technology other than Entity Framework Core, you can create an implementation of `IResourceRepository<TResource, TId>`.
44
If you only need minor changes you can override the methods defined in `EntityFrameworkCoreRepository<TResource, TId>`.
55

6-
The repository should then be registered in Startup.cs.
7-
86
```c#
9-
public void ConfigureServices(IServiceCollection services)
10-
{
11-
services.AddScoped<IResourceRepository<Article, int>, ArticleRepository>();
12-
services.AddScoped<IResourceReadRepository<Article, int>, ArticleRepository>();
13-
services.AddScoped<IResourceWriteRepository<Article, int>, ArticleRepository>();
14-
}
7+
// Program.cs
8+
builder.Services.AddScoped<IResourceRepository<Article, int>, ArticleRepository>();
9+
builder.Services.AddScoped<IResourceReadRepository<Article, int>, ArticleRepository>();
10+
builder.Services.AddScoped<IResourceWriteRepository<Article, int>, ArticleRepository>();
1511
```
1612

1713
In v4.0 we introduced an extension method that you can use to register a resource repository on all of its JsonApiDotNetCore interfaces.
@@ -20,13 +16,8 @@ This is helpful when you implement (a subset of) the resource interfaces and wan
2016
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
2117

2218
```c#
23-
public class Startup
24-
{
25-
public void ConfigureServices(IServiceCollection services)
26-
{
27-
services.AddResourceRepository<ArticleRepository>();
28-
}
29-
}
19+
// Program.cs
20+
builder.Services.AddResourceRepository<ArticleRepository>();
3021
```
3122

3223
A sample implementation that performs authorization might look like this.
@@ -64,7 +55,8 @@ If you need to use multiple Entity Framework Core DbContexts, first create a rep
6455
This example shows a single `DbContextARepository` for all entities that are members of `DbContextA`.
6556

6657
```c#
67-
public class DbContextARepository<TResource, TId> : EntityFrameworkCoreRepository<TResource, TId>
58+
public class DbContextARepository<TResource, TId>
59+
: EntityFrameworkCoreRepository<TResource, TId>
6860
where TResource : class, IIdentifiable<TId>
6961
{
7062
public DbContextARepository(ITargetedFields targetedFields,
@@ -83,13 +75,12 @@ public class DbContextARepository<TResource, TId> : EntityFrameworkCoreRepositor
8375
Then register the added types and use the non-generic overload of `AddJsonApi` to add their resources to the graph.
8476

8577
```c#
86-
// In Startup.ConfigureServices
87-
88-
services.AddDbContext<DbContextA>(options => options.UseSqlite("Data Source=A.db"));
89-
services.AddDbContext<DbContextB>(options => options.UseSqlite("Data Source=B.db"));
78+
// Program.cs
79+
builder.Services.AddDbContext<DbContextA>(options => options.UseSqlite("Data Source=A.db"));
80+
builder.Services.AddDbContext<DbContextB>(options => options.UseSqlite("Data Source=B.db"));
9081

91-
services.AddScoped<IResourceRepository<ResourceA>, DbContextARepository<ResourceA>>();
92-
services.AddScoped<IResourceRepository<ResourceB>, DbContextBRepository<ResourceB>>();
82+
builder.Services.AddJsonApi(dbContextTypes: new[] { typeof(DbContextA), typeof(DbContextB) });
9383

94-
services.AddJsonApi(dbContextTypes: new[] { typeof(DbContextA), typeof(DbContextB) });
84+
builder.Services.AddScoped<IResourceRepository<ResourceA>, DbContextARepository<ResourceA>>();
85+
builder.Services.AddScoped<IResourceRepository<ResourceB>, DbContextBRepository<ResourceB>>();
9586
```

0 commit comments

Comments
 (0)