WE'VE MOVED TO NET5! :) Version 0.4.0 is a
net5.0
library!
Sometimes in your ASP.NET Core projects you need to create some initialization steps like:
- Seeding the database
- Waiting for some external resource to be available
- Downloading needed stuff
Most times this is done in the Program.Main
method, before starting the host. However, this means that you cannot process any request until your init work is performed.
Core-Init wants to help with this.
Just submit an issue or (even better!) a pull request. Please if you want to submit a PR, submit from the dev branch, not from master!
Just clone the repo (dev or master branch) and build the LoCrestia.AspNetCore.Initializer.sln
VS2017 solution. Solution contains two projects (core-init itself and a web project which is just a demo)
You can also use dotnet
cli if you prefer.
Just add the LoCrestia.AspNetCore.Initializer
package to your project (current version 0.3.0)
Install-Package LoCrestia.AspNetCore.Initializer
#0.3.0 CHANGELOG
- breaking change: Removed
IApplicationBuilder
extension methodRunInitializationsAsync()
used to launch the initialization tasks. - breaking change: The initialization tasks are launched using extension method
RunInitTasks
fromIWebHost
. - Support for tasks defined either in
Startup
class or in theMain
method. - Support for tasks that use scoped objects.
You add Core-Init to your project in the Main
method before creating the host:
CreateHostBuilder(args)
.UseInitializer()
.Build().Run();
The method UseInitializer
adds Core-Init to the pipeline of the application and creates all needed infrastructure.
You can add initialization steps in the ConfigureServices
method of the Startup
class, using the extension method AddInitTasks
:
services.AddInitTasks(options =>
{
options.AddTask(async () => Task.Delay(10000));
});
You can also add initialization steps in the Main
method, using the extension method RunInitTasks
. This is a extension method over IHost
:
public static void Main(string[] args)
{
CreateHostBuilder(args)
.UseInitializer(options =>
{
options.ErrorText = "Doing some stuff";
options.ResultPath = "/initresult";
})
.Build()
.RunInitTasks(opt =>
{
opt.AddTask<MyContext>("EF Seed", async (ctx) =>
{
await Task.Delay(20000);
ctx.Database.Migrate();
for (var i = 0; i < 1000; i++)
{
ctx.MyEntities.Add(new MyEntity() { Name = $"Test {i}" });
}
await ctx.SaveChangesAsync();
});
})
.Run();
}
Currently two supported types of initialization steps are supported: method-based and class-based.
The first ones are added using the AddTask
and passing a Func<Task>
which contains the code to run:
options.AddTask(async () => Task.Delay(10000));
If you use the RunInitTasks
method you can use:
AddTask<T>
: Which adds a Task that is a function that takes a parameter of typeT
. This parameter is resolved via DI. This method takes aFunc<T, Task>
as a parameter that contains the init task code.AddTask
: Which adds a Task that is a function that do not take parameters. This method takes aFunc<Task>
as a parameter that contains the init task code.
There is no (currently) support for class-based tasks using RunInitTasks
.
But you can create your own complex initialization steps by simply having one class with a method called Run
:
public class MyCustomTask
{
private readonly ILogger _logger;
public MyCustomTask(ILogger<MyCustomTask> logger)
{
_logger = logger;
}
public async Task Run()
{
await Task.Delay(10000);
}
}
Note that the class do not need to inherit from any other class, nor implement any interface. Only the Run
method is required. Also note that you can inject any other required service in the constructor.
The return value of mtehod Run is ignored but the method Run
can return a Task
to indicate that is an asynchronous method. If the method Run
returns a Task, it will be called using await
. Only Task
is supported (Task<T>
and any other awaitable object is not supported).
Once you have the class created you simply call the generic version of the AddTask
method with no parameters:
options.AddTask<MyCustomTask>();
For this to work you must register the task class (MyCustomTask
) to the DI system (in the ConfigureServices
method)
services.AddTransient<MyCustomTask>();
The extension method RunInitTasks
of the IWebHost
runs ALL initializations tasks.
CreateHostBuilder(args)
.UseInitializer()
.Build()
.RunInitTasks()
If you added any initialization task using the RunInitTasks
that accepts one parameter, you don't need to call it again. But if you added only tasks using Startup
class need to call this version with no parameters.
RunInitTasks must be called once with parameters (for also adding tasks) or without parameters. Don't call it multiple times!
If an exception happens during the execution of a initialization task, core-init allows two options:
- Continue with the next initialization task
- Stop the initialization process, but don't rethrow the exception. All pending tasks are not run.
If you use ContinueOnError()
when adding a task, core-init will run next task if an exception happens:
options.AddTask(async () => throw new Exception("foo")).ContinueOnError();
If you don't use ContinueOnError()
and the initialization task has an exception, the initialization process stops.
When registering core-ini (in Main
method) you can enter a URL that will return a JSON with the status of the initialization process:
- If Started or not
- Task running
Once finished it will show the status of all tasks (run correctly, run with error or skipped). If there is an exception the exception details are also serialized.
To set the endpoint use the property ResultPath
of the options:
.UseInitializer(options =>
{
options.ResultPath = "/initresult";
})
If you set ResultPath
to null
the endpoint is disabled.
While your initializations tasks are running your application can start receiving requests: all requests will get a 503 (Service Unavailable) until all initialization tasks are completed. Once all are completed requests will be processed in a normal way.
This allows to start your server application fast and start to serving requests (ok, they will receive a 503 but in a future this behavior will be more customizable).
By default all requests sent while initialization tasks are running receive a 503 with the content-type "text/plain" and the text "Service is Starting...". You can change the Status Code and the text (but not the content-type yet) in the UseInitializer
, using the properties ErrorText
and StatusCode
:
.UseInitializer(options =>
{
options.ErrorText = "Doing some stuff";
options.StatusCode = 500;
})
- More customization in responses while running initialization tasks
- Cancelling initialization tasks
- ...