Beispiel #1
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>();

            services.Configure <PlatformOptions>(Configuration.GetSection("VirtoCommerce"));
            services.Configure <HangfireOptions>(Configuration.GetSection("VirtoCommerce:Jobs"));

            PlatformVersion.CurrentVersion = SemanticVersion.Parse(Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.ApplicationVersion);

            services.AddPlatformServices(Configuration);
            services.AddSecurityServices();

            var mvcBuilder = services.AddMvc().AddJsonOptions(options =>
            {
                //Next line needs to represent custom derived types in the resulting swagger doc definitions. Because default SwaggerProvider used global JSON serialization settings
                //we should register this converter globally.
                options.SerializerSettings.ContractResolver = new PolymorphJsonContractResolver();
                //Next line allow to use polymorph types as parameters in API controller methods
                options.SerializerSettings.Converters.Add(new PolymorphJsonConverter());
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                options.SerializerSettings.DateTimeZoneHandling  = DateTimeZoneHandling.Utc;
                options.SerializerSettings.NullValueHandling     = NullValueHandling.Ignore;
                options.SerializerSettings.Converters.Add(new StringEnumConverter());
                options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
                options.SerializerSettings.Formatting = Formatting.None;

                options.SerializerSettings.Error += (sender, args) =>
                {
                    // Expose any JSON serialization exception as HTTP error
                    throw new JsonException(args.ErrorContext.Error.Message);
                };
                options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
                options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
            }
                                                              )
                             .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            var modulesDiscoveryPath = Path.GetFullPath("Modules");

            services.AddModules(mvcBuilder, options =>
            {
                options.DiscoveryPath = modulesDiscoveryPath;
                options.ProbingPath   = "App_Data/Modules";
            });

            services.Configure <ExternalModuleCatalogOptions>(Configuration.GetSection("ExternalModules"));
            services.AddExternalModules();

            services.AddDbContext <SecurityDbContext>(options =>
            {
                options.UseSqlServer(Configuration.GetConnectionString("VirtoCommerce"));
                // Register the entity sets needed by OpenIddict.
                // Note: use the generic overload if you need
                // to replace the default OpenIddict entities.
                options.UseOpenIddict();
            });

            services.Configure <CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded    = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddSecurityServices(options =>
            {
                options.NonEditableUsers = new[] { "admin" };
            });

            services.AddIdentity <ApplicationUser, Role>()
            .AddEntityFrameworkStores <SecurityDbContext>()
            .AddDefaultTokenProviders();

            // Configure Identity to use the same JWT claims as OpenIddict instead
            // of the legacy WS-Federation claims it uses by default (ClaimTypes),
            // which saves you from doing the mapping in your authorization controller.
            services.Configure <IdentityOptions>(options =>
            {
                options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
                options.ClaimsIdentity.UserIdClaimType   = OpenIdConnectConstants.Claims.Subject;
                options.ClaimsIdentity.RoleClaimType     = OpenIdConnectConstants.Claims.Role;
            });


            // Register the OAuth2 validation handler.
            services.AddAuthentication().AddOAuthValidation();

            // Register the OpenIddict services.
            // Note: use the generic overload if you need
            // to replace the default OpenIddict entities.
            services.AddOpenIddict(options =>
            {
                // Register the Entity Framework stores.
                options.AddEntityFrameworkCoreStores <SecurityDbContext>();


                // Register the ASP.NET Core MVC binder used by OpenIddict.
                // Note: if you don't call this method, you won't be able to
                // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
                options.AddMvcBinders();

                // Enable the authorization, logout, token and userinfo endpoints.
                options.EnableTokenEndpoint("/connect/token")
                .EnableUserinfoEndpoint("/api/security/userinfo");

                // Note: the Mvc.Client sample only uses the code flow and the password flow, but you
                // can enable the other flows if you need to support implicit or client credentials.
                options.AllowPasswordFlow()
                .AllowRefreshTokenFlow()
                .AllowClientCredentialsFlow();

                // Make the "client_id" parameter mandatory when sending a token request.
                //options.RequireClientIdentification();

                // When request caching is enabled, authorization and logout requests
                // are stored in the distributed cache by OpenIddict and the user agent
                // is redirected to the same page with a single parameter (request_id).
                // This allows flowing large OpenID Connect requests even when using
                // an external authentication provider like Google, Facebook or Twitter.
                options.EnableRequestCaching();

                // During development, you can disable the HTTPS requirement.
                options.DisableHttpsRequirement();

                // Note: to use JWT access tokens instead of the default
                // encrypted format, the following lines are required:
                //
                options.UseJsonWebTokens();
                //TODO: Replace to X.509 certificate
                options.AddEphemeralSigningKey();
            });

            services.Configure <IdentityOptions>(Configuration.GetSection("IdentityOptions"));

            //always  return 401 instead of 302 for unauthorized  requests
            services.ConfigureApplicationCookie(options =>
            {
                options.Events.OnRedirectToLogin = context =>
                {
                    context.Response.StatusCode = 401;
                    return(Task.CompletedTask);
                };
            });


            services.AddAuthorization();
            // register the AuthorizationPolicyProvider which dynamically registers authorization policies for each permission defined in module manifest
            services.AddSingleton <IAuthorizationPolicyProvider, PermissionAuthorizationPolicyProvider>();
            //Platform authorization handler for policies based on permissions
            services.AddSingleton <IAuthorizationHandler, PermissionAuthorizationHandler>();

            // Add memory cache services
            services.AddMemoryCache();
            //Add Smidge runtime bundling library configuration
            services.AddSmidge(Configuration.GetSection("smidge"), new PhysicalFileProvider(modulesDiscoveryPath));
            services.AddSmidgeNuglify();

            // Register the Swagger generator
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Title       = "VirtoCommerce Solution REST API documentation",
                    Version     = "v1",
                    Description = "For this sample, you can use the"
                    ,
                    Contact = new Contact
                    {
                        Email = "*****@*****.**",
                        Name  = "Virto Commerce",
                        Url   = "http://virtocommerce.com"
                    }
                });
                c.TagActionsBy(api => api.GroupByModuleName(services));
                c.DocInclusionPredicate((docName, api) => true);
                c.DescribeAllEnumsAsStrings();
                c.IgnoreObsoleteProperties();
                c.IgnoreObsoleteActions();
                c.OperationFilter <FileResponseTypeFilter>();
                c.OperationFilter <OptionalParametersFilter>();
                c.OperationFilter <TagsFilter>();
                c.DocumentFilter <TagsFilter>();
                c.MapType <object>(() => new Schema {
                    Type = "object"
                });
                c.AddModulesXmlComments(services);
            });

            //Add SignalR for push notifications
            services.AddSignalR();


            var assetsProvider = Configuration.GetSection("Assets:Provider").Value;

            if (assetsProvider.EqualsInvariant("AzureBlobStorage"))
            {
                var azureBlobOptions = new AzureBlobContentOptions();
                Configuration.GetSection("Assets:AzureBlobStorage").Bind(azureBlobOptions);

                services.AddAzureBlobProvider(options =>
                {
                    options.ConnectionString = azureBlobOptions.ConnectionString;
                    options.CdnUrl           = azureBlobOptions.CdnUrl;
                });
            }
            else
            {
                var fileSystemBlobOptions = new FileSystemBlobContentOptions();
                Configuration.GetSection("Assets:FileSystem").Bind(fileSystemBlobOptions);

                services.AddFileSystemBlobProvider(options =>
                {
                    options.RootPath  = HostingEnvironment.MapPath(fileSystemBlobOptions.RootPath);
                    options.PublicUrl = fileSystemBlobOptions.PublicUrl;
                });
            }

            var hangfireOptions = new HangfireOptions();

            Configuration.GetSection("VirtoCommerce:Hangfire").Bind(hangfireOptions);
            if (hangfireOptions.JobStorageType == HangfireJobStorageType.SqlServer)
            {
                services.AddHangfire(config => config.UseSqlServerStorage(Configuration.GetConnectionString("VirtoCommerce")));
            }
            else
            {
                services.AddHangfire(config => config.UseMemoryStorage());
            }
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            services.AddResponseCaching();

            services.Configure <StorefrontOptions>(Configuration.GetSection("VirtoCommerce"));

            //The IHttpContextAccessor service is not registered by default
            //https://github.com/aspnet/Hosting/issues/793
            services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>();
            services.AddSingleton <IWorkContextAccessor, WorkContextAccessor>();
            services.AddSingleton <IUrlBuilder, UrlBuilder>();
            services.AddSingleton <IStorefrontUrlBuilder, StorefrontUrlBuilder>();

            services.AddSingleton <IStoreService, StoreService>();
            services.AddSingleton <ICurrencyService, CurrencyService>();
            services.AddSingleton <ISlugRouteService, SlugRouteService>();
            services.AddSingleton <IMemberService, MemberService>();
            services.AddSingleton <ICustomerOrderService, CustomerOrderService>();
            services.AddSingleton <IQuoteService, QuoteService>();
            services.AddSingleton <ISubscriptionService, SubscriptionService>();
            services.AddSingleton <ICatalogService, CatalogService>();
            services.AddSingleton <IInventoryService, InventoryService>();
            services.AddSingleton <IPricingService, PricingService>();
            services.AddSingleton <ITaxEvaluator, TaxEvaluator>();
            services.AddSingleton <IPromotionEvaluator, PromotionEvaluator>();
            services.AddSingleton <IDynamicContentEvaluator, DynamicContentEvaluator>();
            services.AddSingleton <IMarketingService, MarketingService>();
            services.AddSingleton <IStaticContentService, StaticContentService>();
            services.AddSingleton <IMenuLinkListService, MenuLinkListServiceImpl>();
            services.AddSingleton <IStaticContentItemFactory, StaticContentItemFactory>();
            services.AddSingleton <IApiChangesWatcher, ApiChangesWatcher>();
            services.AddSingleton <AssociationRecommendationsProvider>();
            services.AddSingleton <CognitiveRecommendationsProvider>();
            services.AddSingleton <IRecommendationProviderFactory, RecommendationProviderFactory>(provider => new RecommendationProviderFactory(provider.GetService <AssociationRecommendationsProvider>(), provider.GetService <CognitiveRecommendationsProvider>()));
            services.AddTransient <IQuoteRequestBuilder, QuoteRequestBuilder>();
            services.AddSingleton <IBlobChangesWatcher, BlobChangesWatcher>();
            services.AddTransient <ICartBuilder, CartBuilder>();
            services.AddTransient <ICartService, CartService>();
            services.AddTransient <AngularAntiforgeryCookieResultFilter>();
            services.AddTransient <AnonymousUserForStoreAuthorizationFilter>();

            //Register events framework dependencies
            services.AddSingleton(new InProcessBus());
            services.AddSingleton <IEventPublisher>(provider => provider.GetService <InProcessBus>());
            services.AddSingleton <IHandlerRegistrar>(provider => provider.GetService <InProcessBus>());

            //Cache
            services.AddSingleton <IStorefrontMemoryCache, StorefrontMemoryCache>();

            //Register platform API clients
            services.AddPlatformEndpoint(options =>
            {
                Configuration.GetSection("VirtoCommerce:Endpoint").Bind(options);
            });


            services.AddSingleton <ICountriesService, FileSystemCountriesService>();
            services.Configure <FileSystemCountriesOptions>(options =>
            {
                options.FilePath = HostingEnvironment.MapPath("~/countries.json");
            });

            var contentConnectionString = BlobConnectionString.Parse(Configuration.GetConnectionString("ContentConnectionString"));

            if (contentConnectionString.Provider.EqualsInvariant("AzureBlobStorage"))
            {
                var azureBlobOptions = new AzureBlobContentOptions();
                Configuration.GetSection("VirtoCommerce:AzureBlobStorage").Bind(azureBlobOptions);

                services.AddAzureBlobContent(options =>
                {
                    options.Container              = contentConnectionString.RootPath;
                    options.ConnectionString       = contentConnectionString.ConnectionString;
                    options.PollForChanges         = azureBlobOptions.PollForChanges;
                    options.ChangesPollingInterval = azureBlobOptions.ChangesPollingInterval;
                });
            }
            else
            {
                var fileSystemBlobOptions = new FileSystemBlobContentOptions();
                Configuration.GetSection("VirtoCommerce:FileSystemBlobStorage").Bind(fileSystemBlobOptions);
                services.AddFileSystemBlobContent(options =>
                {
                    options.Path = HostingEnvironment.MapPath(contentConnectionString.RootPath);
                });
            }

            //Identity overrides for use remote user storage
            services.AddScoped <IUserStore <User>, UserStoreStub>();
            services.AddScoped <IRoleStore <Role>, UserStoreStub>();
            services.AddScoped <UserManager <User>, CustomUserManager>();
            services.AddScoped <SignInManager <User>, CustomSignInManager>();

            //Resource-based authorization that requires API permissions for some operations
            services.AddSingleton <IAuthorizationHandler, CanImpersonateAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, CanReadContentItemAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, OnlyRegisteredUserAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, AnonymousUserForStoreAuthorizationHandler>();
            // register the AuthorizationPolicyProvider which dynamically registers authorization policies for each permission defined in the platform
            services.AddSingleton <IAuthorizationPolicyProvider, PermissionAuthorizationPolicyProvider>();
            //Storefront authorization handler for policy based on permissions
            services.AddSingleton <IAuthorizationHandler, PermissionAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, CanEditOrganizationResourceAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, CanAccessOrderAuthorizationHandler>();
            services.AddAuthorization(options =>
            {
                options.AddPolicy(CanImpersonateAuthorizationRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanImpersonateAuthorizationRequirement()));
                options.AddPolicy(CanReadContentItemAuthorizeRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanReadContentItemAuthorizeRequirement()));
                options.AddPolicy(CanEditOrganizationResourceAuthorizeRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanEditOrganizationResourceAuthorizeRequirement()));
                options.AddPolicy(OnlyRegisteredUserAuthorizationRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new OnlyRegisteredUserAuthorizationRequirement()));
                options.AddPolicy(AnonymousUserForStoreAuthorizationRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new AnonymousUserForStoreAuthorizationRequirement()));
                options.AddPolicy(CanAccessOrderAuthorizationRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanAccessOrderAuthorizationRequirement()));
            });


            var auth = services.AddAuthentication();

            var facebookSection = Configuration.GetSection("Authentication:Facebook");

            if (facebookSection.GetChildren().Any())
            {
                auth.AddFacebook(facebookOptions =>
                {
                    facebookSection.Bind(facebookOptions);
                });
            }
            var googleSection = Configuration.GetSection("Authentication:Google");

            if (googleSection.GetChildren().Any())
            {
                auth.AddGoogle(googleOptions =>
                {
                    googleSection.Bind(googleOptions);
                });
            }
            var githubSection = Configuration.GetSection("Authentication:Github");

            if (githubSection.GetChildren().Any())
            {
                auth.AddGitHub(GitHubAuthenticationOptions =>
                {
                    githubSection.Bind(GitHubAuthenticationOptions);
                });
            }
            var stackexchangeSection = Configuration.GetSection("Authentication:Stackexchange");

            if (stackexchangeSection.GetChildren().Any())
            {
                auth.AddStackExchange(StackExchangeAuthenticationOptions =>
                {
                    stackexchangeSection.Bind(StackExchangeAuthenticationOptions);
                });
            }

            //This line is required in order to use the old Identity V2 hashes to prevent rehashes passwords for platform users which login in the storefront
            //and it can lead to platform access denied for them. (TODO: Need to remove after platform migration to .NET Core)
            services.Configure <PasswordHasherOptions>(option => option.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2);
            services.Configure <IdentityOptions>(Configuration.GetSection("IdentityOptions"));
            services.Configure <CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, Configuration.GetSection("CookieAuthenticationOptions"));
            services.AddIdentity <User, Role>(options => { }).AddDefaultTokenProviders();

            services.Configure <CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded    = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            // The Tempdata provider cookie is not essential. Make it essential
            // so Tempdata is functional when tracking is disabled.
            services.Configure <CookieTempDataProviderOptions>(options =>
            {
                options.Cookie.IsEssential = true;
            });

            services.Replace(ServiceDescriptor.Transient <CookieAuthenticationHandler, CustomCookieAuthenticationHandler>());

            //Add Liquid view engine
            services.AddLiquidViewEngine(options =>
            {
                Configuration.GetSection("VirtoCommerce:LiquidThemeEngine").Bind(options);
            });

            var snapshotProvider = services.BuildServiceProvider();

            services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
            services.AddMvc(options =>
            {
                //Workaround to avoid 'Null effective policy causing exception' (on logout)
                //https://github.com/aspnet/Mvc/issues/7809
                //TODO: Try to remove in ASP.NET Core 2.2
                options.AllowCombiningAuthorizeFilters = false;

                // Thus we disable anonymous users based on "Store:AllowAnonymous" store option
                options.Filters.AddService <AnonymousUserForStoreAuthorizationFilter>();

                options.CacheProfiles.Add("Default", new CacheProfile()
                {
                    Duration     = (int)TimeSpan.FromHours(1).TotalSeconds,
                    VaryByHeader = "host"
                });
                options.CacheProfiles.Add("None", new CacheProfile()
                {
                    NoStore  = true,
                    Location = ResponseCacheLocation.None
                });

                options.Filters.AddService(typeof(AngularAntiforgeryCookieResultFilter));

                // To include only Api controllers to swagger document
                options.Conventions.Add(new ApiExplorerApiControllersConvention());

                // Use the routing logic of ASP.NET Core 2.1 or earlier:
                options.EnableEndpointRouting = false;
            }).AddJsonOptions(options =>
            {
                options.SerializerSettings.ContractResolver = new DefaultContractResolver()
                {
                    NamingStrategy = new CamelCaseNamingStrategy()
                };
                options.SerializerSettings.DefaultValueHandling  = DefaultValueHandling.Include;
                options.SerializerSettings.NullValueHandling     = NullValueHandling.Ignore;
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
                options.SerializerSettings.Converters.Add(new CartTypesJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new MoneyJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new CurrencyJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new OrderTypesJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new RecommendationJsonConverter(snapshotProvider.GetService <IRecommendationProviderFactory>()));
                //Force serialize MutablePagedList type as array, instead of dictionary
                options.SerializerSettings.Converters.Add(new MutablePagedListAsArrayJsonConverter(options.SerializerSettings));
                //Converter for providing back compatibility with old themes was used CustomerInfo type which has contained user and contact data in the single type.
                //May be removed when all themes will fixed to new User type with nested Contact property.
                options.SerializerSettings.Converters.Add(new UserBackwardCompatibilityJsonConverter(options.SerializerSettings));
            }).AddViewOptions(options =>
            {
                options.ViewEngines.Add(snapshotProvider.GetService <ILiquidViewEngine>());
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);


            //Register event handlers via reflection
            services.RegisterAssembliesEventHandlers(typeof(Startup));

            services.AddApplicationInsightsTelemetry();
            services.AddApplicationInsightsExtensions(Configuration);


            //https://github.com/aspnet/HttpAbstractions/issues/315
            //Changing the default html encoding options, to not encode non-Latin characters
            services.Configure <WebEncoderOptions>(options => options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));

            services.Configure <HstsOptions>(options =>
            {
                options.IncludeSubDomains = true;
                options.MaxAge            = TimeSpan.FromDays(30);
            });

            // Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info {
                    Title = "Storefront REST API documentation", Version = "v1"
                });
                c.DescribeAllEnumsAsStrings();
                c.IgnoreObsoleteProperties();
                c.IgnoreObsoleteActions();
                // To include 401 response type to actions that requires Authorization
                c.OperationFilter <AuthResponsesOperationFilter>();
                c.OperationFilter <OptionalParametersFilter>();
                c.OperationFilter <FileResponseTypeFilter>();
                // Workaround of problem with int default value for enum parameter: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/868
                c.ParameterFilter <EnumDefaultValueParameterFilter>();

                // To avoid errors with repeating type names
                c.CustomSchemaIds(type => (Attribute.GetCustomAttribute(type, typeof(SwaggerSchemaIdAttribute)) as SwaggerSchemaIdAttribute)?.Id ?? type.FriendlyId());
            });

            services.AddResponseCompression();
        }
Beispiel #3
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            services.AddResponseCaching();

            services.Configure <StorefrontOptions>(Configuration.GetSection("VirtoCommerce"));

            services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

            //The IHttpContextAccessor service is not registered by default
            //https://github.com/aspnet/Hosting/issues/793
            services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>();
            services.AddSingleton <IWorkContextAccessor, WorkContextAccessor>();
            services.AddSingleton <IUrlBuilder, UrlBuilder>();
            services.AddSingleton <IStorefrontUrlBuilder, StorefrontUrlBuilder>();

            services.AddSingleton <IStoreService, StoreService>();
            services.AddSingleton <ICurrencyService, CurrencyService>();
            services.AddSingleton <ISlugRouteService, SlugRouteService>();
            services.AddSingleton <IMemberService, MemberService>();
            services.AddSingleton <ICustomerOrderService, CustomerOrderService>();
            services.AddSingleton <IQuoteService, QuoteService>();
            services.AddSingleton <ISubscriptionService, SubscriptionService>();
            services.AddSingleton <ICatalogService, CatalogService>();
            services.AddSingleton <IInventoryService, InventoryService>();
            services.AddSingleton <IPricingService, PricingService>();
            services.AddSingleton <ITaxEvaluator, TaxEvaluator>();
            services.AddSingleton <IPromotionEvaluator, PromotionEvaluator>();
            services.AddSingleton <IMarketingService, MarketingService>();
            services.AddSingleton <IStaticContentService, StaticContentService>();
            services.AddSingleton <IMenuLinkListService, MenuLinkListServiceImpl>();
            services.AddSingleton <IStaticContentItemFactory, StaticContentItemFactory>();
            services.AddSingleton <IApiChangesWatcher, ApiChangesWatcher>();
            services.AddSingleton <AssociationRecommendationsProvider>();
            services.AddSingleton <CognitiveRecommendationsProvider>();
            services.AddSingleton <IRecommendationProviderFactory, RecommendationProviderFactory>(provider => new RecommendationProviderFactory(provider.GetService <AssociationRecommendationsProvider>(), provider.GetService <CognitiveRecommendationsProvider>()));
            services.AddTransient <IQuoteRequestBuilder, QuoteRequestBuilder>();
            services.AddSingleton <IBlobChangesWatcher, BlobChangesWatcher>();
            services.AddTransient <ICartBuilder, CartBuilder>();
            services.AddTransient <ICartService, CartService>();

            //Register events framework dependencies
            services.AddSingleton(new InProcessBus());
            services.AddSingleton <IEventPublisher>(provider => provider.GetService <InProcessBus>());
            services.AddSingleton <IHandlerRegistrar>(provider => provider.GetService <InProcessBus>());

            //Register platform API clients
            services.AddPlatformEndpoint(options =>
            {
                Configuration.GetSection("VirtoCommerce:Endpoint").Bind(options);
            });

            services.AddSingleton <ICountriesService, FileSystemCountriesService>();
            services.Configure <FileSystemCountriesOptions>(options =>
            {
                options.FilePath = HostingEnvironment.MapPath("~/countries.json");
            });

            var contentConnectionString = BlobConnectionString.Parse(Configuration.GetConnectionString("ContentConnectionString"));

            if (contentConnectionString.Provider.EqualsInvariant("AzureBlobStorage"))
            {
                var azureBlobOptions = new AzureBlobContentOptions();
                Configuration.GetSection("VirtoCommerce:AzureBlobStorage").Bind(azureBlobOptions);

                services.AddAzureBlobContent(options =>
                {
                    options.Container              = contentConnectionString.RootPath;
                    options.ConnectionString       = contentConnectionString.ConnectionString;
                    options.PollForChanges         = azureBlobOptions.PollForChanges;
                    options.ChangesPoolingInterval = azureBlobOptions.ChangesPoolingInterval;
                });
            }
            else
            {
                var fileSystemBlobOptions = new FileSystemBlobContentOptions();
                Configuration.GetSection("VirtoCommerce:FileSystemBlobStorage").Bind(fileSystemBlobOptions);
                services.AddFileSystemBlobContent(options =>
                {
                    options.Path = HostingEnvironment.MapPath(contentConnectionString.RootPath);
                });
            }

            //Identity overrides for use remote user storage
            services.AddScoped <IUserStore <User>, UserStoreStub>();
            services.AddScoped <IRoleStore <Role>, UserStoreStub>();
            services.AddScoped <UserManager <User>, CustomUserManager>();
            services.AddScoped <SignInManager <User>, CustomSignInManager>();

            //Resource-based authorization that requires API permissions for some operations
            services.AddSingleton <IAuthorizationHandler, CanImpersonateAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, CanReadContentItemAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, OnlyRegisteredUserAuthorizationHandler>();
            // register the AuthorizationPolicyProvider which dynamically registers authorization policies for each permission defined in the platform
            services.AddSingleton <IAuthorizationPolicyProvider, PermissionAuthorizationPolicyProvider>();
            //Storefront authorization handler for policy based on permissions
            services.AddSingleton <IAuthorizationHandler, PermissionAuthorizationHandler>();
            services.AddSingleton <IAuthorizationHandler, CanEditOrganizationResourceAuthorizationHandler>();
            services.AddAuthorization(options =>
            {
                options.AddPolicy(CanImpersonateAuthorizationRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanImpersonateAuthorizationRequirement()));
                options.AddPolicy(CanReadContentItemAuthorizeRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanReadContentItemAuthorizeRequirement()));
                options.AddPolicy(CanEditOrganizationResourceAuthorizeRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new CanEditOrganizationResourceAuthorizeRequirement()));
                options.AddPolicy(OnlyRegisteredUserAuthorizationRequirement.PolicyName,
                                  policy => policy.Requirements.Add(new OnlyRegisteredUserAuthorizationRequirement()));
            });


            var auth = services.AddAuthentication();

            var facebookSection = Configuration.GetSection("Authentication:Facebook");

            if (facebookSection.GetChildren().Any())
            {
                auth.AddFacebook(facebookOptions =>
                {
                    facebookSection.Bind(facebookOptions);
                });
            }
            var googleSection = Configuration.GetSection("Authentication:Google");

            if (googleSection.GetChildren().Any())
            {
                auth.AddGoogle(googleOptions =>
                {
                    googleSection.Bind(googleOptions);
                });
            }
            var githubSection = Configuration.GetSection("Authentication:Github");

            if (githubSection.GetChildren().Any())
            {
                auth.AddGitHub(GitHubAuthenticationOptions =>
                {
                    githubSection.Bind(GitHubAuthenticationOptions);
                });
            }
            var stackexchangeSection = Configuration.GetSection("Authentication:Stackexchange");

            if (stackexchangeSection.GetChildren().Any())
            {
                auth.AddStackExchange(StackExchangeAuthenticationOptions =>
                {
                    stackexchangeSection.Bind(StackExchangeAuthenticationOptions);
                });
            }

            //This line is required in order to use the old Identity V2 hashes to prevent rehashes passwords for platform users which login in the storefront
            //and it can lead to platform access denied for them. (TODO: Need to remove after platform migration to .NET Core)
            services.Configure <PasswordHasherOptions>(option => option.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2);
            services.Configure <IdentityOptions>(Configuration.GetSection("IdentityOptions"));
            services.Configure <CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, Configuration.GetSection("CookieAuthenticationOptions"));
            services.AddIdentity <User, Role>(options => { }).AddDefaultTokenProviders();

            services.Configure <CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded    = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            // The Tempdata provider cookie is not essential. Make it essential
            // so Tempdata is functional when tracking is disabled.
            services.Configure <CookieTempDataProviderOptions>(options =>
            {
                options.Cookie.IsEssential = true;
            });

            services.Replace(ServiceDescriptor.Transient <CookieAuthenticationHandler, CustomCookieAuthenticationHandler>());

            //Add Liquid view engine
            services.AddLiquidViewEngine(options =>
            {
                Configuration.GetSection("VirtoCommerce:LiquidThemeEngine").Bind(options);
            });

            var snapshotProvider = services.BuildServiceProvider();

            services.AddMvc(options =>
            {
                //Workaround to avoid 'Null effective policy causing exception' (on logout)
                //https://github.com/aspnet/Mvc/issues/7809
                //TODO: Try to remove in ASP.NET Core 2.2
                options.AllowCombiningAuthorizeFilters = false;


                options.CacheProfiles.Add("Default", new CacheProfile()
                {
                    Duration     = (int)TimeSpan.FromHours(1).TotalSeconds,
                    VaryByHeader = "host"
                });
            }).AddJsonOptions(options =>
            {
                options.SerializerSettings.DefaultValueHandling  = DefaultValueHandling.Include;
                options.SerializerSettings.NullValueHandling     = NullValueHandling.Ignore;
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
                options.SerializerSettings.Converters.Add(new CartTypesJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new MoneyJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new CurrencyJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new OrderTypesJsonConverter(snapshotProvider.GetService <IWorkContextAccessor>()));
                options.SerializerSettings.Converters.Add(new RecommendationJsonConverter(snapshotProvider.GetService <IRecommendationProviderFactory>()));
                //Converter for providing back compatibility with old themes was used CustomerInfo type which has contained user and contact data in the single type.
                //May be removed when all themes will fixed to new User type with nested Contact property.
                options.SerializerSettings.Converters.Add(new UserBackwardCompatibilityJsonConverter(options.SerializerSettings));
            }).AddViewOptions(options =>
            {
                options.ViewEngines.Add(snapshotProvider.GetService <ILiquidViewEngine>());
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);


            //Register event handlers via reflection
            services.RegisterAssembliesEventHandlers(typeof(Startup));

            services.AddApplicationInsightsTelemetry();
            services.AddApplicationInsightsExtensions(Configuration);
            services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
        }