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;
}
}
}