// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { #if SERVE_REMOTING if (_serverSt.EnableSailAPI) { var machServer = new BlackMaple.MachineWatch.RemotingServer( _fmsImpl, _fmsSt.DataDirectory ); services.AddSingleton <BlackMaple.MachineWatch.RemotingServer>(machServer); } else { // add dummy service which does not serve anything services.AddSingleton <BlackMaple.MachineWatch.RemotingServer>(new BlackMaple.MachineWatch.RemotingServer()); } #endif services .AddSingleton <BlackMaple.MachineFramework.FMSImplementation>(_fmsImpl) .AddSingleton <BlackMaple.MachineFramework.IFMSBackend>(_fmsImpl.Backend) .AddSingleton <Controllers.WebsocketManager>( new Controllers.WebsocketManager( _fmsImpl.Backend.LogDatabase(), _fmsImpl.Backend.JobDatabase(), _fmsImpl.Backend.JobControl()) ); var mvcBuilder = services .AddResponseCompression() .AddCors() .AddMvcCore(options => { options.ModelBinderProviders.Insert(0, new DateTimeBinderProvider()); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddApiExplorer() .AddFormatterMappings() .AddJsonFormatters() .AddJsonOptions(options => { options.SerializerSettings.Converters.Add(new StringEnumConverter()); options.SerializerSettings.Converters.Add(new TimespanConverter()); options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); options.SerializerSettings.ConstructorHandling = Newtonsoft.Json.ConstructorHandling.AllowNonPublicDefaultConstructor; }); services.AddSwaggerDocument(cfg => { cfg.Title = "SeedTactic FMS Insight"; cfg.Description = "API for access to FMS Insight for flexible manufacturing system control"; cfg.Version = "1.11"; var settings = new Newtonsoft.Json.JsonSerializerSettings(); settings.Converters.Add(new StringEnumConverter()); settings.Converters.Add(new TimespanConverter()); settings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); cfg.SerializerSettings = settings; //cfg.DefaultReferenceTypeNullHandling = NJsonSchema.ReferenceTypeNullHandling.NotNull; cfg.DefaultResponseReferenceTypeNullHandling = NJsonSchema.ReferenceTypeNullHandling.NotNull; cfg.RequireParametersWithoutDefault = true; cfg.IgnoreObsoleteProperties = true; }); if (_serverSt.UseAuthentication) { mvcBuilder.AddAuthorization(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = _serverSt.OpenIDConnectAuthority; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = true, ValidAudiences = _serverSt.AuthTokenAudiences.Split(';') }; #if DEBUG options.RequireHttpsMetadata = false; #endif options.Events = new JwtBearerEvents { OnMessageReceived = context => { var token = context.Request.Query["token"]; if (context.Request.Path == "/api/v1/events" && !string.IsNullOrEmpty(token)) { context.Token = token; } return(System.Threading.Tasks.Task.CompletedTask); } }; }); } }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure( IApplicationBuilder app, IApplicationLifetime lifetime, IHostingEnvironment env, #if SERVE_REMOTING BlackMaple.MachineWatch.RemotingServer machServer, #endif Controllers.WebsocketManager wsManager) { app.UseResponseCompression(); app.UseCors(builder => builder .WithOrigins( _fmsSt.AdditionalLogServers #if DEBUG .Concat(new[] { "http://localhost:1234" }) // parcel bundler url #endif .ToArray() ) #if DEBUG .WithMethods(new[] { "GET", "PUT", "POST", "DELETE" }) #else .WithMethods("GET") #endif .WithHeaders("content-type", "authorization") ); app.UseMiddleware(typeof(ErrorHandlingMiddleware)); if (_serverSt.UseAuthentication) { app.UseAuthentication(); } app.UseMvc(); // https://github.com/aspnet/Home/issues/2442 var fileExt = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); fileExt.Mappings[".webmanifest"] = "application/manifest+json"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = fileExt }); if (!string.IsNullOrEmpty(_fmsSt.InstructionFilePath)) { if (System.IO.Directory.Exists(_fmsSt.InstructionFilePath)) { app.UseStaticFiles(new StaticFileOptions() { FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(_fmsSt.InstructionFilePath), RequestPath = "/instructions" }); } else { Log.Error("Instruction directory {path} does not exist or is not a directory", _fmsSt.InstructionFilePath); } } if (!string.IsNullOrEmpty(_serverSt.TLSCertFile)) { if (!env.IsDevelopment()) { app.UseHsts(); } app.UseHttpsRedirection(); } app.UseWebSockets(); app.Use(async(context, next) => { if (context.Request.Path == "/api/v1/events") { if (context.WebSockets.IsWebSocketRequest) { if (_serverSt.UseAuthentication) { var res = await context.AuthenticateAsync(); if (res.Failure != null) { context.Response.StatusCode = 401; return; } } var ws = await context.WebSockets.AcceptWebSocketAsync(); await wsManager.HandleWebsocket(ws); } else { context.Response.StatusCode = 400; } } else { await next(); } }); app .UseSwagger(settings => { settings.PostProcess = (doc, req) => { doc.Host = null; doc.BasePath = null; doc.Schemes = null; }; }) .UseSwaggerUi3(); app.Run(async context => { context.Response.ContentType = "text/html"; await context.Response.SendFileAsync(System.IO.Path.Combine(env.WebRootPath, "index.html")); }); lifetime.ApplicationStopping.Register(async() => { if (_fmsImpl == null) { return; } await wsManager.CloseAll(); foreach (var w in _fmsImpl.Workers) { w.Dispose(); } _fmsImpl.Backend?.Dispose(); }); #if SERVE_REMOTING if (_serverSt.EnableSailAPI) { lifetime.ApplicationStopping.Register(() => { machServer.Dispose(); }); } #endif }