示例#1
0
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context,
                                                   ResourceExecutionDelegate next)
        {
            Site site        = null;
            var  httpContext = context.HttpContext;
            // if we've already fetched it on this request it's present in Items
            int?siteId = null;

            if (httpContext.User.Identity.IsAuthenticated)
            {
                // if the user is authenticated, that is their site
                siteId = new UserClaimLookup(httpContext.User).GetId(ClaimType.SiteId);
            }
            else
            {
                string sitePath = context.RouteData.Values["sitePath"]?.ToString();
                // first check, did they use a sitePath giving them a specific site
                if (!string.IsNullOrEmpty(sitePath))
                {
                    site = await _siteLookupService.GetSiteByPathAsync(sitePath);

                    if (site != null)
                    {
                        siteId = site.Id;
                    }
                }
                // if not check if they already have one in their session
                if (siteId == null)
                {
                    siteId = httpContext.Session.GetInt32(SessionKey.SiteId);
                }
                // if not then resort to the default
                if (siteId == null)
                {
                    siteId = await _siteLookupService.GetDefaultSiteIdAsync();
                }
            }
            if (site == null)
            {
                site = await _siteLookupService.GetByIdAsync((int)siteId);
            }

            httpContext.Items[ItemKey.GoogleAnalytics] = site.GoogleAnalyticsTrackingId;
            httpContext.Items[ItemKey.SiteStage]       = _siteLookupService.GetSiteStageAsync(site);
            httpContext.Session.SetInt32(SessionKey.SiteId, (int)siteId);
            httpContext.Items[ItemKey.SiteId] = (int)siteId;

            await next();
        }
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context,
                                                   ResourceExecutionDelegate next)
        {
            Site site        = null;
            var  httpContext = context.HttpContext;
            // if we've already fetched it on this request it's present in Items
            int?siteId = null;

            if (httpContext.User.Identity.IsAuthenticated)
            {
                // if the user is authenticated, that is their site
                try
                {
                    siteId = _userContextProvider.GetId(httpContext.User, ClaimType.SiteId);
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Unable to get SiteId claim for user {httpContext.User.Identity.Name}: {ex.Message}");
                }
            }

            if (siteId == null)
            {
                string sitePath = context.RouteData.Values["sitePath"]?.ToString();
                // first check, did they use a sitePath giving them a specific site
                if (!string.IsNullOrEmpty(sitePath))
                {
                    site = await _siteLookupService.GetSiteByPathAsync(sitePath);

                    if (site != null)
                    {
                        siteId = site.Id;
                    }
                }
                // if not check if they already have one in their session
                if (siteId == null)
                {
                    siteId = httpContext.Session.GetInt32(SessionKey.SiteId);
                }
                // if not then resort to the default
                if (siteId == null)
                {
                    siteId = await _siteLookupService.GetDefaultSiteIdAsync();
                }
            }
            if (site == null)
            {
                site = await _siteLookupService.GetByIdAsync((int)siteId);
            }

            var siteStage = _siteLookupService.GetSiteStage(site);

            bool showChallenges = true;
            bool showEvents     = true;

            if (siteStage == SiteStage.BeforeRegistration)
            {
                // we might need to hide challenges and/or events since we're pre-registration
                // hide means that if the value is set then we want showChallenges to be false
                // hence == null rather than != null
                showChallenges = site.Settings
                                 .FirstOrDefault(_ => _.Key == SiteSettingKey.Challenges.HideUntilRegistrationOpen)?
                                 .Value == null;

                showEvents = site.Settings
                             .FirstOrDefault(_ => _.Key == SiteSettingKey.Events.HideUntilRegistrationOpen)?
                             .Value == null;
            }

            httpContext.Items[ItemKey.GoogleAnalytics] = site.GoogleAnalyticsTrackingId;
            httpContext.Items[ItemKey.SiteStage]       = siteStage;
            httpContext.Session.SetInt32(SessionKey.SiteId, (int)siteId);
            httpContext.Items[ItemKey.SiteId]         = (int)siteId;
            httpContext.Items[ItemKey.ShowChallenges] = showChallenges;
            httpContext.Items[ItemKey.ShowEvents]     = showEvents;

            // only check if the site.css and site.js have changed periodically by default and cache
            // the last modification time
            string siteCssCacheKey = $"s{siteId}.{CacheKey.SiteCss}";
            string siteJsCacheKey  = $"s{siteId}.{CacheKey.SiteJs}";

            var cssLastModified = await _cache.GetStringAsync(siteCssCacheKey);

            var jsLastModified = await _cache.GetStringAsync(siteJsCacheKey);

            // compute the appropriate cache time in minutes, default to 60 if not provided
            int cacheMinutes = 60;

            var cacheSiteCustomizationsMinutes = site.Settings
                                                 .FirstOrDefault(_ => _.Key == SiteSettingKey.Web.CacheSiteCustomizationsMinutes)?
                                                 .Value;

            if (cacheSiteCustomizationsMinutes != null)
            {
                if (!int.TryParse(cacheSiteCustomizationsMinutes, out cacheMinutes))
                {
                    _logger.LogError("Could not convert cache site customizations value to a number: {cacheSiteCustomizationsMinutes}", cacheSiteCustomizationsMinutes);
                }
            }

            // kill any cached values if the cache value was set to 0
            if (cacheMinutes == 0)
            {
                if (!string.IsNullOrEmpty(cssLastModified))
                {
                    await _cache.RemoveAsync(siteCssCacheKey);
                }
                if (!string.IsNullOrEmpty(jsLastModified))
                {
                    await _cache.RemoveAsync(siteJsCacheKey);
                }
            }

            // check for a cache miss and compute the appropriate last modification date
            if (string.IsNullOrEmpty(cssLastModified) ||
                string.IsNullOrEmpty(jsLastModified))
            {
                // file path to the site.css file
                string file = _pathResolver.ResolveContentFilePath(
                    Path.Combine($"site{siteId}", "styles", "site.css"));

                cssLastModified
                    = await UpdateCacheAsync(file, cacheMinutes, cssLastModified, siteCssCacheKey);

                // file path to the site.js file
                file = _pathResolver.ResolveContentFilePath(
                    Path.Combine($"site{siteId}", "scripts", "site.js"));

                jsLastModified
                    = await UpdateCacheAsync(file, cacheMinutes, jsLastModified, siteJsCacheKey);
            }

            // if we have values then set appropriate HttpContext.Items
            if (!string.IsNullOrEmpty(cssLastModified) ||
                !string.IsNullOrEmpty(jsLastModified))
            {
                string contentPath = _config[ConfigurationKey.ContentPath].StartsWith("/")
                    ? _config[ConfigurationKey.ContentPath]
                    : $"/{_config[ConfigurationKey.ContentPath]}";

                if (!string.IsNullOrEmpty(cssLastModified))
                {
                    httpContext.Items[ItemKey.SiteCss] = $"{contentPath}/site{siteId}/styles/site.css?v={cssLastModified}";
                }
                if (!string.IsNullOrEmpty(jsLastModified))
                {
                    httpContext.Items[ItemKey.SiteJs] = $"{contentPath}/site{siteId}/scripts/site.js?v={jsLastModified}";
                }
            }

            if (string.IsNullOrEmpty(httpContext.Session.GetString(SessionKey.CallItGroup)))
            {
                httpContext.Items[ItemKey.HouseholdTitle] = "Family";
            }
            else
            {
                httpContext.Items[ItemKey.HouseholdTitle] = "Group";
            }

            if (!string.IsNullOrWhiteSpace(site.ExternalEventListUrl))
            {
                httpContext.Items[ItemKey.ExternalEventListUrl] = site.ExternalEventListUrl;
            }

            await next();
        }
示例#3
0
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context,
                                                   ResourceExecutionDelegate next)
        {
            Site site = null;
            await context.HttpContext.Session.LoadAsync();

            // if we've already fetched it on this request it's present in Items
            int?siteId = null;

            if (context.HttpContext.User.Identity.IsAuthenticated)
            {
                // if the user is authenticated, that is their site
                try
                {
                    siteId = _userContextProvider.GetId(context.HttpContext.User,
                                                        ClaimType.SiteId);
                }
                catch (Exception ex)
                {
                    _logger.LogError("Unable to get SiteId claim for user {Name}: {Message}",
                                     context.HttpContext.User.Identity.Name,
                                     ex.Message);
                }
            }

            if (siteId == null)
            {
                string sitePath = context.RouteData.Values["sitePath"]?.ToString();
                // first check, did they use a sitePath giving them a specific site
                if (!string.IsNullOrEmpty(sitePath))
                {
                    site = await _siteLookupService.GetSiteByPathAsync(sitePath);

                    if (site != null)
                    {
                        siteId = site.Id;
                    }
                }
                // if not check if they already have one in their session
                if (siteId == null)
                {
                    siteId = context.HttpContext.Session.GetInt32(SessionKey.SiteId);
                }
                // if not then resort to the default
                if (siteId == null)
                {
                    siteId = await _siteLookupService.GetDefaultSiteIdAsync();
                }
            }
            if (site == null)
            {
                site = await _siteLookupService.GetByIdAsync((int)siteId);
            }

            var siteStage = _siteLookupService.GetSiteStage(site);

            // we might need to hide challenges and/or events based on system settings
            // the setting is to hide so the logic is == null rather than != null
            bool showChallenges = true;
            bool showEvents     = true;

            if (siteStage == SiteStage.RegistrationOpen ||
                siteStage == SiteStage.BeforeRegistration)
            {
                showEvents = site.Settings
                             .FirstOrDefault(_ => _.Key == SiteSettingKey
                                             .Events
                                             .HideUntilProgramOpen)?
                             .Value == null;
            }

            if (siteStage == SiteStage.BeforeRegistration)
            {
                showChallenges = site.Settings
                                 .FirstOrDefault(_ => _.Key == SiteSettingKey
                                                 .Challenges
                                                 .HideUntilRegistrationOpen)?
                                 .Value == null;

                if (!showEvents)
                {
                    // they might be hidden above
                    showEvents = site.Settings
                                 .FirstOrDefault(_ => _.Key == SiteSettingKey
                                                 .Events
                                                 .HideUntilRegistrationOpen)?
                                 .Value == null;
                }
            }

            context.HttpContext.Items[ItemKey.GoogleAnalytics] = site.GoogleAnalyticsTrackingId;
            context.HttpContext.Items[ItemKey.RouteId]         = context.RouteData.Values["id"];
            context.HttpContext.Items[ItemKey.SiteName]        = site?.Name
                                                                 ?? _config[ConfigurationKey.DefaultSiteName];
            context.HttpContext.Items[ItemKey.SiteStage] = siteStage;
            context.HttpContext.Session.SetInt32(SessionKey.SiteId, (int)siteId);
            context.HttpContext.Items[ItemKey.SiteId]         = (int)siteId;
            context.HttpContext.Items[ItemKey.ShowChallenges] = showChallenges;
            context.HttpContext.Items[ItemKey.ShowEvents]     = showEvents;
            context.HttpContext.Items[ItemKey.WebScheme]      = site.IsHttpsForced ? "https" : "http";

            // only check if the site.css and site.js have changed periodically by default and
            // cache the last modification time
            string siteCssCacheKey = $"s{siteId}.{CacheKey.SiteCss}";
            string siteJsCacheKey  = $"s{siteId}.{CacheKey.SiteJs}";

            var cssLastModified = await _cache.GetStringAsync(siteCssCacheKey);

            var jsLastModified = await _cache.GetStringAsync(siteJsCacheKey);

            // compute the appropriate cache time in minutes, default to 60 if not provided
            int cacheMinutes = 60;

            var cacheSiteCustomizationsMinutes = site.Settings
                                                 .FirstOrDefault(_ => _.Key == SiteSettingKey.Web.CacheSiteCustomizationsMinutes)?
                                                 .Value;

            if (cacheSiteCustomizationsMinutes != null &&
                !int.TryParse(cacheSiteCustomizationsMinutes, out cacheMinutes))
            {
                _logger.LogError("Could not convert cache site customizations value to a number: {cacheSiteCustomizationsMinutes}", cacheSiteCustomizationsMinutes);
            }

            // kill any cached values if the cache value was set to 0
            if (cacheMinutes == 0)
            {
                if (!string.IsNullOrEmpty(cssLastModified))
                {
                    await _cache.RemoveAsync(siteCssCacheKey);
                }
                if (!string.IsNullOrEmpty(jsLastModified))
                {
                    await _cache.RemoveAsync(siteJsCacheKey);
                }
            }

            // check for a cache miss and compute the appropriate last modification date
            if (string.IsNullOrEmpty(cssLastModified) ||
                string.IsNullOrEmpty(jsLastModified))
            {
                // file path to the site.css file
                string file = _pathResolver.ResolveContentFilePath(
                    Path.Combine($"site{siteId}", "styles", "site.css"));

                cssLastModified
                    = await UpdateCacheAsync(file, cacheMinutes, cssLastModified, siteCssCacheKey);

                // file path to the site.js file
                file = _pathResolver.ResolveContentFilePath(
                    Path.Combine($"site{siteId}", "scripts", "site.js"));

                jsLastModified
                    = await UpdateCacheAsync(file, cacheMinutes, jsLastModified, siteJsCacheKey);
            }

            // if we have values then set appropriate HttpContext.Items
            if (!string.IsNullOrEmpty(cssLastModified) ||
                !string.IsNullOrEmpty(jsLastModified))
            {
                string contentPath = _config[ConfigurationKey.ContentPath].StartsWith("/")
                    ? _config[ConfigurationKey.ContentPath]
                    : $"/{_config[ConfigurationKey.ContentPath]}";

                if (!string.IsNullOrEmpty(cssLastModified))
                {
                    context.HttpContext.Items[ItemKey.SiteCss]
                        = $"{contentPath}/site{siteId}/styles/site.css?v={cssLastModified}";
                }
                if (!string.IsNullOrEmpty(jsLastModified))
                {
                    context.HttpContext.Items[ItemKey.SiteJs]
                        = $"{contentPath}/site{siteId}/scripts/site.js?v={jsLastModified}";
                }
            }

            context.HttpContext.Items[ItemKey.HouseholdTitle]
                = string.IsNullOrEmpty(context
                                       .HttpContext
                                       .Session
                                       .GetString(SessionKey.CallItGroup))
                ? Annotations.Interface.Family
                : Annotations.Interface.Group;

            if (!string.IsNullOrWhiteSpace(site.ExternalEventListUrl))
            {
                context.HttpContext.Items[ItemKey.ExternalEventListUrl]
                    = site.ExternalEventListUrl;
            }

            var currentCulture = _userContextProvider.GetCurrentCulture();

            context.HttpContext.Items[ItemKey.ISOLanguageName]
                = currentCulture.TwoLetterISOLanguageName;

            int?activeUserId = _userContextProvider.GetContext().ActiveUserId;

            if (_l10nOptions.Value?.SupportedCultures.Count > 1)
            {
                var cookieCulture = context
                                    .HttpContext
                                    .Request
                                    .Cookies[CookieRequestCultureProvider.DefaultCookieName];

                if (currentCulture.Name == Culture.DefaultName)
                {
                    if (cookieCulture != null)
                    {
                        context
                        .HttpContext
                        .Response
                        .Cookies
                        .Delete(CookieRequestCultureProvider.DefaultCookieName);
                        if (activeUserId != null)
                        {
                            await _userService.UpdateCulture(null);
                        }
                    }
                }
                else
                {
                    // no cookie or new culture selected, reset cookie
                    context.HttpContext.Response.Cookies.Append(
                        CookieRequestCultureProvider.DefaultCookieName,
                        CookieRequestCultureProvider
                        .MakeCookieValue(new RequestCulture(currentCulture.Name)),
                        new CookieOptions {
                        Expires = DateTimeOffset.UtcNow.AddDays(14)
                    }
                        );
                    if (activeUserId != null)
                    {
                        await _userService.UpdateCulture(currentCulture.Name);
                    }
                }

                // generate list for drop-down
                var cultureList     = new List <SelectListItem>();
                var cultureHrefLang = new Dictionary <string, string>
                {
                    { "x-default", Culture.DefaultName }
                };
                foreach (var culture in _l10nOptions.Value.SupportedCultures)
                {
                    var text = culture.Parent != null
                        ? culture.Parent.NativeName
                        : culture.NativeName;
                    cultureList.Add(new SelectListItem(text, culture.Name));
                    if (!cultureHrefLang.Keys.Contains(culture.Name))
                    {
                        cultureHrefLang.Add(culture.Name, culture.Name);
                        if (culture.Parent != null &&
                            !cultureHrefLang.Keys.Contains(culture.Parent.Name))
                        {
                            cultureHrefLang.Add(culture.Parent.Name, culture.Parent.Name);
                        }
                    }
                }
                context.HttpContext.Items[ItemKey.HrefLang] = cultureHrefLang;
                context.HttpContext.Items[ItemKey.L10n]     = cultureList.OrderBy(_ => _.Text);
            }

            using (LogContext.PushProperty(LoggingEnrichment.ActiveUserId, activeUserId))
                using (LogContext.PushProperty(LoggingEnrichment.LanguageId,
                                               currentCulture?.TwoLetterISOLanguageName))
                    using (LogContext.PushProperty(LoggingEnrichment.RouteAction,
                                                   context.RouteData?.Values["action"]))
                        using (LogContext.PushProperty(LoggingEnrichment.RouteArea,
                                                       context.RouteData?.Values["area"]))
                            using (LogContext.PushProperty(LoggingEnrichment.RouteController,
                                                           context.RouteData?.Values["controller"]))
                                using (LogContext.PushProperty(LoggingEnrichment.RouteId,
                                                               context.RouteData?.Values["id"]))
                                    using (LogContext.PushProperty(LoggingEnrichment.SiteStage, siteStage))
                                    {
                                        await next();
                                    }
        }
示例#4
0
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context,
                                                   ResourceExecutionDelegate next)
        {
            Site site        = null;
            var  httpContext = context.HttpContext;
            // if we've already fetched it on this request it's present in Items
            int?siteId = null;

            if (httpContext.User.Identity.IsAuthenticated)
            {
                // if the user is authenticated, that is their site
                try
                {
                    siteId = _userContextProvider.GetId(httpContext.User, ClaimType.SiteId);
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Unable to get SiteId claim for user {httpContext.User.Identity.Name}: {ex.Message}");
                }
            }

            if (siteId == null)
            {
                string sitePath = context.RouteData.Values["sitePath"]?.ToString();
                // first check, did they use a sitePath giving them a specific site
                if (!string.IsNullOrEmpty(sitePath))
                {
                    site = await _siteLookupService.GetSiteByPathAsync(sitePath);

                    if (site != null)
                    {
                        siteId = site.Id;
                    }
                }
                // if not check if they already have one in their session
                if (siteId == null)
                {
                    siteId = httpContext.Session.GetInt32(SessionKey.SiteId);
                }
                // if not then resort to the default
                if (siteId == null)
                {
                    siteId = await _siteLookupService.GetDefaultSiteIdAsync();
                }
            }
            if (site == null)
            {
                site = await _siteLookupService.GetByIdAsync((int)siteId);
            }

            var siteStage = _siteLookupService.GetSiteStage(site);

            bool showChallenges = true;
            bool showEvents     = true;

            if (siteStage == SiteStage.BeforeRegistration)
            {
                // we might need to hide challenges and/or events since we're pre-registration
                // hide means that if the value is set then we want showChallenges to be false
                // hence == null rather than != null
                showChallenges = site.Settings
                                 .Where(_ => _.Key == SiteSettingKey.Challenges.HideUntilRegistrationOpen)
                                 .FirstOrDefault()?
                                 .Value == null;

                showEvents = site.Settings
                             .Where(_ => _.Key == SiteSettingKey.Events.HideUntilRegistrationOpen)
                             .FirstOrDefault()?
                             .Value == null;
            }

            httpContext.Items[ItemKey.GoogleAnalytics] = site.GoogleAnalyticsTrackingId;
            httpContext.Items[ItemKey.SiteStage]       = siteStage;
            httpContext.Session.SetInt32(SessionKey.SiteId, (int)siteId);
            httpContext.Items[ItemKey.SiteId]         = (int)siteId;
            httpContext.Items[ItemKey.ShowChallenges] = showChallenges;
            httpContext.Items[ItemKey.ShowEvents]     = showEvents;

            if (string.IsNullOrEmpty(httpContext.Session.GetString(SessionKey.CallItGroup)))
            {
                httpContext.Items[ItemKey.HouseholdTitle] = "Family";
            }
            else
            {
                httpContext.Items[ItemKey.HouseholdTitle] = "Group";
            }

            if (!string.IsNullOrWhiteSpace(site.ExternalEventListUrl))
            {
                httpContext.Items[ItemKey.ExternalEventListUrl] = site.ExternalEventListUrl;
            }

            await next();
        }
示例#5
0
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app,
                              IHostingEnvironment env,
                              ILoggerFactory loggerFactory,
                              IPathResolver pathResolver,
                              RoleService roleService,
                              SiteLookupService siteLookupService)
        {
            loggerFactory.AddSerilog();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseStatusCodePagesWithReExecute("/Error/Index/{0}");
            }

            var dbContext = app.ApplicationServices.GetService <Data.Context>();

            try
            {
                var pending = dbContext.GetPendingMigrations();
                if (pending != null && pending.Count() > 0)
                {
                    Log.Logger.Warning($"Applying {pending.Count()} database migrations, last is: {pending.Last()}");
                }
            }
            catch (Exception ex)
            {
                Log.Logger.Error($"Error looking up migrations to perform: {ex.Message}");
            }
            dbContext.Migrate();
            Task.Run(() => siteLookupService.GetDefaultSiteIdAsync()).Wait();
            Task.Run(() => roleService.SyncPermissionsAsync()).Wait();

            app.UseRequestLocalization();

            app.UseResponseCompression();

            // configure static files with 7 day cache
            app.UseStaticFiles(new StaticFileOptions()
            {
                OnPrepareResponse = _ =>
                {
                    var headers          = _.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
                    {
                        MaxAge = TimeSpan.FromDays(7)
                    };
                }
            });

            string contentPath = pathResolver.ResolveContentFilePath();

            if (!Directory.Exists(contentPath))
            {
                try
                {
                    Directory.CreateDirectory(contentPath);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Unable to create directory '{contentPath}' in {Directory.GetCurrentDirectory()}");
                    throw (ex);
                }
            }

            string pathString = pathResolver.ResolveContentPath();

            if (!pathString.StartsWith("/"))
            {
                pathString = "/" + pathString;
            }

            // configure /content with 7 day cache
            app.UseStaticFiles(new StaticFileOptions()
            {
                FileProvider      = new PhysicalFileProvider(contentPath),
                RequestPath       = new PathString(pathString),
                OnPrepareResponse = _ =>
                {
                    var headers          = _.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
                    {
                        MaxAge = TimeSpan.FromDays(7)
                    };
                }
            });

            app.UseSession();

            // set cookie authentication options
            var cookieAuthOptions = new CookieAuthenticationOptions
            {
                AuthenticationScheme  = Controllers.Authentication.SchemeGRACookie,
                LoginPath             = new PathString("/SignIn/"),
                AccessDeniedPath      = new PathString("/"),
                AutomaticAuthenticate = true,
                AutomaticChallenge    = true
            };

            // if there's a data protection path, set it up - for clustered/multi-server configs
            if (!string.IsNullOrEmpty(Configuration[ConfigurationKey.DataProtectionPath]))
            {
                string protectionPath = Configuration[ConfigurationKey.DataProtectionPath];
                cookieAuthOptions.DataProtectionProvider = DataProtectionProvider.Create(
                    new DirectoryInfo(Path.Combine(protectionPath, "cookies")));
            }

            app.UseCookieAuthentication(cookieAuthOptions);

            // sitePath is also referenced in GRA.Controllers.Filter.SiteFilter
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: null,
                    template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

                routes.MapRoute(
                    name: null,
                    template: "{sitePath}/Info/{stub}",
                    defaults: new { controller = "Info", action = "Index" },
                    constraints: new
                {
                    sitePath = new SiteRouteConstraint(app.ApplicationServices.GetRequiredService <Controllers.Base.ISitePathValidator>())
                });
                routes.MapRoute(
                    name: null,
                    template: "Info/{stub}",
                    defaults: new { controller = "Info", action = "Index" });

                routes.MapRoute(
                    name: null,
                    template: "{sitePath}/{controller}/{action}/{id?}",
                    defaults: new { controller = "Home", action = "Index" },
                    constraints: new
                {
                    sitePath = new SiteRouteConstraint(app.ApplicationServices.GetRequiredService <Controllers.Base.ISitePathValidator>())
                });
                routes.MapRoute(
                    name: null,
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseWebSockets(new WebSocketOptions
            {
                KeepAliveInterval = TimeSpan.FromSeconds(30)
            });

            app.Use(async(context, next) =>
            {
                if (context.WebSockets.IsWebSocketRequest)
                {
                    var handler = app.ApplicationServices.GetService <WebSocketHandler>();
                    await handler.Handle(context);
                }
                else
                {
                    await next();
                }
            });
        }