public MultiTenantOAuthOptionsResolver( ISiteResolver siteResolver, MultiTenantOptions multiTenantOptions) { this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; }
/// <summary> /// Initializes a <see cref="TwitterAuthenticationMiddleware"/> /// </summary> /// <param name="next">The next middleware in the HTTP pipeline to invoke</param> /// <param name="dataProtectionProvider"></param> /// <param name="loggerFactory"></param> /// <param name="encoder"></param> /// <param name="sharedOptions"></param> /// <param name="options">Configuration options for the middleware</param> /// <param name="configureOptions"></param> public MultiTenantTwitterAuthenticationMiddleware( RequestDelegate next, IDataProtectionProvider dataProtectionProvider, ILoggerFactory loggerFactory, ISiteResolver siteResolver, ISiteRepository siteRepository, IOptions<MultiTenantOptions> multiTenantOptionsAccesor, IUrlEncoder encoder, IOptions<SharedAuthenticationOptions> sharedOptions, IOptions<TwitterAuthenticationOptions> options, ConfigureOptions<TwitterAuthenticationOptions> configureOptions = null) : base(next, options, loggerFactory, encoder, configureOptions) { //if (string.IsNullOrEmpty(Options.ConsumerSecret)) //{ // throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerSecret))); //} //if (string.IsNullOrEmpty(Options.ConsumerKey)) //{ // throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.ConsumerKey))); //} if (Options.Notifications == null) { Options.Notifications = new TwitterAuthenticationNotifications(); } if (Options.StateDataFormat == null) { var dataProtector = dataProtectionProvider.CreateProtector( typeof(TwitterAuthenticationMiddleware).FullName, Options.AuthenticationScheme, "v1"); Options.StateDataFormat = new SecureDataFormat<RequestToken>( Serializers.RequestToken, dataProtector, TextEncodings.Base64Url); } if (string.IsNullOrEmpty(Options.SignInScheme)) { Options.SignInScheme = sharedOptions.Options.SignInScheme; } if (string.IsNullOrEmpty(Options.SignInScheme)) { //throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "SignInScheme")); throw new ArgumentException("Resources.Exception_OptionMustBeProvided, SignInScheme"); } _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)); _httpClient.Timeout = Options.BackchannelTimeout; _httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB _httpClient.DefaultRequestHeaders.Accept.ParseAdd("*/*"); _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Twitter middleware"); _httpClient.DefaultRequestHeaders.ExpectContinue = false; this.loggerFactory = loggerFactory; this.siteResolver = siteResolver; multiTenantOptions = multiTenantOptionsAccesor.Options; siteRepo = siteRepository; }
private static bool TryEnsureTenantWwwRoot( IHostingEnvironment env, ISiteContext tenant, MultiTenantOptions options) { var siteFilesPath = Path.Combine(env.ContentRootPath, options.SiteUploadFilesRootFolderName); if (!Directory.Exists(siteFilesPath)) { try { Directory.CreateDirectory(siteFilesPath); } catch { return(false); } } string tenantFolder; if (options.UseRelatedSitesMode && !string.IsNullOrWhiteSpace(options.RelatedSiteAliasId)) { tenantFolder = Path.Combine(siteFilesPath, options.RelatedSiteAliasId); } else { tenantFolder = Path.Combine(siteFilesPath, tenant.AliasId); } if (!Directory.Exists(tenantFolder)) { try { Directory.CreateDirectory(tenantFolder); } catch { return(false); } } var tenantWwwRoot = Path.Combine(tenantFolder, options.SiteContentFolderName); if (!Directory.Exists(tenantWwwRoot)) { try { Directory.CreateDirectory(tenantWwwRoot); } catch { return(false); } } return(true); }
public MultiTenantOAuthOptionsResolver( //ISiteResolver siteResolver, IHttpContextAccessor contextAccessor, ITenantResolver <SiteSettings> siteResolver, MultiTenantOptions multiTenantOptions) { this.siteResolver = siteResolver; this.contextAccessor = contextAccessor; //site = currentSite; this.multiTenantOptions = multiTenantOptions; }
public MultiTenantTwitterOptionsResolver( TwitterOptions originalOptions, ISiteSettings currentSite, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; //this.siteResolver = siteResolver; site = currentSite; this.multiTenantOptions = multiTenantOptions; //siteRepo = siteRepository; }
public MultiTenantGoogleOptionsResolver( GoogleAuthenticationOptions originalOptions, ISiteResolver siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
public MultiTenantMicrosoftOptionsResolver( MicrosoftAccountOptions originalOptions, ISiteResolver siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
public static IApplicationBuilder UseCloudscribeCoreDefaultAuthentication( this IApplicationBuilder builder, ILoggerFactory loggerFactory, MultiTenantOptions multiTenantOptions, SiteContext tenant, CookieSecurePolicy applicationCookieSecure = CookieSecurePolicy.SameAsRequest ) { var useFolder = !multiTenantOptions.UseRelatedSitesMode && multiTenantOptions.Mode == cloudscribe.Core.Models.MultiTenantMode.FolderName && tenant.SiteFolderName.Length > 0; var externalCookieOptions = builder.SetupOtherCookies( AuthenticationScheme.External, multiTenantOptions.UseRelatedSitesMode, tenant); builder.UseCookieAuthentication(externalCookieOptions); var twoFactorRememberMeCookieOptions = builder.SetupOtherCookies( AuthenticationScheme.TwoFactorRememberMe, multiTenantOptions.UseRelatedSitesMode, tenant); builder.UseCookieAuthentication(twoFactorRememberMeCookieOptions); var twoFactorUserIdCookie = builder.SetupOtherCookies( AuthenticationScheme.TwoFactorUserId, multiTenantOptions.UseRelatedSitesMode, tenant); builder.UseCookieAuthentication(twoFactorUserIdCookie); //var cookieEvents = new CookieAuthenticationEvents(); var logger = loggerFactory.CreateLogger <SiteAuthCookieValidator>(); var cookieValidator = new SiteAuthCookieValidator(logger); var appCookieOptions = builder.SetupAppCookie( cookieValidator, AuthenticationScheme.Application, multiTenantOptions.UseRelatedSitesMode, tenant, applicationCookieSecure ); builder.UseCookieAuthentication(appCookieOptions); // known issue here is if a site is updated to populate the // social auth keys, it currently requires a restart so that the middleware gets registered // in order for it to work or for the social auth buttons to appear builder.UseSocialAuth(tenant, externalCookieOptions, useFolder); return(builder); }
public MultiTenantFacebookOptionsResolver( FacebookOptions originalOptions, ISiteResolver siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
public SiteIdentityOptionsResolver( IHttpContextAccessor httpContextAccessor, IOptions <MultiTenantOptions> multiTenantOptionsAccessor, SiteAuthCookieValidator siteValidator ) { this.httpContextAccessor = httpContextAccessor; this.cookieEvents = new CookieAuthenticationEvents(); this.siteValidator = siteValidator; multiTenantOptions = multiTenantOptionsAccessor.Value; }
public SiteIdentityOptionsResolver( IHttpContextAccessor httpContextAccessor, IOptions <MultiTenantOptions> multiTenantOptionsAccessor, IOptions <TokenOptions> tokenOptionsAccessor ) { this.httpContextAccessor = httpContextAccessor; multiTenantOptions = multiTenantOptionsAccessor.Value; tokenOptions = tokenOptionsAccessor.Value; }
/// <summary> /// Creates a new instance of <see cref="ThemeFileVersionProvider"/>. /// </summary> /// <param name="fileProvider">The file provider to get and watch files.</param> /// <param name="cache"><see cref="IMemoryCache"/> where versioned urls of files are cached.</param> /// <param name="requestPathBase">The base path for the current HTTP request.</param> public ThemeFileVersionProvider( MultiTenantOptions multiTenantOptions, IFileProvider fileProvider, IMemoryCache cache, PathString requestPathBase ) { _multiTenantOptions = multiTenantOptions; _fileProvider = fileProvider ?? throw new ArgumentNullException(nameof(fileProvider)); _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _requestPathBase = requestPathBase; }
/// <summary> /// this overload is deprecated, the ILoggerFactory is not used or needed here /// </summary> /// <param name="app"></param> /// <param name="loggerFactory"></param> /// <param name="multiTenantOptions"></param> /// <param name="sslIsAvailable"></param> /// <param name="applicationCookieSecure"></param> /// <returns></returns> public static IApplicationBuilder UseCloudscribeCore( this IApplicationBuilder app, ILoggerFactory loggerFactory, MultiTenantOptions multiTenantOptions, bool sslIsAvailable = false, CookieSecurePolicy applicationCookieSecure = CookieSecurePolicy.SameAsRequest ) { app.UseCloudscribeCore(multiTenantOptions, sslIsAvailable, applicationCookieSecure); return(app); }
public SiteIdentityOptionsResolver( IHttpContextAccessor httpContextAccessor, IOptions <MultiTenantOptions> multiTenantOptionsAccessor, IOptions <TokenOptions> tokenOptionsAccessor, IIdentityOptionsFactory identityOptionsFactory ) { _httpContextAccessor = httpContextAccessor; _multiTenantOptions = multiTenantOptionsAccessor.Value; _tokenOptions = tokenOptionsAccessor.Value; _optionsFactory = identityOptionsFactory; }
public static IApplicationBuilder UseCloudscribeCoreDefaultAuthentication( this IApplicationBuilder builder, ILoggerFactory loggerFactory, MultiTenantOptions multiTenantOptions, SiteContext tenant, bool sslIsAvailable = true, CookieSecurePolicy applicationCookieSecure = CookieSecurePolicy.SameAsRequest ) { var useFolder = !multiTenantOptions.UseRelatedSitesMode && multiTenantOptions.Mode == cloudscribe.Core.Models.MultiTenantMode.FolderName && tenant.SiteFolderName.Length > 0; var externalCookieOptions = builder.SetupOtherCookies( AuthenticationScheme.External, multiTenantOptions.UseRelatedSitesMode, tenant); builder.UseCookieAuthentication(externalCookieOptions); var twoFactorRememberMeCookieOptions = builder.SetupOtherCookies( AuthenticationScheme.TwoFactorRememberMe, multiTenantOptions.UseRelatedSitesMode, tenant); builder.UseCookieAuthentication(twoFactorRememberMeCookieOptions); var twoFactorUserIdCookie = builder.SetupOtherCookies( AuthenticationScheme.TwoFactorUserId, multiTenantOptions.UseRelatedSitesMode, tenant); builder.UseCookieAuthentication(twoFactorUserIdCookie); //var cookieEvents = new CookieAuthenticationEvents(); var logger = loggerFactory.CreateLogger <SiteAuthCookieValidator>(); var cookieValidator = new SiteAuthCookieValidator(logger); var appCookieOptions = builder.SetupAppCookie( cookieValidator, AuthenticationScheme.Application, multiTenantOptions.UseRelatedSitesMode, tenant, applicationCookieSecure ); builder.UseCookieAuthentication(appCookieOptions); builder.UseSocialAuth(tenant, externalCookieOptions, useFolder, sslIsAvailable); return(builder); }
public MultiTenantMicrosoftOptionsResolver( MicrosoftAccountOptions originalOptions, //ISiteResolver siteResolver, //ISiteRepository siteRepository, ISiteSettings currentSite, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; //this.siteResolver = siteResolver; site = currentSite; this.multiTenantOptions = multiTenantOptions; //siteRepo = siteRepository; }
public MultiTenantGoogleOptionsResolver( GoogleOptions originalOptions, //ISiteResolver siteResolver, //ISiteRepository siteRepository, ISiteSettings currentSite, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; //this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; //siteRepo = siteRepository; site = currentSite; }
public SiteAntiforgeryTokenStore( IOptions <MultiTenantOptions> multiTenantOptionsAccessor, IOptions <AntiforgeryOptions> optionsAccessor ) { if (optionsAccessor == null) { throw new ArgumentNullException(nameof(optionsAccessor)); } _options = optionsAccessor.Value; multiTenantOptions = multiTenantOptionsAccessor.Value; }
public MultiTenantEndpointRouter( Dictionary <string, EndpointName> pathToNameMap, IdentityServerOptions options, IEnumerable <EndpointMapping> mappings, IOptions <MultiTenantOptions> multiTenantOptionsAccessor, ILogger <MultiTenantEndpointRouter> logger ) { _pathToNameMap = pathToNameMap; _options = options; _mappings = mappings; _logger = logger; multiTenantOptions = multiTenantOptionsAccessor.Value; }
public MultiTenantEndpointRouter( IEnumerable <Endpoint> endpoints, IdentityServerOptions options, IOptions <MultiTenantOptions> multiTenantOptionsAccessor, ILogger <MultiTenantEndpointRouter> logger ) { //_pathToNameMap = pathToNameMap; _endpoints = endpoints; _options = options; //_mappings = mappings; _logger = logger; multiTenantOptions = multiTenantOptionsAccessor.Value; }
public MultiTenantTwitterHandler( HttpClient httpClient, ISiteResolver siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions, ILoggerFactory loggerFactory) { _httpClient = httpClient; log = loggerFactory.CreateLogger <MultiTenantTwitterHandler>(); this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
public MultiTenantFacebookOptionsResolver( FacebookOptions originalOptions, //ISiteResolver siteResolver, //IHttpContextAccessor contextAccessor, //ITenantResolver<SiteSettings> siteResolver, ISiteSettings currentSite, //ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions) { this.originalOptions = originalOptions; //this.siteResolver = siteResolver; //this.contextAccessor = contextAccessor; this.multiTenantOptions = multiTenantOptions; //siteRepo = siteRepository; site = currentSite; }
public static IApplicationBuilder UseCloudscribeCore( this IApplicationBuilder app, ILoggerFactory loggerFactory, MultiTenantOptions multiTenantOptions, bool sslIsAvailable = false, CookieSecurePolicy applicationCookieSecure = CookieSecurePolicy.SameAsRequest ) { app.UseCloudscribeCommonStaticFiles(); app.UseMultitenancy <cloudscribe.Core.Models.SiteContext>(); app.UsePerTenant <cloudscribe.Core.Models.SiteContext>((ctx, builder) => { if (!string.IsNullOrWhiteSpace(ctx.Tenant.ForcedCulture) && !string.IsNullOrWhiteSpace(ctx.Tenant.ForcedUICulture)) { var tenantLocalization = new RequestLocalizationOptions(); tenantLocalization.DefaultRequestCulture = new RequestCulture(culture: ctx.Tenant.ForcedCulture, uiCulture: ctx.Tenant.ForcedUICulture); tenantLocalization.SupportedCultures = new[] { new CultureInfo(ctx.Tenant.ForcedCulture) }; tenantLocalization.SupportedUICultures = new[] { new CultureInfo(ctx.Tenant.ForcedUICulture) }; builder.UseRequestLocalization(tenantLocalization); } // custom 404 and error page - this preserves the status code (ie 404) if (multiTenantOptions.Mode != cloudscribe.Core.Models.MultiTenantMode.FolderName || string.IsNullOrEmpty(ctx.Tenant.SiteFolderName)) { builder.UseStatusCodePagesWithReExecute("/oops/error/{0}"); } else { builder.UseStatusCodePagesWithReExecute("/" + ctx.Tenant.SiteFolderName + "/oops/error/{0}"); } // resolve static files from wwwroot folders within themes and within sitefiles builder.UseSiteAndThemeStaticFiles(loggerFactory, multiTenantOptions, ctx.Tenant); //builder.UseAuthentication(); }); app.UseCloudscribeEnforceSiteRulesMiddleware(); app.UseAuthentication(); return(app); }
private static bool TryEnsureTenantWwwRoot(ISiteContext tenant, MultiTenantOptions options) { var siteFilesPath = Path.Combine(Directory.GetCurrentDirectory(), options.SiteFilesFolderName); if (!Directory.Exists(siteFilesPath)) { try { Directory.CreateDirectory(siteFilesPath); } catch { return(false); } } var tenantFolder = Path.Combine(siteFilesPath, tenant.AliasId); if (!Directory.Exists(tenantFolder)) { try { Directory.CreateDirectory(tenantFolder); } catch { return(false); } } var tenantWwwRoot = Path.Combine(tenantFolder, options.SiteContentFolderName); if (!Directory.Exists(tenantWwwRoot)) { try { Directory.CreateDirectory(tenantWwwRoot); } catch { return(false); } } return(true); }
public MultiTenantGoogleHandler( HttpClient httpClient, ISiteResolver siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions, ILoggerFactory loggerFactory) : base( httpClient, loggerFactory, new MultiTenantOAuthOptionsResolver(siteResolver, multiTenantOptions) ) { log = loggerFactory.CreateLogger <MultiTenantGoogleHandler>(); this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
public MultiTenantTwitterHandler( HttpClient httpClient, //ISiteResolver siteResolver, IHttpContextAccessor contextAccessor, ITenantResolver <SiteSettings> siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions, ILoggerFactory loggerFactory) { _httpClient = httpClient; log = loggerFactory.CreateLogger <MultiTenantTwitterHandler>(); this.contextAccessor = contextAccessor; this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
public MultiTenantMicrosoftAccountHandler( HttpClient httpClient, //ISiteResolver siteResolver, IHttpContextAccessor contextAccessor, ITenantResolver <SiteSettings> siteResolver, ISiteRepository siteRepository, MultiTenantOptions multiTenantOptions, ILoggerFactory loggerFactory) : base( httpClient, loggerFactory, new MultiTenantOAuthOptionsResolver(contextAccessor, siteResolver, multiTenantOptions) ) { log = loggerFactory.CreateLogger <MultiTenantMicrosoftAccountHandler>(); this.contextAccessor = contextAccessor; this.siteResolver = siteResolver; this.multiTenantOptions = multiTenantOptions; siteRepo = siteRepository; }
/// <summary> /// Exec <see cref="ITenantIdenityRetrieveInterceptor.IdentityRetrieveFailed(TenantIdentityRetrieveContext, CancellationToken)"/> of all registed interceptors. /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task IdentityFalid(object context, MultiTenantOptions options, CancellationToken cancellationToken = default) { var idInterceptors = _interceptors.Find <ITenantIdenityRetrieveInterceptor>(); if (idInterceptors.Count() == 0) { return; } var retrieveContext = new TenantIdentityRetrieveContext() { Context = context, Options = options }; foreach (var idInterceptor in idInterceptors) { await idInterceptor.IdentityRetrieveFailed(retrieveContext, cancellationToken); } }
/// <summary> /// Exec <see cref="ITenantInfoRetrieveInterceptor.BeforeGetTenantInfo(TenantInfoRetrieveContext, CancellationToken)"/> of all registed interceptors. /// </summary> /// <param name="tenantId"></param> /// <param name="context"></param> /// <param name="options"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task BeforeGetTenantInfo(string tenantId, object context, MultiTenantOptions options, CancellationToken cancellationToken = default) { var infoInterceptors = _interceptors.Find <ITenantInfoRetrieveInterceptor>(); if (infoInterceptors.Count() == 0) { return; } var retrieveContext = new TenantInfoRetrieveContext() { TenantId = tenantId, Context = context, Options = options }; foreach (var infoInterceptor in infoInterceptors) { await infoInterceptor.BeforeGetTenantInfo(retrieveContext, cancellationToken); } }
public static IApplicationBuilder UseSiteAndThemeStaticFiles( this IApplicationBuilder builder, ILoggerFactory loggerFactory, MultiTenantOptions multiTenantOptions, SiteContext tenant ) { var env = builder.ApplicationServices.GetRequiredService <IHostingEnvironment>(); var tenantSegment = ""; if (multiTenantOptions.Mode == MultiTenantMode.FolderName && !string.IsNullOrEmpty(tenant.SiteFolderName)) { tenantSegment = tenant.SiteFolderName + "/"; } var themeName = tenant.Theme; bool themeFound = false; if (multiTenantOptions.UserPerSiteThemes) { // this allows serving static files from the "wwwroot" folder beneath the theme folder // we don't want to serve the view files over http, but we can serve css and js etc from the static folder beneath the theme folder // without serving theme views // TODO: we could possibly use the GzipMappingFileProvider here to handle .gz // but probably would want a config setting to make that optional if (!string.IsNullOrEmpty(themeName)) { var themePath = Path.Combine(env.ContentRootPath, multiTenantOptions.SiteFilesFolderName, tenant.AliasId, multiTenantOptions.SiteThemesFolderName, themeName, multiTenantOptions.ThemeStaticFilesFolderName); if (Directory.Exists(themePath)) { builder.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(themePath), RequestPath = new PathString("/" + tenantSegment + themeName) }); themeFound = true; } } } if (!themeFound && multiTenantOptions.UseSharedThemes) { if (!string.IsNullOrEmpty(themeName)) { var themePath = Path.Combine(env.ContentRootPath, multiTenantOptions.SharedThemesFolderName, themeName, multiTenantOptions.ThemeStaticFilesFolderName); if (Directory.Exists(themePath)) { builder.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(themePath) , RequestPath = new PathString("/" + tenantSegment + themeName) }); } } } if (multiTenantOptions.UserPerSiteWwwRoot) { // this allows serving static files from /sitefiles/[aliasid]/wwwroot // so that files can be isolated per tenant // but if using related sites mode and RelatedAliasId is specified then all sites use the same var folderExists = TryEnsureTenantWwwRoot(env, tenant, multiTenantOptions); string aliasId = tenant.AliasId; var usingAlias = false; if (multiTenantOptions.UseRelatedSitesMode && !string.IsNullOrWhiteSpace(multiTenantOptions.RelatedSiteAliasId)) { aliasId = multiTenantOptions.RelatedSiteAliasId; usingAlias = true; } if (folderExists) { var siteFilesPath = Path.Combine(env.ContentRootPath, multiTenantOptions.SiteUploadFilesRootFolderName, aliasId, multiTenantOptions.SiteContentFolderName); if (string.IsNullOrEmpty(tenantSegment)) // root tenant or hostname tenant { builder.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(siteFilesPath) //,RequestPath = new PathString("/files") }); } else if (!usingAlias) { builder.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(siteFilesPath), RequestPath = new PathString("/" + tenant.SiteFolderName) }); } } } return(builder); }