Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

ServiceCollectionExtensions.cs

using System.Net;
using System.Threading.RateLimiting;
using Azure.Identity;
using Azure.Storage.Blobs;
using FluentValidation;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Serialization;
using Nop.Core;
using Nop.Core.Configuration;
using Nop.Core.Domain.Common;
using Nop.Core.Http;
using Nop.Core.Infrastructure;
using Nop.Core.Security;
using Nop.Data;
using Nop.Services.Authentication;
using Nop.Services.Authentication.External;
using Nop.Services.Common;
using Nop.Web.Framework.Mvc.ModelBinding;
using Nop.Web.Framework.Mvc.ModelBinding.Binders;
using Nop.Web.Framework.Mvc.Routing;
using Nop.Web.Framework.Security.Captcha;
using Nop.Web.Framework.Themes;
using Nop.Web.Framework.Validators;
using Nop.Web.Framework.WebOptimizer;
using WebMarkupMin.AspNetCore8;
using WebMarkupMin.Core;
using WebMarkupMin.NUglify;

namespace Nop.Web.Framework.Infrastructure.Extensions;

/// 
/// Represents extensions of IServiceCollection
/// 
public static class ServiceCollectionExtensions
{
    /// 
    /// Configure base application settings
    /// 
    /// Collection of service descriptors
    /// A builder for web applications and services
    public static void ConfigureApplicationSettings(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        //let the operating system decide what TLS protocol version to use
        //see https://docs.microsoft.com/dotnet/framework/network-programming/tls
        ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault;

        //create default file provider
        CommonHelper.DefaultFileProvider = new NopFileProvider(builder.Environment);

        //register type finder
        var typeFinder = new WebAppTypeFinder();
        Singleton.Instance = typeFinder;
        services.AddSingleton(typeFinder);

        //add configuration parameters
        var configurations = typeFinder
            .FindClassesOfType()
            .Select(configType => (IConfig)Activator.CreateInstance(configType))
            .ToList();

        foreach (var config in configurations)
            builder.Configuration.GetSection(config.Name).Bind(config, options => options.BindNonPublicProperties = true);

        var appSettings = AppSettingsHelper.SaveAppSettings(configurations, CommonHelper.DefaultFileProvider, false);
        services.AddSingleton(appSettings);
    }

    /// 
    /// Add services to the application and configure service provider
    /// 
    /// Collection of service descriptors
    /// A builder for web applications and services
    public static void ConfigureApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        //add accessor to HttpContext
        services.AddHttpContextAccessor();

        //initialize plugins
        var mvcCoreBuilder = services.AddMvcCore();
        var pluginConfig = new PluginConfig();
        builder.Configuration.GetSection(nameof(PluginConfig)).Bind(pluginConfig, options => options.BindNonPublicProperties = true);
        mvcCoreBuilder.PartManager.InitializePlugins(pluginConfig);

        //create engine and configure service provider
        var engine = EngineContext.Create();

        builder.Services.AddRateLimiter(options =>
        {
            var settings = Singleton.Instance.Get();

            options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext =>
                RateLimitPartition.GetFixedWindowLimiter(
                    partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(),
                    factory: partition => new FixedWindowRateLimiterOptions
                    {
                        AutoReplenishment = true,
                        PermitLimit = settings.PermitLimit,
                        QueueLimit = settings.QueueCount,
                        Window = TimeSpan.FromMinutes(1)
                    }));

            options.RejectionStatusCode = settings.RejectionStatusCode;
        });

        engine.ConfigureServices(services, builder.Configuration);
    }

    /// 
    /// Register HttpContextAccessor
    /// 
    /// Collection of service descriptors
    public static void AddHttpContextAccessor(this IServiceCollection services)
    {
        services.AddSingleton();
    }

    /// 
    /// Adds services required for anti-forgery support
    /// 
    /// Collection of service descriptors
    public static void AddAntiForgery(this IServiceCollection services)
    {
        //override cookie name
        services.AddAntiforgery(options =>
        {
            options.Cookie.Name = $"{NopCookieDefaults.Prefix}{NopCookieDefaults.AntiforgeryCookie}";
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        });
    }

    /// 
    /// Adds services required for application session state
    /// 
    /// Collection of service descriptors
    public static void AddHttpSession(this IServiceCollection services)
    {
        services.AddSession(options =>
        {
            options.Cookie.Name = $"{NopCookieDefaults.Prefix}{NopCookieDefaults.SessionCookie}";
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        });
    }

    /// 
    /// Adds services required for themes support
    /// 
    /// Collection of service descriptors
    public static void AddThemes(this IServiceCollection services)
    {
        if (!DataSettingsManager.IsDatabaseInstalled())
            return;

        //themes support
        services.Configure(options =>
        {
            options.ViewLocationExpanders.Add(new ThemeableViewLocationExpander());
        });
    }

    /// 
    /// Adds services required for distributed cache
    /// 
    /// Collection of service descriptors
    public static void AddDistributedCache(this IServiceCollection services)
    {
        var appSettings = Singleton.Instance;
        var distributedCacheConfig = appSettings.Get();

        if (!distributedCacheConfig.Enabled)
            return;

        switch (distributedCacheConfig.DistributedCacheType)
        {
            case DistributedCacheType.Memory:
                services.AddDistributedMemoryCache();
                break;

            case DistributedCacheType.SqlServer:
                services.AddDistributedSqlServerCache(options =>
                {
                    options.ConnectionString = distributedCacheConfig.ConnectionString;
                    options.SchemaName = distributedCacheConfig.SchemaName;
                    options.TableName = distributedCacheConfig.TableName;
                });
                break;

            case DistributedCacheType.Redis:
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = distributedCacheConfig.ConnectionString;
                    options.InstanceName = distributedCacheConfig.InstanceName ?? string.Empty;
                });
                break;

            case DistributedCacheType.RedisSynchronizedMemory:
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = distributedCacheConfig.ConnectionString;
                    options.InstanceName = distributedCacheConfig.InstanceName ?? string.Empty;
                });
                break;
        }
    }

    /// 
    /// Adds data protection services
    /// 
    /// Collection of service descriptors
    public static void AddNopDataProtection(this IServiceCollection services)
    {
        var appSettings = Singleton.Instance;
        if (appSettings.Get().Enabled && appSettings.Get().StoreDataProtectionKeys)
        {
            var blobServiceClient = new BlobServiceClient(appSettings.Get().ConnectionString);
            var blobContainerClient = blobServiceClient.GetBlobContainerClient(appSettings.Get().DataProtectionKeysContainerName);
            var blobClient = blobContainerClient.GetBlobClient(NopDataProtectionDefaults.AzureDataProtectionKeyFile);

            var dataProtectionBuilder = services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient);

            if (!appSettings.Get().DataProtectionKeysEncryptWithVault)
                return;

            var keyIdentifier = appSettings.Get().DataProtectionKeysVaultId;
            var credentialOptions = new DefaultAzureCredentialOptions();
            var tokenCredential = new DefaultAzureCredential(credentialOptions);

            dataProtectionBuilder.ProtectKeysWithAzureKeyVault(new Uri(keyIdentifier), tokenCredential);
        }
        else
        {
            var dataProtectionKeysPath = CommonHelper.DefaultFileProvider.MapPath(NopDataProtectionDefaults.DataProtectionKeysPath);
            var dataProtectionKeysFolder = new System.IO.DirectoryInfo(dataProtectionKeysPath);

            //configure the data protection system to persist keys to the specified directory
            services.AddDataProtection().PersistKeysToFileSystem(dataProtectionKeysFolder);
        }
    }

    /// 
    /// Adds authentication service
    /// 
    /// Collection of service descriptors
    public static void AddNopAuthentication(this IServiceCollection services)
    {
        //set default authentication schemes
        var authenticationBuilder = services.AddAuthentication(options =>
        {
            options.DefaultChallengeScheme = NopAuthenticationDefaults.AuthenticationScheme;
            options.DefaultScheme = NopAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = NopAuthenticationDefaults.ExternalAuthenticationScheme;
        });

        //add main cookie authentication
        authenticationBuilder.AddCookie(NopAuthenticationDefaults.AuthenticationScheme, options =>
        {
            options.Cookie.Name = $"{NopCookieDefaults.Prefix}{NopCookieDefaults.AuthenticationCookie}";
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            options.LoginPath = NopAuthenticationDefaults.LoginPath;
            options.AccessDeniedPath = NopAuthenticationDefaults.AccessDeniedPath;
        });

        //add external authentication
        authenticationBuilder.AddCookie(NopAuthenticationDefaults.ExternalAuthenticationScheme, options =>
        {
            options.Cookie.Name = $"{NopCookieDefaults.Prefix}{NopCookieDefaults.ExternalAuthenticationCookie}";
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            options.LoginPath = NopAuthenticationDefaults.LoginPath;
            options.AccessDeniedPath = NopAuthenticationDefaults.AccessDeniedPath;
        });

        //register and configure external authentication plugins now
        var typeFinder = Singleton.Instance;
        var externalAuthConfigurations = typeFinder.FindClassesOfType();
        var externalAuthInstances = externalAuthConfigurations
            .Select(x => (IExternalAuthenticationRegistrar)Activator.CreateInstance(x));

        foreach (var instance in externalAuthInstances)
            instance.Configure(authenticationBuilder);
    }

    /// 
    /// Add and configure MVC for the application
    /// 
    /// Collection of service descriptors
    /// A builder for configuring MVC services
    public static IMvcBuilder AddNopMvc(this IServiceCollection services)
    {
        //add basic MVC feature
        var mvcBuilder = services.AddControllersWithViews();

        mvcBuilder.AddRazorRuntimeCompilation();

        var appSettings = Singleton.Instance;
        if (appSettings.Get().UseSessionStateTempDataProvider)
        {
            //use session-based temp data provider
            mvcBuilder.AddSessionStateTempDataProvider();
        }
        else
        {
            //use cookie-based temp data provider
            mvcBuilder.AddCookieTempDataProvider(options =>
            {
                options.Cookie.Name = $"{NopCookieDefaults.Prefix}{NopCookieDefaults.TempDataCookie}";
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            });
        }

        services.AddRazorPages();

        //MVC now serializes JSON with camel case names by default, use this code to avoid it
        mvcBuilder.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

        //set some options
        mvcBuilder.AddMvcOptions(options =>
        {
            options.ModelBinderProviders.Insert(1, new NopModelBinderProvider());
            //add custom display metadata provider 
            options.ModelMetadataDetailsProviders.Add(new NopMetadataProvider());

            //in .NET model binding for a non-nullable property may fail with an error message "The value '' is invalid"
            //here we set the locale name as the message, we'll replace it with the actual one later when not-null validation failed
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(_ => NopValidationDefaults.NotNullValidationLocaleName);
        });

        //add fluent validation
        services.AddFluentValidationAutoValidation().AddFluentValidationClientsideAdapters();

        //register all available validators from Nop assemblies
        var assemblies = mvcBuilder.PartManager.ApplicationParts
            .OfType()
            .Where(part => part.Name.StartsWith("Nop", StringComparison.InvariantCultureIgnoreCase))
            .Select(part => part.Assembly);
        services.AddValidatorsFromAssemblies(assemblies);

        //register controllers as services, it'll allow to override them
        mvcBuilder.AddControllersAsServices();

        return mvcBuilder;
    }

    /// 
    /// Register custom RedirectResultExecutor
    /// 
    /// Collection of service descriptors
    public static void AddNopRedirectResultExecutor(this IServiceCollection services)
    {
        //we use custom redirect executor as a workaround to allow using non-ASCII characters in redirect URLs
        services.AddScoped, NopRedirectResultExecutor>();
    }

    /// 
    /// Add and configure WebMarkupMin service
    /// 
    /// Collection of service descriptors
    public static void AddNopWebMarkupMin(this IServiceCollection services)
    {
        //check whether database is installed
        if (!DataSettingsManager.IsDatabaseInstalled())
            return;

        services
            .AddWebMarkupMin(options =>
            {
                options.AllowMinificationInDevelopmentEnvironment = true;
                options.AllowCompressionInDevelopmentEnvironment = true;
                options.DisableMinification = !EngineContext.Current.Resolve().EnableHtmlMinification;
                options.DisableCompression = true;
                options.DisablePoweredByHttpHeaders = true;
            })
            .AddHtmlMinification(options =>
            {
                options.MinificationSettings.AttributeQuotesRemovalMode = HtmlAttributeQuotesRemovalMode.KeepQuotes;

                options.CssMinifierFactory = new NUglifyCssMinifierFactory();
                options.JsMinifierFactory = new NUglifyJsMinifierFactory();
            })
            .AddXmlMinification(options =>
            {
                var settings = options.MinificationSettings;
                settings.RenderEmptyTagsWithSpace = true;
                settings.CollapseTagsWithoutContent = true;
            });
    }

    /// 
    /// Adds WebOptimizer to the specified  and enables CSS and JavaScript minification.
    /// 
    /// Collection of service descriptors
    public static void AddNopWebOptimizer(this IServiceCollection services)
    {
        var appSettings = Singleton.Instance;
        var woConfig = appSettings.Get();

        if (!woConfig.EnableCssBundling && !woConfig.EnableJavaScriptBundling)
        {
            services.AddScoped();
            return;
        }

        //add minification & bundling
        var cssSettings = new CssBundlingSettings
        {
            FingerprintUrls = false,
            Minify = woConfig.EnableCssBundling
        };

        var codeSettings = new CodeBundlingSettings
        {
            Minify = woConfig.EnableJavaScriptBundling,
            AdjustRelativePaths = false //disable this feature because it breaks function names that have "Url(" at the end
        };

        services.AddWebOptimizer(null, cssSettings, codeSettings);
        services.AddScoped();
    }

    /// 
    /// Add and configure default HTTP clients
    /// 
    /// Collection of service descriptors
    public static void AddNopHttpClients(this IServiceCollection services)
    {
        //default client
        services.AddHttpClient(NopHttpDefaults.DefaultHttpClient).WithProxy();

        //client to request current store
        services.AddHttpClient();

        //client to request nopCommerce official site
        services.AddHttpClient().WithProxy();

        //client to request reCAPTCHA service
        services.AddHttpClient().WithProxy();
    }
}