You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
456 lines
18 KiB
456 lines
18 KiB
using Hangfire;
|
|
using Hangfire.MemoryStorage;
|
|
using Infrastructure.Application.Services.Settings;
|
|
using Infrastructure.Data;
|
|
using Infrastructure.Events;
|
|
using Infrastructure.Extensions;
|
|
using Infrastructure.Office;
|
|
using Infrastructure.Security;
|
|
using Infrastructure.UI;
|
|
using Infrastructure.Web.Authentication.Cookies;
|
|
using Infrastructure.Web.Mvc.ModelBinding.Metadata;
|
|
using Infrastructure.Web.SignalR;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.AspNetCore.HttpOverrides;
|
|
using Microsoft.AspNetCore.Localization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.Razor;
|
|
using Microsoft.AspNetCore.Mvc.Versioning;
|
|
using Microsoft.AspNetCore.ResponseCompression;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Storage;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Microsoft.OpenApi.Models;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Unicode;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Infrastructure.Web
|
|
{
|
|
public class BaseStartup
|
|
{
|
|
protected readonly IConfiguration cfg;
|
|
protected readonly IWebHostEnvironment env;
|
|
private string _connectionString;
|
|
private readonly string _origins = "AllowAllHeaders";
|
|
|
|
public BaseStartup(IConfiguration configuration, IWebHostEnvironment env)
|
|
{
|
|
this.cfg = configuration;
|
|
this.env = env;
|
|
}
|
|
|
|
public virtual void ConfigureServices(IServiceCollection services)
|
|
{
|
|
if (!this.env.IsDevelopment())
|
|
{
|
|
services.AddResponseCompression(options =>
|
|
{
|
|
options.Providers.Add<BrotliCompressionProvider>();
|
|
options.Providers.Add<GzipCompressionProvider>();
|
|
});
|
|
}
|
|
services.AddTransient<SettingService>();
|
|
services.AddTransient<ISettingService, CachedSettingService>();
|
|
services.AddSingleton(cfg as IConfigurationRoot);
|
|
services.AddCors(options => options.AddPolicy(_origins,
|
|
builder =>
|
|
{
|
|
builder.SetIsOriginAllowed(o => true)
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader()
|
|
.AllowCredentials();
|
|
}));
|
|
services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
|
|
services.Configure<FormOptions>(o =>
|
|
{
|
|
o.ValueCountLimit = int.MaxValue;
|
|
o.MultipartBodyLengthLimit = long.MaxValue;
|
|
});
|
|
this.ConfigureOptions(services);
|
|
EfDbContext.OnModelCreatingAction = this.OnModelCreating;
|
|
services.AddScoped<DbContext, EfDbContext>();
|
|
var dbConnectionName = this.cfg.GetSection("AppSettings").GetValue<string>("database");
|
|
_connectionString = this.cfg.GetConnectionString(dbConnectionName);
|
|
if (dbConnectionName == "sqlite")
|
|
{
|
|
services.AddDbContext<EfDbContext>(options => options.UseSqlite(_connectionString));
|
|
}
|
|
else if (dbConnectionName == "mysql")
|
|
{
|
|
services.AddDbContext<EfDbContext>(options => options.UseMySql(_connectionString));
|
|
}
|
|
else
|
|
{
|
|
services.AddDbContext<EfDbContext>(options => options.UseNpgsql(_connectionString));
|
|
}
|
|
//https://github.com/aspnet/Entropy/blob/master/samples/Localization.StarterWeb/Startup.cs
|
|
services.AddLocalization(options => options.ResourcesPath = null);
|
|
//https://yetawf.com/BlogEntry/Title/AdditionalMetadataAttribute%20Anyone/?BlogEntry=1005
|
|
services.AddMvc(o => o.ModelMetadataDetailsProviders.Add(new AdditionalMetadataProvider()))
|
|
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
|
.AddNewtonsoftJson()
|
|
.AddControllersAsServices()
|
|
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
|
|
.AddDataAnnotationsLocalization(options =>
|
|
{
|
|
options.DataAnnotationLocalizerProvider = (type, factory) =>
|
|
{
|
|
var localizer = factory.Create("Resources.Resource", Assembly.GetEntryAssembly().FullName);
|
|
return localizer;
|
|
};
|
|
});
|
|
services.AddApiVersioning(o =>
|
|
{
|
|
o.ReportApiVersions = true;
|
|
o.AssumeDefaultVersionWhenUnspecified = true;
|
|
o.DefaultApiVersion = new ApiVersion(1, 0);
|
|
o.ApiVersionReader = ApiVersionReader.Combine(
|
|
new UrlSegmentApiVersionReader()
|
|
//,new QueryStringApiVersionReader()
|
|
//,new HeaderApiVersionReader()
|
|
);
|
|
});
|
|
services.AddControllers()
|
|
.ConfigureApiBehaviorOptions(options =>
|
|
{
|
|
options.SuppressConsumesConstraintForFormFileParameters = true;
|
|
options.SuppressInferBindingSourcesForParameters = true;
|
|
options.SuppressModelStateInvalidFilter = true;
|
|
options.SuppressMapClientErrors = true;
|
|
//options.ClientErrorMapping[404].Link = "https://httpstatuses.com/404";
|
|
})
|
|
.AddNewtonsoftJson(o =>
|
|
{
|
|
o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
|
});
|
|
services.Configure<RequestLocalizationOptions>(o =>
|
|
{
|
|
var supportedCultures = new[]
|
|
{
|
|
new CultureInfo("zh-CN"),
|
|
new CultureInfo("en-US")
|
|
};
|
|
o.DefaultRequestCulture = new RequestCulture(culture: "zh-CN", uiCulture: "zh-CN");
|
|
o.SupportedCultures = supportedCultures;
|
|
o.SupportedUICultures = supportedCultures;
|
|
});
|
|
this.AddAuthentication(services);
|
|
//services.AddAuthorization();
|
|
this.AddSwagger(services);
|
|
var cache = this.cfg.GetSection("AppSettings").GetValue<string>("cache", "memory");
|
|
if (cache == "redis")
|
|
{
|
|
services.AddStackExchangeRedisCache(o =>
|
|
{
|
|
o.Configuration = cfg.GetConnectionString("redis");
|
|
o.InstanceName = "iot";
|
|
});
|
|
}
|
|
else
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
}
|
|
services.AddSession(options =>
|
|
{
|
|
options.IdleTimeout = TimeSpan.FromMinutes(30);
|
|
options.Cookie.HttpOnly = true;
|
|
});
|
|
services.AddHttpClient();
|
|
var signalRBuilder = services
|
|
.AddSignalR(o => o.EnableDetailedErrors = true)
|
|
.AddJsonProtocol();
|
|
if (cfg.GetValue<bool>("useRedisSignalR", false))
|
|
{
|
|
signalRBuilder = signalRBuilder.AddStackExchangeRedis(cfg.GetConnectionString("redis"), o =>
|
|
{
|
|
o.Configuration.ChannelPrefix = "iot";
|
|
//o.ConnectionFactory = async writer =>
|
|
//{
|
|
// var config = new ConfigurationOptions
|
|
// {
|
|
// AbortOnConnectFail = false
|
|
// };
|
|
// config.EndPoints.Add(IPAddress.Loopback, 0);
|
|
// config.SetDefaultPorts();
|
|
// var connection = await ConnectionMultiplexer.ConnectAsync(config, writer);
|
|
// connection.ConnectionFailed += (_, e) =>
|
|
// {
|
|
// Console.WriteLine("Connection to Redis failed.");
|
|
// };
|
|
|
|
// if (!connection.IsConnected)
|
|
// {
|
|
// Console.WriteLine("Did not connect to Redis.");
|
|
// }
|
|
|
|
// return connection;
|
|
//};
|
|
});
|
|
}
|
|
this.UseScheduler(services);
|
|
|
|
services.AddMemoryCache();
|
|
services.AddHttpContextAccessor();
|
|
services.AddSingleton<ITicketStore, DistributedCacheTicketStore>();
|
|
services.AddTransient(typeof(IRepository<>), typeof(EfRepository<>));
|
|
services.AddTransient<IEncryptionService, EncryptionService>();
|
|
services.AddTransient<IExcelReader, ExcelReader>();
|
|
AppDomain.CurrentDomain.GetAssemblies().SelectMany(o => o.GetTypes())
|
|
.Where(t => t.GetInterfaces().Any(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(IEventHander<>)))
|
|
.ToList()
|
|
.ForEach(t =>
|
|
{
|
|
t.GetInterfaces().Where(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(IEventHander<>)).ToList().ForEach(o =>
|
|
{
|
|
services.AddTransient(o, t);
|
|
});
|
|
});
|
|
services.AddSingleton<IEventPublisher, EventPublisher>();
|
|
if (cfg.GetValue("useServiceServer", false))
|
|
{
|
|
services.AddNacosAspNetCore(cfg);
|
|
}
|
|
}
|
|
|
|
public virtual void UseScheduler(IServiceCollection services)
|
|
{
|
|
services.AddHangfire(x => x.UseMemoryStorage());
|
|
}
|
|
|
|
public virtual void ConfigureOptions(IServiceCollection services)
|
|
{
|
|
services.ConfigureOptions(new FileConfigureOptions(this.env));
|
|
}
|
|
|
|
public virtual void AddAuthentication(IServiceCollection services)
|
|
{
|
|
services.AddAuthentication(x =>
|
|
{
|
|
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
})
|
|
.AddJwtBearer(o =>
|
|
{
|
|
services.AddSingleton(o);
|
|
o.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuerSigningKey = true,
|
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(cfg["jwt:key"])),
|
|
ValidateIssuer = false,
|
|
ValidIssuer = cfg["jwt:issuer"],
|
|
ValidateAudience = false,
|
|
ValidAudience = cfg["jwt:audience"]
|
|
};
|
|
o.Events = new JwtBearerEvents
|
|
{
|
|
OnTokenValidated = context =>
|
|
{
|
|
if (DateTime.UtcNow > context.SecurityToken.ValidTo)
|
|
{
|
|
context.Fail("");
|
|
}
|
|
return Task.CompletedTask;
|
|
},
|
|
OnForbidden = context =>
|
|
{
|
|
return Task.CompletedTask;
|
|
},
|
|
OnAuthenticationFailed = context =>
|
|
{
|
|
return Task.CompletedTask;
|
|
},
|
|
OnChallenge = context =>
|
|
{
|
|
if (!context.Request.IsAjax())
|
|
{
|
|
context.Response.Redirect("/UserCenter/Account/Login");
|
|
context.HandleResponse();
|
|
}
|
|
return Task.CompletedTask;
|
|
},
|
|
OnMessageReceived = context =>
|
|
{
|
|
if (!context.Request.IsStatic())
|
|
{
|
|
Debug.WriteLine(context.Request.Path);
|
|
if (context.Request.Query.ContainsKey("access_token"))
|
|
{
|
|
context.Token = context.Request.Query["access_token"];
|
|
}
|
|
var jwtCookieName = context.HttpContext.GetJwtCookieName();
|
|
if (!context.Request.Headers.ContainsKey("Authorization") && context.Request.Cookies.Keys.Contains(jwtCookieName))
|
|
{
|
|
context.Token = context.Request.Cookies[jwtCookieName];
|
|
}
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
};
|
|
o.SecurityTokenValidators.Clear();
|
|
o.SecurityTokenValidators.Insert(0, new JwtTokenValidator(services.BuildServiceProvider()));
|
|
});
|
|
}
|
|
|
|
public virtual void AddSwagger(IServiceCollection services)
|
|
{
|
|
services.AddSwaggerGen(c =>
|
|
{
|
|
c.SwaggerDoc(this.cfg.GetValue<string>("openapi.name", "v1"), new OpenApiInfo
|
|
{
|
|
Title = this.cfg.GetValue<string>("openapi.title", "web api"),
|
|
Version = this.cfg.GetValue<string>("openapi.version", "1.0")
|
|
});
|
|
c.EnableAnnotations();
|
|
});
|
|
}
|
|
|
|
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
|
|
{
|
|
if (app == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(app));
|
|
}
|
|
if (!env.IsDevelopment())
|
|
{
|
|
app.UseResponseCompression();
|
|
}
|
|
if (env.IsDevelopment())
|
|
{
|
|
app.UseDeveloperExceptionPage();
|
|
}
|
|
else
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
app.UseStatusCodePagesWithReExecute("/Error");
|
|
}
|
|
string basePath = this.cfg.GetValue<String>("BasePath", "");
|
|
app.UsePathBase(basePath);
|
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
|
{
|
|
ForwardedHeaders = ForwardedHeaders.All
|
|
});
|
|
|
|
app.UseStaticFiles();
|
|
app.UseRouting();
|
|
app.UseCors(_origins);
|
|
var localizationOption = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
|
|
app.UseRequestLocalization(localizationOption.Value);
|
|
app.UseSession();
|
|
this.UseAuthentication(app);
|
|
this.UseSwagger(app);
|
|
this.UseScheduler(app);
|
|
app.UseEndpoints(endpoints =>
|
|
{
|
|
this.UseSignalR(endpoints);
|
|
endpoints.MapControllerRoute(
|
|
name: "areas",
|
|
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
|
|
|
|
endpoints.MapControllerRoute(
|
|
name: "default",
|
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
|
});
|
|
|
|
using var scope = app.ApplicationServices.CreateScope();
|
|
var services = scope.ServiceProvider;
|
|
this.CreateDatabase(services);
|
|
if (cfg.GetValue("useServiceServer", false))
|
|
{
|
|
app.UseNacosAspNetCore();
|
|
}
|
|
}
|
|
|
|
public virtual void CreateDatabase(IServiceProvider services)
|
|
{
|
|
var context = services.GetService<DbContext>();
|
|
if (context.Database.EnsureCreated())
|
|
{
|
|
var sql = context.GetService<IRelationalDatabaseCreator>().GenerateCreateScript();
|
|
var file = "db.sql";
|
|
if (File.Exists(file))
|
|
{
|
|
File.Delete(file);
|
|
}
|
|
using (var fs = File.CreateText(file))
|
|
{
|
|
fs.Write(sql);
|
|
}
|
|
this.Seed(context, services, this.cfg);
|
|
}
|
|
}
|
|
|
|
public virtual void UseAuthentication(IApplicationBuilder app)
|
|
{
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
}
|
|
|
|
public virtual void UseSwagger(IApplicationBuilder app)
|
|
{
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI(c =>
|
|
{
|
|
c.SwaggerEndpoint("v1/swagger.json", "API V1");
|
|
});
|
|
}
|
|
|
|
public virtual void UseScheduler(IApplicationBuilder app)
|
|
{
|
|
app.UseHangfireDashboard();
|
|
app.UseHangfireServer();
|
|
}
|
|
|
|
public virtual void UseSignalR(IEndpointRouteBuilder endpoints)
|
|
{
|
|
this.UseSignalR<BasePageHub>(endpoints);
|
|
}
|
|
|
|
protected virtual void UseSignalR<T>(IEndpointRouteBuilder endpoints) where T : Hub
|
|
{
|
|
endpoints.MapHub<T>("/hub", o =>
|
|
{
|
|
o.ApplicationMaxBufferSize = long.MaxValue;
|
|
o.TransportMaxBufferSize = long.MaxValue;
|
|
});
|
|
}
|
|
|
|
public virtual void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
}
|
|
|
|
public virtual void Seed(DbContext dbContext, IServiceProvider serviceProvider, IConfiguration configuration)
|
|
{
|
|
}
|
|
|
|
public virtual Task ValidatePrincipal(CookieValidatePrincipalContext arg)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public virtual Task TokenValidated(TokenValidatedContext arg)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
} |