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.
iot/projects/Infrastructure/Web/BaseStartup.cs

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