using Asp.Versioning; using Asp.Versioning.ApiExplorer; using OnlineSalesAutoCrop.CoreAPI.API.Swagger; using OnlineSalesAutoCrop.CoreAPI.Configuration.DI; using OnlineSalesAutoCrop.CoreAPI.Configurations; using OnlineSalesAutoCrop.CoreAPI.Models; using OnlineSalesAutoCrop.CoreAPI.Models.Global; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; using OnlineSalesAutoCrop.CoreAPI.SignalRHub; using Hangfire; using Hangfire.SqlServer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.CookiePolicy; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; 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 Newtonsoft.Json; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.IO; using System.Net; using System.Reflection; using System.Text; using System.Threading.RateLimiting; using System.Threading.Tasks; namespace OnlineSalesAutoCrop.CoreAPI { /// /// /// public class Startup { private readonly AppSettings _appSettings; /// /// /// public IConfiguration Configuration { get; } /// /// Initializes a new instance of the Startup class using the specified configuration settings. /// /// The configuration settings used to initialize the application. Cannot be null. public Startup(IConfiguration configuration) { Configuration = configuration; _appSettings = Configuration.GetSection("AppSettings").Get(); } /// /// /// /// public void ConfigureServices(IServiceCollection services) { try { if (_appSettings.IsValid()) { #region AppSettings & MenuSettings services.Configure(Configuration.GetSection("AppSettings")); services.Configure(Configuration.GetSection("MenuSettings")); #endregion #region Controllers services.AddControllers(options => { options.Filters.Add(new ProducesAttribute("application/json")); }); #endregion #region API versioning services.AddApiVersioning(options => { options.ReportApiVersions = true; options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ApiVersionReader = new UrlSegmentApiVersionReader(); }) .AddMvc() // API versioning extensions for MVC Core .AddApiExplorer(); // API version-aware API Explorer extensions #endregion #region CORS string[] origins = _appSettings.CorsOrigins.Split(','); string[] methods = _appSettings.CorsMethods.Split(','); string[] headers = _appSettings.CorsHeaders.Split(','); services.AddCors(options => { options.DefaultPolicyName = "CORSPolicies"; options.AddDefaultPolicy(builder => { builder.WithOrigins(origins) .SetIsOriginAllowedToAllowWildcardSubdomains() .WithMethods(methods) .WithHeaders(headers) .AllowCredentials(); }); }); #endregion #region Caching if (_appSettings.CacheType == EnumCacheType.Redis && !string.IsNullOrEmpty(_appSettings.CacheUrl)) { services.AddStackExchangeRedisCache(options => { options.Configuration = _appSettings.CacheUrl; }); } else if (_appSettings.CacheType == EnumCacheType.SqlServer && !string.IsNullOrEmpty(_appSettings.CacheUrl) && !string.IsNullOrEmpty(_appSettings.CacheTableName) && !string.IsNullOrEmpty(_appSettings.CacheSchemaName)) { services.AddDistributedSqlServerCache(options => { options.ConnectionString = _appSettings.CacheUrl; options.TableName = _appSettings.CacheTableName; options.SchemaName = _appSettings.CacheSchemaName; options.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(5); }); } else { services.AddMemoryCache(); } #endregion #region Cookie Settings services.Configure(options => { options.ConsentCookieValue = "true"; options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = (SameSiteMode)_appSettings.CookieSameSite; if (_appSettings.CookieHttpOnly) { options.HttpOnly = HttpOnlyPolicy.Always; } }); #endregion #region Session services.AddSession(options => { options.Cookie.Name = "OnlineSalesAutoCrop.Net9.Session.Id"; options.Cookie.HttpOnly = _appSettings.CookieHttpOnly; options.Cookie.SameSite = (SameSiteMode)_appSettings.CookieSameSite; options.IdleTimeout = TimeSpan.FromMinutes(_appSettings.CookieLifeTime); }); #endregion #region JWT Authentication var key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey); services.AddAuthentication(options => { options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.SaveToken = true; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { ClockSkew = TimeSpan.Zero, ValidateIssuerSigningKey = true, ValidIssuer = _appSettings.JwtIssuer, ValidAudience = _appSettings.JwtAudience, ValidateIssuer = _appSettings.JwtValidateIssuer, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateAudience = _appSettings.JwtValidateAudience }; options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) context.Response.Headers.Append("Token-Expired", "true"); return Task.CompletedTask; } }; }); #endregion #region Mvc services.AddMvc(); #endregion #region Forwarded Headers services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); #endregion #region Antiforgery services.AddAntiforgery(options => { options.SuppressXFrameOptionsHeader = false; options.HeaderName = "OnlineSalesAutoCrop.X-XSRF-TOKEN"; options.Cookie.Name = "OnlineSalesAutoCrop.X-XSRF-ANTIFORGERY"; options.Cookie.HttpOnly = _appSettings.CookieHttpOnly; options.Cookie.SameSite = (SameSiteMode)_appSettings.CookieSameSite; }); #endregion #region SignalR services.AddSignalR(); services.AddResponseCompression(opt => { opt.MimeTypes = ["application/octet-stream"]; }); #endregion #region Rate Limit if (!string.IsNullOrEmpty(_appSettings.RLName)) { services.AddRateLimiter(options => { options.AddPolicy(_appSettings.RLName, httpContext => RateLimitPartition.GetFixedWindowLimiter( partitionKey: httpContext.GetIpAddress(), factory: _ => new FixedWindowRateLimiterOptions { QueueLimit = _appSettings.RLQueueLimit, PermitLimit = _appSettings.RLPermitLimit, AutoReplenishment = _appSettings.RLAutoReplenishment, Window = TimeSpan.FromSeconds(_appSettings.RLWindowInSecond), QueueProcessingOrder = (QueueProcessingOrder)_appSettings.RLQueueProcessingOrder })); options.OnRejected = (context, cancellationToken) => { if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) { context.HttpContext.Response.Headers.RetryAfter = retryAfter.TotalSeconds.ToString(); } context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; context.HttpContext.Response.WriteAsync(text: $"Too many requests (more than {_appSettings.RLPermitLimit} calls in {_appSettings.RLWindowInSecond} seconds). Please try again later.", cancellationToken: cancellationToken); return new ValueTask(); }; }); } #endregion #region Hangfire if (!string.IsNullOrEmpty(_appSettings.HangfireDb)) { services.AddHangfire(config => { config.UseFilter(new TimeRestrictionServerFilter(startHour: 1, endHour: 7)); config.UseSqlServerStorage(_appSettings.HangfireDb, new SqlServerStorageOptions { DisableGlobalLocks = true, QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true, CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5) }); }); services.AddHangfireServer(); } #endregion #region SWAGGER if (_appSettings.Swagger.Enabled) { services.AddTransient, ConfigureSwaggerOptions>(); services.AddSwaggerGen(options => { options.OperationFilter(); options.IncludeXmlComments(filePath: XmlCommentsFilePath); }); } #endregion #region Business Service services.ConfigureBusinessServices(); #endregion } } catch { //Nothimg to do here as we are handling all exceptions in Configure method using global exception handler } } /// /// /// /// /// /// /// /// public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider, ILoggerFactory loggerFactory, ILogger logger) { logger.LogTrace("Startup::Configure"); try { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { #region Exception Handling in Production mode app.UseExceptionHandler(a => a.Run(async context => { IExceptionHandlerPathFeature feature = context.Features.Get(); Exception exception = feature.Error; HttpStatusCode code = HttpStatusCode.InternalServerError; if (exception is ArgumentNullException) code = HttpStatusCode.BadRequest; else if (exception is ArgumentException) code = HttpStatusCode.BadRequest; else if (exception is UnauthorizedAccessException) code = HttpStatusCode.Unauthorized; else if (exception is WebException && (exception as WebException).Response is HttpWebResponse response) code = response.StatusCode; logger.LogError($"GLOBAL ERROR HANDLER::HTTP:{code}::{exception.Message}"); var result = JsonConvert.SerializeObject(exception, Formatting.Indented); context.Response.Clear(); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(result); })); app.UseHsts(); app.UseHttpsRedirection(); #endregion } #region CORS app.UseCors("CORSPolicies"); #endregion #region Use Static Files app.UseStaticFiles(); #endregion #region Routing app.UseRouting(); #endregion #region Session app.UseSession(); #endregion #region For Authentication app.UseAuthentication(); #endregion #region For Authorization app.UseAuthorization(); #endregion #region X-Frame-Options app.Use(async (context, next) => { if (_appSettings.CookieHttpOnly) { string tokens = context.Request.Cookies["OnlineSalesAutoCrop.X-XSRF-TOKEN"]; if (!string.IsNullOrEmpty(tokens)) context.Request.Headers.Append("OnlineSalesAutoCrop.X-XSRF-TOKEN", tokens); } await next(); }); #endregion #region RateLimiter if (!string.IsNullOrEmpty(_appSettings.RLName)) { app.UseRateLimiter(); } #endregion #region Logger if (!string.IsNullOrEmpty(_appSettings.LoggerPath)) { loggerFactory.AddFile(pathFormat: $@"{_appSettings.LoggerPath}\Log.txt", minimumLevel: (LogLevel)_appSettings.LoggerMinLevel); } #endregion #region Controllers and SignalR app.UseEndpoints(endpoints => { if (!string.IsNullOrEmpty(_appSettings.RLName)) { endpoints.MapControllers().RequireRateLimiting(_appSettings.RLName); } else { endpoints.MapControllers(); } endpoints.MapHub("/signalRHub"); }); #endregion #region Forwarded Headers app.UseForwardedHeaders(); #endregion #region Request Localization app.UseRequestLocalization(); #endregion #region Hangfire if (!string.IsNullOrEmpty(_appSettings.HangfireDb)) { app.UseHangfireDashboard(); RecurringJobOptions options = new() { TimeZone = TimeZoneInfo.Local, MisfireHandling = MisfireHandlingMode.Relaxed }; } #endregion #region Swagger if (_appSettings.IsValid() && _appSettings.Swagger.Enabled) { app.UseSwagger(); app.UseSwaggerUI(options => { foreach (var description in provider.ApiVersionDescriptions) { string sjp = string.IsNullOrWhiteSpace(options.RoutePrefix) ? "." : ".."; options.SwaggerEndpoint($"{sjp}/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); } }); } #endregion app.Run(async (context) => { context.Response.ContentType = "text/html"; await context.Response.WriteAsync("Ease Taskforce Api is Running"); }); } catch (Exception ex) { logger.LogError(ex); } } /// /// /// static string XmlCommentsFilePath { get { var basePath = AppContext.BaseDirectory; var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml"; return Path.Combine(basePath, fileName); } } } }