Files
mixtape/zero.Web/ZeroBuilder.cs
T

273 lines
8.3 KiB
C#
Raw Normal View History

2020-05-03 17:09:45 +02:00
using FluentValidation;
2020-11-04 01:25:38 +01:00
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Hosting;
2020-05-03 17:09:45 +02:00
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
2020-04-15 01:42:06 +02:00
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
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;
using zero.Core.Api;
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;
using zero.Core.Extensions;
using zero.Core.Handlers;
2020-04-15 01:42:06 +02:00
using zero.Core.Identity;
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;
using zero.Web.Controllers;
using zero.Web.Defaults;
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; }
public virtual IMvcBuilder Mvc { get; }
IConfiguration Configuration;
IZeroStartupOptions StartupOptions;
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;
Mvc = services.AddMvc();
Configuration = configuration;
2020-04-15 01:42:06 +02:00
// create startup options
2020-05-27 18:29:36 +02:00
StartupOptions = new ZeroStartupOptions(Mvc);
StartupOptions.AssemblyDiscoveryRules.Add(new ZeroAssemblyDiscoveryRule());
setupAction?.Invoke(StartupOptions);
// adds and discovers additional and built-in assemblies
new AssemblyDiscovery(Mvc).Execute(StartupOptions.AssemblyDiscoveryRules);
// add default plugin
AddPlugin<ZeroBackofficePlugin>();
2020-05-27 18:29:36 +02:00
// create and bind zero options
Services.AddOptions<ZeroOptions>()
.Bind(Configuration.GetSection("Zero"))
.Configure(opts =>
{
//opts.AssemblyDiscoveryRules.Add(new ZeroAssemblyDiscoveryRule());
opts.ZeroVersion = "0.0.1.0"; // TODO
});
2020-05-13 14:06:11 +02:00
// add transient options to DI
Services.AddTransient<IZeroOptions>(factory => factory.GetService<IOptionsMonitor<ZeroOptions>>().CurrentValue);
// configure MVC
Services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, ZeroBuilderMvcOptions>());
Mvc.AddNewtonsoftJson(x =>
{
// TODO this shall only be configurated for backoffice controllers
BackofficeJsonSerlializerSettings.Setup(x.SerializerSettings);
});
2020-09-18 01:06:21 +02:00
if (Environment.GetEnvironmentVariable("DOTNET_WATCH") == "1")
{
Mvc.AddRazorRuntimeCompilation();
}
2020-11-04 16:16:36 +01:00
// configure Raven + identity
ConfigureDatabase();
ConfigureIdentity();
// configure FluentValidation
2020-07-09 15:33:58 +02:00
ValidatorOptions.Global.PropertyNameResolver = ValidatorCamelCasePropertyResolver.ResolvePropertyName;
// 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-11-06 19:18:56 +01:00
Services.AddScoped<IBackofficeStore, BackofficeStore>();
2020-11-18 12:00:51 +01:00
Services.AddScoped<BackofficeFilterAttribute>();
Services.AddScoped<ModelStateValidationFilterAttribute>();
2020-11-19 00:14:52 +01:00
Services.AddSingleton<ICollectionInterceptorHandler, CollectionInterceptorHandler>();
2020-11-18 15:55:18 +01: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>();
}
/// <summary>
/// Configures Raven database instance
/// </summary>
void ConfigureDatabase()
{
// add raven
2020-11-15 17:07:27 +01:00
Services.AddSingleton<IZeroStore, ZeroStore>(context =>
{
IZeroOptions options = context.GetService<IZeroOptions>();
2020-11-15 17:07:27 +01:00
IDocumentStore store = new ZeroStore(options)
{
2020-11-15 17:07:27 +01:00
Urls = new string[1] { options.Raven.Url }
};
IDocumentStore raven = store.Setup(options).Initialize();
2020-05-15 14:23:47 +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-12-09 13:42:23 +01:00
Services.AddScoped<IZeroDocumentSession>(services =>
{
var session = services.GetRequiredService<IZeroStore>()!.OpenAsyncSession();
session.Advanced.WaitForIndexesAfterSaveChanges();
return session as ZeroDocumentSession;
});
}
/// <summary>
2020-11-04 16:16:36 +01:00
/// Configures identity
/// </summary>
void ConfigureIdentity()
{
2020-11-04 16:16:36 +01:00
Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
Services.AddZeroIdentity<BackofficeUser, BackofficeUserRole>();
Services.Replace<IUserClaimsPrincipalFactory<BackofficeUser>, ZeroBackofficeClaimsPrincipalFactory<BackofficeUser, BackofficeUserRole>>();
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
Services.AddAuthentication(Constants.Auth.BackofficeScheme)
.AddZeroBackofficeCookie<BackofficeUser, BackofficeUserRole>();
2020-11-04 16:16:36 +01:00
Services.AddAuthorization();
2020-04-05 01:19:42 +02:00
}
/// <summary>
/// Use specified options
/// </summary>
2020-04-05 01:19:42 +02:00
public ZeroBuilder WithOptions(Action<ZeroOptions> configureOptions)
{
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
{
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
{
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");
}
}
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
}
}