using FluentValidation; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Raven.Client.Documents; using Raven.Client.Documents.Conventions; using System; using System.Collections.Generic; using System.Threading.Tasks; using zero.Core; using zero.Core.Api; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Identity; using zero.Core.Mapper; using zero.Core.Options; using zero.Core.Plugins; using zero.Core.Validation; using zero.Web.Defaults; using zero.Web.Mapper; namespace zero.Web { // TODO maybe use a middleware like Hangfire does: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.AspNetCore/HangfireEndpointRouteBuilderExtensions.cs public class ZeroBuilder { public virtual IServiceCollection Services { get; } IConfiguration Configuration { get; set; } public ZeroBuilder(IServiceCollection services, IConfiguration configuration) { Services = services; Configuration = configuration; //CultureInfo cultureInfo = new CultureInfo("en-US"); //cultureInfo.NumberFormat.CurrencySymbol = "€"; //CultureInfo.DefaultThreadCurrentCulture = cultureInfo; AddConfiguration(); ConfgureMvc(); ConfigureDatabase(); ConfigureValidation(); ConfigureMapper(); ConfigureIdentity(); AddServices(); //AddPlugins(); } /// /// Adds zero specific configuration /// void AddConfiguration() { Services.AddOptions() .Bind(Configuration.GetSection("Zero")) .Configure(opts => { opts.ZeroVersion = "0.0.1.0"; // TODO opts.Plugins = new PluginCollection(Services, opts); // resolve default plugin new DefaultBackofficePlugin().Configure(Services, opts); }); Services.AddTransient(factory => factory.GetService>().CurrentValue); } /// /// Adds all services which are required by zero /// void AddServices() { Services.AddHttpContextAccessor(); Services.AddTransient(); Services.AddTransient(factory => { IWebHostEnvironment env = factory.GetService(); return new Paths(env.WebRootPath, true); }); Services.AddTransient(); Services.AddTransient(); Services.AddTransient(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); Services.AddScoped(); } /// /// Configures ASP.NET Core MVC /// IMvcBuilder ConfgureMvc() { IMvcBuilder mvc = Services.AddMvc(); mvc.AddNewtonsoftJson(opts => { opts.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" }); opts.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; JsonConvert.DefaultSettings = () => opts.SerializerSettings; }); if (Environment.GetEnvironmentVariable("DOTNET_WATCH") == "1") { mvc.AddRazorRuntimeCompilation(); } return mvc; } /// /// Configures Raven database instance /// void ConfigureDatabase() { // add raven Services.AddSingleton(context => { IZeroOptions options = context.GetService(); DocumentStore store = new DocumentStore() { Urls = new string[1] { options.Raven.Url }, Database = options.Raven.Database }; store.Conventions.FindCollectionName = type => { if (!typeof(IZeroDbConventions).IsAssignableFrom(type)) { return DocumentConventions.DefaultGetCollectionName(type); } Type finalType = type; if (type.IsSubclassOf(typeof(SpaceContent))) { finalType = typeof(SpaceContent); } return Constants.Database.CollectionPrefix + DocumentConventions.DefaultGetCollectionName(finalType); }; store.Conventions.TransformTypeCollectionNameToDocumentIdPrefix = name => { return name.ToCamelCaseId(); }; store.Conventions.IdentityPartsSeparator = "."; return store.Initialize(); }); } /// /// Configures FluentValidation /// void ConfigureValidation() { ValidatorOptions.PropertyNameResolver = ValidatorCamelCasePropertyResolver.ResolvePropertyName; } /// /// Configures internal object mapper /// void ConfigureMapper() { Services.AddMapper(opts => { opts.Add(); opts.Add(); opts.Add(); opts.Add(); opts.Add(); }); } /// /// Configures user + roles /// void ConfigureIdentity() { Services.AddIdentity(opts => { opts.ClaimsIdentity.UserIdClaimType = Constants.Auth.Claims.UserId; opts.ClaimsIdentity.UserNameClaimType = Constants.Auth.Claims.UserName; opts.ClaimsIdentity.RoleClaimType = Constants.Auth.Claims.Role; opts.ClaimsIdentity.SecurityStampClaimType = Constants.Auth.Claims.SecurityStamp; opts.Password.RequireDigit = false; opts.Password.RequireLowercase = false; opts.Password.RequireUppercase = false; opts.Password.RequireNonAlphanumeric = false; opts.Password.RequiredLength = 8; opts.Password.RequiredUniqueChars = 1; opts.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); opts.Lockout.MaxFailedAccessAttempts = 5; opts.Lockout.AllowedForNewUsers = true; }) .AddClaimsPrincipalFactory() .AddDefaultTokenProviders(); Services.ConfigureApplicationCookie(opts => { //opts.Cookie.Path = // TODO use backoffice path opts.Cookie.Name = Constants.Auth.CookieName; opts.SlidingExpiration = true; opts.ExpireTimeSpan = TimeSpan.FromMinutes(60); // override redirect to login page (handled by vue frontend) and return a 401 instead opts.Events.OnRedirectToLogin = (context) => { context.Response.StatusCode = 401; return Task.CompletedTask; }; }); Services.AddTransient, ZeroUserStore>(); Services.AddTransient, ZeroRoleStore>(); Services.AddScoped>(); Services.AddScoped>(); Services.AddScoped>(); } /// /// Use specified options /// public ZeroBuilder WithOptions(Action configureOptions) { Services.Configure(configureOptions); return this; } } }