2020-05-03 17:09:45 +02:00
using FluentValidation ;
2020-11-04 01:25:38 +01:00
using Microsoft.AspNetCore.Authentication.Cookies ;
2020-05-09 20:05:08 +02:00
using Microsoft.AspNetCore.Hosting ;
2020-05-03 17:09:45 +02:00
using Microsoft.AspNetCore.Identity ;
2020-10-27 15:47:23 +01:00
using Microsoft.AspNetCore.Mvc ;
2020-05-11 15:34:47 +02:00
using Microsoft.Extensions.Configuration ;
2020-04-15 01:42:06 +02:00
using Microsoft.Extensions.DependencyInjection ;
2020-10-27 15:47:23 +01:00
using Microsoft.Extensions.DependencyInjection.Extensions ;
2020-05-11 15:34:47 +02:00
using Microsoft.Extensions.Options ;
using Raven.Client.Documents ;
2020-05-15 14:23:47 +02:00
using Raven.Client.Documents.Indexes ;
2020-04-05 01:19:42 +02:00
using System ;
2020-11-01 23:23:36 +01:00
using System.IO ;
2020-05-29 14:33:20 +02:00
using System.Linq ;
2020-05-15 14:23:47 +02:00
using System.Reflection ;
2020-04-05 01:19:42 +02:00
using zero.Core ;
2020-05-09 20:05:08 +02:00
using zero.Core.Api ;
2020-05-27 16:03:38 +02:00
using zero.Core.Assemblies ;
2020-11-19 00:14:52 +01:00
using zero.Core.Collections ;
2020-12-15 16:09:58 +01:00
using zero.Core.Cultures ;
2020-11-15 17:07:27 +01:00
using zero.Core.Database ;
2020-04-05 01:19:42 +02:00
using zero.Core.Entities ;
2020-05-11 15:34:47 +02:00
using zero.Core.Extensions ;
2020-11-05 00:27:02 +01:00
using zero.Core.Handlers ;
2020-04-15 01:42:06 +02:00
using zero.Core.Identity ;
2020-05-14 13:32:33 +02:00
using zero.Core.Options ;
2020-05-08 18:43:39 +02:00
using zero.Core.Plugins ;
2020-11-03 16:07:35 +01:00
using zero.Core.Security ;
2020-05-03 17:09:45 +02:00
using zero.Core.Validation ;
2020-10-27 15:47:23 +01:00
using zero.Web.Controllers ;
2020-05-14 13:32:33 +02:00
using zero.Web.Defaults ;
2020-08-13 12:03:24 +02:00
using zero.Web.Filters ;
2020-11-04 16:16:36 +01:00
using zero.Web.Security ;
2020-11-01 23:23:36 +01:00
using Zero.Web.DevServer ;
2020-04-05 00:39:12 +02:00
namespace zero.Web
{
2020-05-12 01:36:27 +02:00
// TODO maybe use a middleware like Hangfire does: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.AspNetCore/HangfireEndpointRouteBuilderExtensions.cs
2020-04-05 00:39:12 +02:00
public class ZeroBuilder
{
public virtual IServiceCollection Services { get ; }
2020-05-27 16:03:38 +02:00
public virtual IMvcBuilder Mvc { get ; }
IConfiguration Configuration ;
IZeroStartupOptions StartupOptions ;
2020-05-11 15:34:47 +02:00
2020-05-27 18:29:36 +02:00
public ZeroBuilder ( IServiceCollection services , IConfiguration configuration , Action < IZeroStartupOptions > setupAction )
2020-04-05 00:39:12 +02:00
{
2020-05-27 18:29:36 +02:00
Services = services ;
2020-10-27 15:47:23 +01:00
Mvc = services . AddMvc ();
2020-05-11 15:34:47 +02:00
Configuration = configuration ;
2020-04-15 01:42:06 +02:00
2020-05-27 16:03:38 +02:00
// create startup options
2020-05-27 18:29:36 +02:00
StartupOptions = new ZeroStartupOptions ( Mvc );
2020-05-27 16:03:38 +02:00
StartupOptions . AssemblyDiscoveryRules . Add ( new ZeroAssemblyDiscoveryRule ());
setupAction ?. Invoke ( StartupOptions );
2020-05-11 15:34:47 +02:00
2020-05-27 16:03:38 +02:00
2020-05-28 00:36:52 +02:00
// adds and discovers additional and built-in assemblies
new AssemblyDiscovery ( Mvc ). Execute ( StartupOptions . AssemblyDiscoveryRules );
2020-05-27 16:03:38 +02:00
// add default plugin
AddPlugin < ZeroBackofficePlugin >();
2020-05-27 18:29:36 +02:00
2020-05-27 16:03:38 +02:00
// create and bind zero options
2020-05-14 13:32:33 +02:00
Services . AddOptions < ZeroOptions >()
. Bind ( Configuration . GetSection ( "Zero" ))
. Configure ( opts =>
{
2020-05-27 16:03:38 +02:00
//opts.AssemblyDiscoveryRules.Add(new ZeroAssemblyDiscoveryRule());
2020-05-14 13:32:33 +02:00
opts . ZeroVersion = "0.0.1.0" ; // TODO
});
2020-05-13 14:06:11 +02:00
2020-05-27 16:03:38 +02:00
// add transient options to DI
Services . AddTransient < IZeroOptions >( factory => factory . GetService < IOptionsMonitor < ZeroOptions >>(). CurrentValue );
2020-05-11 15:34:47 +02:00
2020-05-27 16:03:38 +02:00
// configure MVC
2020-10-27 15:47:23 +01:00
Services . TryAddEnumerable ( ServiceDescriptor . Transient < IConfigureOptions < MvcOptions >, ZeroBuilderMvcOptions >());
2020-08-27 14:22:13 +02:00
Mvc . AddNewtonsoftJson ( x =>
{
// TODO this shall only be configurated for backoffice controllers
2020-10-27 15:47:23 +01:00
BackofficeJsonSerlializerSettings . Setup ( x . SerializerSettings );
2020-08-27 14:22:13 +02:00
});
2020-09-18 01:06:21 +02:00
2020-05-11 15:34:47 +02:00
if ( Environment . GetEnvironmentVariable ( "DOTNET_WATCH" ) == "1" )
{
2020-05-27 16:03:38 +02:00
Mvc . AddRazorRuntimeCompilation ();
2020-10-27 15:47:23 +01:00
}
2020-05-22 15:06:17 +02:00
2020-11-04 16:16:36 +01:00
// configure Raven + identity
2020-05-27 16:03:38 +02:00
ConfigureDatabase ();
ConfigureIdentity ();
// configure FluentValidation
2020-07-09 15:33:58 +02:00
ValidatorOptions . Global . PropertyNameResolver = ValidatorCamelCasePropertyResolver . ResolvePropertyName ;
2020-05-27 16:03:38 +02:00
// add default services
2020-11-03 16:07:35 +01:00
Services . AddHttpContextAccessor ();
2020-11-16 01:07:07 +01:00
Services . AddScoped < IApplicationResolver , ApplicationResolver >();
2020-12-15 16:09:58 +01:00
Services . AddScoped < ICultureResolver , CultureResolver >();
2020-11-04 20:45:53 +01:00
Services . AddScoped < IZeroContext , ZeroContext >();
2020-05-27 16:03:38 +02:00
2020-11-06 19:18:56 +01:00
Services . AddScoped < IBackofficeStore , BackofficeStore >();
2020-11-18 12:00:51 +01:00
Services . AddScoped < BackofficeFilterAttribute >();
2020-08-13 12:03:24 +02:00
Services . AddScoped < ModelStateValidationFilterAttribute >();
2020-11-19 00:14:52 +01:00
Services . AddSingleton < ICollectionInterceptorHandler , CollectionInterceptorHandler >();
2020-11-18 15:55:18 +01:00
2020-05-27 16:03:38 +02:00
Services . AddHttpContextAccessor ();
Services . AddTransient < IZeroVue , ZeroVue >();
Services . AddTransient < IPaths >( factory =>
{
IWebHostEnvironment env = factory . GetService < IWebHostEnvironment >();
return new Paths ( env . WebRootPath , true );
});
2020-11-01 23:23:36 +01:00
2020-11-05 00:46:18 +01:00
Services . AddTransient < IHandlerHolder , HandlerHolder >();
2020-11-01 23:23:36 +01:00
// add dev server
Services . AddOptions < ZeroDevOptions >()
. Bind ( Configuration . GetSection ( "Zero:DevServer" ))
. Configure < IWebHostEnvironment >(( opts , env ) =>
{
opts . WorkingDirectory = Path . Combine ( env . ContentRootPath , ".." , "Zero.Web.UI" , "App" );
});
Services . AddHostedService < ZeroDevService >();
2020-05-11 15:34:47 +02:00
}
/// <summary>
/// Configures Raven database instance
/// </summary>
void ConfigureDatabase ()
{
// add raven
2020-11-15 17:07:27 +01:00
Services . AddSingleton < IZeroStore , ZeroStore >( context =>
2020-05-11 15:34:47 +02:00
{
IZeroOptions options = context . GetService < IZeroOptions >();
2020-11-15 17:07:27 +01:00
2020-11-15 20:11:27 +01:00
IDocumentStore store = new ZeroStore ( options )
2020-05-11 15:34:47 +02:00
{
2020-11-15 17:07:27 +01:00
Urls = new string [ 1 ] { options . Raven . Url }
2020-05-11 15:34:47 +02:00
};
2020-05-21 11:18:05 +02:00
IDocumentStore raven = store . Setup ( options ). Initialize ();
2020-05-15 14:23:47 +02:00
2020-05-21 11:18:05 +02:00
// create all indexes
2020-05-29 14:33:20 +02:00
var assemblies = AssemblyDiscovery . Current . GetAssemblies (). ToList ();
// TODO maybe we shouldn't use all auto-registered assemblies but specify them directly via options?
2020-12-02 15:45:44 +01:00
if ( options . SetupCompleted )
2020-05-29 14:33:20 +02:00
{
2020-12-02 15:45:44 +01:00
foreach ( Assembly assembly in assemblies )
{
IndexCreation . CreateIndexes ( assembly , store , database : options . Raven . Database );
}
2020-05-29 14:33:20 +02:00
}
2020-05-15 14:23:47 +02:00
2020-11-15 17:07:27 +01:00
return ( ZeroStore ) raven ;
2020-05-11 15:34:47 +02:00
});
2020-12-09 13:42:23 +01:00
Services . AddScoped < IZeroDocumentSession >( services =>
{
var session = services . GetRequiredService < IZeroStore >()!. OpenAsyncSession ();
session . Advanced . WaitForIndexesAfterSaveChanges ();
return session as ZeroDocumentSession ;
});
2020-05-11 15:34:47 +02:00
}
/// <summary>
2020-11-04 16:16:36 +01:00
/// Configures identity
2020-05-11 15:34:47 +02:00
/// </summary>
void ConfigureIdentity ()
{
2020-11-04 16:16:36 +01:00
Services . TryAddEnumerable ( ServiceDescriptor . Singleton < IPostConfigureOptions < CookieAuthenticationOptions >, PostConfigureCookieAuthenticationOptions >());
2020-11-05 00:27:02 +01:00
Services . AddZeroIdentity < BackofficeUser , BackofficeUserRole >();
Services . Replace < IUserClaimsPrincipalFactory < BackofficeUser >, ZeroBackofficeClaimsPrincipalFactory < BackofficeUser , BackofficeUserRole >>();
2020-11-17 12:24:56 +01:00
Services . Replace < IUserStore < BackofficeUser >, RavenCoreUserStore < BackofficeUser , BackofficeUserRole >>( ServiceLifetime . Scoped );
Services . Replace < IRoleStore < BackofficeUserRole >, RavenCoreRoleStore < BackofficeUserRole >>( ServiceLifetime . Scoped );
2020-11-04 16:16:36 +01:00
2020-11-04 16:22:40 +01:00
Services . AddAuthentication ( Constants . Auth . BackofficeScheme )
2020-11-05 00:27:02 +01:00
. AddZeroBackofficeCookie < BackofficeUser , BackofficeUserRole >();
2020-11-04 16:16:36 +01:00
Services . AddAuthorization ();
2020-04-05 01:19:42 +02:00
}
2020-05-11 15:34:47 +02:00
/// <summary>
/// Use specified options
/// </summary>
2020-04-05 01:19:42 +02:00
public ZeroBuilder WithOptions ( Action < ZeroOptions > configureOptions )
{
2020-05-14 13:32:33 +02:00
Services . Configure ( configureOptions );
2020-04-05 01:19:42 +02:00
return this ;
2020-04-05 00:39:12 +02:00
}
2020-05-18 15:55:43 +02:00
/// <summary>
/// Adds a zero plugin
/// </summary>
2020-05-27 18:29:36 +02:00
public ZeroBuilder AddPlugin < T >() where T : class , IZeroPlugin , new ()
2020-05-18 15:55:43 +02:00
{
2020-05-28 00:36:52 +02:00
AssemblyDiscovery . Current . AddAssembly ( typeof ( T ). Assembly );
2020-11-03 00:49:41 +01:00
Services . AddSingleton < IZeroPlugin , T >();
2020-05-18 15:55:43 +02:00
AddPluginServices < T >();
2020-05-27 18:29:36 +02:00
return this ;
2020-05-18 15:55:43 +02:00
}
/// <summary>
/// Adds a zero plugin
/// </summary>
2020-05-27 18:29:36 +02:00
public ZeroBuilder AddPlugin < T >( Func < IServiceProvider , T > implementationFactory ) where T : class , IZeroPlugin , new ()
2020-05-18 15:55:43 +02:00
{
2020-05-28 00:36:52 +02:00
AssemblyDiscovery . Current . AddAssembly ( typeof ( T ). Assembly );
2020-11-03 00:49:41 +01:00
Services . AddSingleton < IZeroPlugin , T >( implementationFactory );
2020-05-18 15:55:43 +02:00
AddPluginServices < T >();
2020-05-27 18:29:36 +02:00
return this ;
2020-05-18 15:55:43 +02:00
}
/// <summary>
/// Creates a temporary instance of the plugin to add additional services
/// </summary>
/// <typeparam name="T"></typeparam>
void AddPluginServices < T >() where T : class , IZeroPlugin , new ()
{
try
{
T plugin = new T ();
2020-09-10 16:12:06 +02:00
plugin . ConfigureServices ( Services , Configuration );
2020-05-18 15:55:43 +02:00
2020-10-19 15:58:32 +02:00
Services . Configure < ZeroOptions >( opts => plugin . Configure ( opts ));
2020-05-18 15:55:43 +02:00
}
catch
{
throw new Exception ( $"Plugin \" { nameof ( T )} \ " needs an additional parameterless constructor as ConfigureServices() is called before the DI container is built" );
}
}
2020-10-27 15:47:23 +01:00
class ZeroBuilderMvcOptions : IConfigureOptions < MvcOptions >
{
IZeroOptions Options { get ; set ; }
public ZeroBuilderMvcOptions ( IZeroOptions options )
{
Options = options ;
}
public void Configure ( MvcOptions options )
{
options . Conventions . Add ( new ZeroBackofficeControllerModelConvention ( Options . BackofficePath ));
}
}
2020-04-05 00:39:12 +02:00
}
}