2026-06-14 12:46:29 +06:00
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
{
/// <summary>
///
/// </summary>
public class Startup
{
private readonly AppSettings _appSettings ;
/// <summary>
///
/// </summary>
public IConfiguration Configuration { get ; }
/// <summary>
/// Initializes a new instance of the Startup class using the specified configuration settings.
/// </summary>
/// <param name="configuration">The configuration settings used to initialize the application. Cannot be null.</param>
public Startup ( IConfiguration configuration )
{
Configuration = configuration ;
_appSettings = Configuration . GetSection ( "AppSettings" ) . Get < AppSettings > ( ) ;
}
2026-06-16 11:36:11 +06:00
2026-06-14 12:46:29 +06:00
/// <summary>
///
/// </summary>
/// <param name="services"></param>
public void ConfigureServices ( IServiceCollection services )
{
try
{
if ( _appSettings . IsValid ( ) )
{
#region AppSettings & MenuSettings
services . Configure < AppSettings > ( Configuration . GetSection ( "AppSettings" ) ) ;
services . Configure < MenuSettings > ( 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 < CookiePolicyOptions > ( 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 < ForwardedHeadersOptions > ( 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 < IConfigureOptions < SwaggerGenOptions > , ConfigureSwaggerOptions > ( ) ;
services . AddSwaggerGen ( options = >
{
options . OperationFilter < SwaggerDefaultValues > ( ) ;
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
}
}
/// <summary>
///
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
/// <param name="provider"></param>
/// <param name="loggerFactory"></param>
/// <param name="logger"></param>
public void Configure ( IApplicationBuilder app , IWebHostEnvironment env , IApiVersionDescriptionProvider provider , ILoggerFactory loggerFactory , ILogger < Startup > 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 < IExceptionHandlerPathFeature > ( ) ;
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 < NotificationHub > ( "/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 ( "<b>Ease Taskforce Api is Running</b>" ) ;
} ) ;
}
catch ( Exception ex )
{
logger . LogError ( ex ) ;
}
}
/// <summary>
///
/// </summary>
static string XmlCommentsFilePath
{
get
{
var basePath = AppContext . BaseDirectory ;
var fileName = typeof ( Startup ) . GetTypeInfo ( ) . Assembly . GetName ( ) . Name + ".xml" ;
return Path . Combine ( basePath , fileName ) ;
}
}
}
}