- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我目前正在使用 .NET Core 创建 Multi-Tenancy Web 应用程序。并且正面临一个问题:
1) Web App 服务不同 浏览次数 和 逻辑 基于一组域名 .
2) View 是MVC View 并存储在中Azure Blob 存储
3)多个站点共享相同的 .NET Core MVC Controller 因此只有 Razor View 与小逻辑不同。
问题....
A) 这可能吗?我创建了一个 MiddleWare 来操作,但是我无法在上下文级别正确分配 FileProviders,因为文件提供程序应该依赖于域。
B) 或者,不是通过 FileProvider 思考和尝试,还有其他方法可以实现我想要实现的目标吗?
非常感谢!!!
最佳答案
你描述的任务不是很简单。这里的主要问题是不获取当前 HttpContext
,这可以通过 IHttpContextAccessor
轻松完成.您将面临的主要障碍是 Razor View Engine 大量使用缓存。
坏消息是请求域名不是这些缓存中键的一部分,只有 View 子路径属于一个键。因此,如果您请求带有子路径 /Views/Home/Index.cshtml
的 View 对于 domain1,它将被加载、编译和缓存。然后您请求具有相同路径但在域 2 内的 View 。您希望获得另一个 View ,特定于域 2,但 Razor 不在乎,它甚至不会调用您的自定义 FileProvider
,因为将使用缓存 View 。
Razor 基本上使用了 2 个缓存:
第一个是 ViewLookupCache
在 RazorViewEngine声明为:
protected IMemoryCache ViewLookupCache { get; }
RazorViewEngine
不是很容易使用具有域作为 key 一部分的 View 缓存。
RazorViewEngine
注册为单例并注入(inject)
PageResultExecutor
类也注册为单例。所以我们没有办法解析
RazorViewEngine
的新实例为每个域,以便它有自己的缓存。
ViewLookupCache
(尽管它没有 setter )到
IMemoryCache
的 Multi-Tenancy 实现.不使用 setter 设置属性
is possible然而,这是一个非常肮脏的黑客。在我向您提出这种解决方法的那一刻,上帝杀死了一只小猫。但是我没有看到更好的选择来绕过
RazorViewEngine
,对于这种情况,它不够灵活。
_precompiledViewLookup
在
RazorViewCompiler :
private readonly Dictionary<string, CompiledViewDescriptor> _precompiledViews;
RazorViewCompiler
的新实例对于每个域,因为它是由
IViewCompilerProvider
定义的我们可以以 Multi-Tenancy 的方式实现。
public class MultiTenantRazorViewEngine : RazorViewEngine
{
public MultiTenantRazorViewEngine(IRazorPageFactoryProvider pageFactory, IRazorPageActivator pageActivator, HtmlEncoder htmlEncoder, IOptions<RazorViewEngineOptions> optionsAccessor, RazorProject razorProject, ILoggerFactory loggerFactory, DiagnosticSource diagnosticSource)
: base(pageFactory, pageActivator, htmlEncoder, optionsAccessor, razorProject, loggerFactory, diagnosticSource)
{
// Dirty hack: setting RazorViewEngine.ViewLookupCache property that does not have a setter.
var field = typeof(RazorViewEngine).GetField("<ViewLookupCache>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(this, new MultiTenantMemoryCache());
// Asserting that ViewLookupCache property was set to instance of MultiTenantMemoryCache
if (ViewLookupCache .GetType() != typeof(MultiTenantMemoryCache))
{
throw new InvalidOperationException("Failed to set multi-tenant memory cache");
}
}
}
MultiTenantRazorViewEngine
源自
RazorViewEngine
和套
ViewLookupCache
MultiTenantMemoryCache
实例的属性.
public class MultiTenantMemoryCache : IMemoryCache
{
// Dictionary with separate instance of IMemoryCache for each domain
private readonly ConcurrentDictionary<string, IMemoryCache> viewLookupCache = new ConcurrentDictionary<string, IMemoryCache>();
public bool TryGetValue(object key, out object value)
{
return GetCurrentTenantCache().TryGetValue(key, out value);
}
public ICacheEntry CreateEntry(object key)
{
return GetCurrentTenantCache().CreateEntry(key);
}
public void Remove(object key)
{
GetCurrentTenantCache().Remove(key);
}
private IMemoryCache GetCurrentTenantCache()
{
var currentDomain = MultiTenantHelper.CurrentRequestDomain;
return viewLookupCache.GetOrAdd(currentDomain, domain => new MemoryCache(new MemoryCacheOptions()));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (var cache in viewLookupCache)
{
cache.Value.Dispose();
}
}
}
}
MultiTenantMemoryCache
是
IMemoryCache
的实现将不同域的缓存数据分开。现在与
MultiTenantRazorViewEngine
和
MultiTenantMemoryCache
我们在 Razor 的第一个缓存层添加了域名。
public class MultiTenantRazorPageFactoryProvider : IRazorPageFactoryProvider
{
// Dictionary with separate instance of IMemoryCache for each domain
private readonly ConcurrentDictionary<string, IRazorPageFactoryProvider> providers = new ConcurrentDictionary<string, IRazorPageFactoryProvider>();
public RazorPageFactoryResult CreateFactory(string relativePath)
{
var currentDomain = MultiTenantHelper.CurrentRequestDomain;
var factoryProvider = providers.GetOrAdd(currentDomain, domain => MultiTenantHelper.ServiceProvider.GetRequiredService<DefaultRazorPageFactoryProvider>());
return factoryProvider.CreateFactory(relativePath);
}
}
MultiTenantRazorPageFactoryProvider
创建
DefaultRazorPageFactoryProvider
的单独实例这样我们就有了
RazorViewCompiler
的独特实例对于每个域。现在我们已经将域名添加到 Razor 的第二个缓存层。
public static class MultiTenantHelper
{
public static IServiceProvider ServiceProvider { get; set; }
public static HttpContext CurrentHttpContext => ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
public static HttpRequest CurrentRequest => CurrentHttpContext.Request;
public static string CurrentRequestDomain => CurrentRequest.Host.Host;
}
MultiTenantHelper
提供对该请求的当前请求和域名的访问。不幸的是,我们必须将其声明为带有静态访问器的静态类
IHttpContextAccessor
. Razor 和静态文件中间件都不允许设置
FileProvider
的新实例对于每个请求(见下面
Startup
类)。这就是为什么
IHttpContextAccessor
未注入(inject)
FileProvider
并且作为静态属性访问。
public class MultiTenantFileProvider : IFileProvider
{
private const string BasePath = @"DomainsData";
public IFileInfo GetFileInfo(string subpath)
{
if (MultiTenantHelper.CurrentHttpContext == null)
{
if (String.Equals(subpath, @"/Pages/_ViewImports.cshtml") || String.Equals(subpath, @"/_ViewImports.cshtml"))
{
// Return FileInfo of non-existing file.
return new NotFoundFileInfo(subpath);
}
throw new InvalidOperationException("HttpContext is not set");
}
return CreateFileInfoForCurrentRequest(subpath);
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
var fullPath = GetPhysicalPath(MultiTenantHelper.CurrentRequestDomain, subpath);
return new PhysicalDirectoryContents(fullPath);
}
public IChangeToken Watch(string filter)
{
return NullChangeToken.Singleton;
}
private IFileInfo CreateFileInfoForCurrentRequest(string subpath)
{
var fullPath = GetPhysicalPath(MultiTenantHelper.CurrentRequestDomain, subpath);
return new PhysicalFileInfo(new FileInfo(fullPath));
}
private string GetPhysicalPath(string tenantId, string subpath)
{
subpath = subpath.TrimStart(Path.AltDirectorySeparatorChar);
subpath = subpath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
return Path.Combine(BasePath, tenantId, subpath);
}
}
MultiTenantFileProvider
的这个实现仅供引用。您应该将您的实现基于 Azure Blob 存储。您可以通过拨打
MultiTenantHelper.CurrentRequestDomain
获取当前请求的域名.你应该准备好
GetFileInfo()
方法将在应用程序启动期间从
app.UseMvc()
调用称呼。它发生在
/Pages/_ViewImports.cshtml
和
/_ViewImports.cshtml
导入所有其他 View 使用的命名空间的文件。自
GetFileInfo()
不在任何请求中调用,
IHttpContextAccessor.HttpContext
将返回
null
.所以你应该有自己的
_ViewImports.cshtml
副本对于每个域和这些初始调用返回
IFileInfo
与
Exists
设置为
false
.或保留
PhysicalFileProvider
在 Razor
FileProviders
集合,以便所有域都可以共享这些文件。在我的示例中,我使用了以前的方法。
ConfigureServices()
方法我们应该:
IRazorViewEngine
的实现与 MultiTenantRazorViewEngine
. IViewCompilerProvider
的实现使用 MultiTenantRazorViewEngine。 IRazorPageFactoryProvider
的实现与 MultiTenantRazorPageFactoryProvider
. FileProviders
收集并添加自己的 MultiTenantFileProvider
实例. public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var fileProviderInstance = new MultiTenantFileProvider();
services.AddSingleton(fileProviderInstance);
services.AddSingleton<IRazorViewEngine, MultiTenantRazorViewEngine>();
// Overriding singleton registration of IViewCompilerProvider
services.AddTransient<IViewCompilerProvider, RazorViewCompilerProvider>();
services.AddTransient<IRazorPageFactoryProvider, MultiTenantRazorPageFactoryProvider>();
// MultiTenantRazorPageFactoryProvider resolves DefaultRazorPageFactoryProvider by its type
services.AddTransient<DefaultRazorPageFactoryProvider>();
services.Configure<RazorViewEngineOptions>(options =>
{
// Remove instance of PhysicalFileProvider
options.FileProviders.Clear();
options.FileProviders.Add(fileProviderInstance);
});
}
Configure()
方法我们应该:
MultiTenantHelper.ServiceProvider
的集合实例. FileProvider
用于静态文件中间件到 MultiTenantFileProvider
的实例. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
MultiTenantHelper.ServiceProvider = app.ApplicationServices.GetRequiredService<IServiceProvider>();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = app.ApplicationServices.GetRequiredService<MultiTenantFileProvider>()
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
关于.net - 如何在域相关的 .NET Core 中更改/创建自定义 FileProvider(即一个 Web 应用程序为多个站点呈现逻辑提供服务),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47313612/
我有以下代码,它只适用于 Android 7 及以下版本。 Android 8 只显示一个空白文件,我手动打开 pdf 并且工作正常。 我有以下代码,这是 repo : private static
fileProvider 似乎没有将我的图像保存到外部存储。当我进入我的文件时,它创建了目录,但没有创建文件。我对 Android 编码相对陌生,所以请原谅我缺乏知识。 Fileprovider 位于
在我的 Android 应用程序中,我需要从 FileProvider 公开一些文件。如果没有加密,这非常简单:我只需将 FileProvider 添加到 manifest.xml 即可。
我有一个应用程序,我在其中捕获图像并将其设置为 ImageView,然后将其上传到服务器。但是,每当我捕获图像时,图像都不会显示,当我尝试上传图像时,我会得到 FileNotFoundExceptio
我有一个运行良好的 FileProvider,我可以将文件共享给任何应用程序,这是我的代码: 文件路径.xml: 设置共享 Intent : File f = new
我想使用 FileProvider访问 MyFirstApplication MySecondApplication 中的文件存储在内部目录中。我已经阅读了 FileProvider 的文档和示例。
我正在尝试做this tutorial . 我已遵循所有步骤,但由于错误说明而无法构建 Unresolved Class FileProvider 我已经安装了支持存储库。我目前的成绩如下: appl
我理解为什么 FileProvider 对于与另一个应用共享一个应用的私有(private)文件(应用的内部存储中的文件),同时控制权限很有用。 文档解释了如何使用它来共享外部存储(SD 卡等)中的文
我是 Android 编程的新手,学习过几个不同的教程。目标是将 pdf 文件从 Assets 文件夹复制到外部存储,然后在按下按钮打开 PDF 查看器时打开 Intent。我尝试使用 FilePro
我正在尝试测试 FileProvider tutorial . 它要求在 list 中包含: 在 list 中,我得到错误: Cannot resolve symbol FileProvider
我如何使用 FileProvider 提供辅助外部存储中的文件? ? 当前执行 FileProvider仅处理 ContextCompat.getExternalFilesDirs 返回的第一个目录
对于 Cache 目录中的文件,我可以在 xml 中添加它来设置 FileProvider 但是如果我将文件存储在外部缓存目录中,我无法获取外部缓存路径标签或类似的东西来设置 FilePro
我的 list 中有以下 FileProvider: 我在应用启动时遇到以下异常: java.lang.RuntimeException: Unable to
我无法打开下载文件夹中的任何文件。 我可以下载一个文件并保存在下载文件夹中: DownloadManager.Request request = new DownloadManager.Requ
我很好奇使用 android.support.v4.content.FileProvider 时的权限生命周期。 FileProvider 的文档说: Set the android:grantUri
我正在尝试让 Camera App 将输出存储到我的内部存储中。我还了解第三方应用程序无法访问我的应用程序的内部存储但是我们可以通过 FileProvider 公开内部目录来做到这一点。我已按照此处的
背景 针对 API 24 或更高版本,开发人员无需使用简单的“Uri.fromFile”命令,而是需要使用 FileProvider(或他们自己的 ContentProvider),以便让其他应用程序
A 正在开发 Android 项目。 我需要提供 ContentProvider 以提供对某些目录的访问。 FileProvider 这对我来说是一个很好的解决方案。 是否可以使用 FileProvi
这个问题已经有答案了: Permission Denial with File Provider through intent (4 个回答) 已关闭去年。 我正在尝试与 FileProvider 共
我制作了一个应用程序,该应用程序在一个文件夹(由同一应用程序创建)内创建一个 *.csv 文件,同时该文件位于 Android 的下载文件夹内。 该文件可以通过 ShareIntent 共享,问题是当
我是一名优秀的程序员,十分优秀!