【问题标题】:Image provider in ASP.NET Core 3.1ASP.NET Core 3.1 中的图像提供程序
【发布时间】:2020-01-30 06:53:32
【问题描述】:

我有一个使用 C# 编写的名为“存储”的基于 ASP.NET Core 的 Web API。这个应用程序旨在为许多带有图像的网络应用程序提供服务。使用我拥有的其他应用程序可以使用 HTTP 请求从存储中请求图像。

为了从辅助/消费应用与我的存储应用交互,我创建了一个自定义文件提供程序实现,允许我像这样访问这些图像

public class MyCustomImagesProvider : IFileProvider
{
    private readonly IHttpClientFactory ClientFactory;

    public MyCustomImagesProvider(IHttpClientFactory clientFactory)
    {
        ClientFactory = clientFactory;
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        try
        {
            var client = ClientFactory.CreateClient("client-name");

            Uri url = new Uri("https://storage.mydomain.com", $"api/files/get?name={subpath}");

            var result = client.GetAsync(url).Result;

            result.EnsureSuccessStatusCode();
            string filename = Path.GetFileName(subpath);
            DateTime lastModifiedAt = (result.Content.Headers.LastModified?.DateTime) ?? DateTime.Now;
            var stream = result.Content.ReadAsStreamAsync().Result;

            return new MyCustomFileInfo(stream, filename, lastModifiedAt);
        }
        catch
        {
        }

        return new MyCustomFileInfo
        {
            Exists = false
        };
    }

    public IDirectoryContents GetDirectoryContents(string subpath) => throw new NotImplementedException();
    public IChangeToken Watch(string filter) => throw new NotImplementedException();
}

我的一些应用只需要图像的缩略图,而其他应用需要全尺寸和/或图像的缩略图。稍后,对于某些应用程序,我什至可能还需要一个中等大小的图像(可能是同一个图像的 4 个不同大小。)磁盘空间限制正在成为一个问题,因为我必须将同一个图像复制到多个文件中(每个大小一个)以容纳我的所有应用程序。

换句话说,如果我有一个名为“abc_fullsize.jpg”的文件,那么我需要创建和存储“abc_thumbnail.jpg”和需要大量磁盘空间的“abc_midside.jpg”。

问题

动态创建这些图像而不是在办公桌上创建它们是否有效?

我想我会有一个ThumbnailImageProviderMidsizeImageProviderFullsizeImageProvider,每个都会发送存储应用所需的最大尺寸,存储应用会从桌面读取全尺寸图像,然后回复调整大小的图像。这是ThumbnailImageProvider的示例

public class ThumbnailImageProvider : IFileProvider
{
    private readonly IHttpClientFactory ClientFactory;

    public ThumbnailImageProvider(IHttpClientFactory clientFactory)
    {
        ClientFactory = clientFactory;
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        try
        {
            var client = ClientFactory.CreateClient("client-name");
            // Note the max-width=250 parameter here would tell the API that I need this image recreated on the fly with max width of 250.
            Uri url = new Uri("https://storage.mydomain.com", $"api/files/get?name={subpath}&maxWidth=250");

            var result = client.GetAsync(url).Result;

            result.EnsureSuccessStatusCode();
            string filename = Path.GetFileName(subpath);
            DateTime lastModifiedAt = (result.Content.Headers.LastModified?.DateTime) ?? DateTime.Now;
            var stream = result.Content.ReadAsStreamAsync().Result;

            return new MyCustomFileInfo(stream, filename, lastModifiedAt);
        }
        catch
        {
        }

        return new MyCustomFileInfo
        {
            Exists = false
        };
    }

    public IDirectoryContents GetDirectoryContents(string subpath) => throw new NotImplementedException();
    public IChangeToken Watch(string filter) => throw new NotImplementedException();
}

api/files/get 端点上的操作方法将响应以下操作

[HttpGet("get")]
public IActionResult Get(string name, int? maxWidth)
{
    IFileInfo fileInfo = FileProvider.GetFileInfo(name);

    if (!fileInfo.Exists)
    {
        return NotFound();
    }

    if(maxWidth.HasValue && maxWidth > 1)
    {
        Image image = ImageProcessor.GetResizedImage(fileInfo.CreateReadStream(), maxWidth.Value, out ImageFormat imageFormat);

        using var memoryStream = new MemoryStream();
        image.Save(memoryStream, imageFormat);

        string extension = Path.GetExtension(fileInfo.Name);
        string rawFilename = Path.GetFileNameWithoutExtension(fileInfo.Name);

        string filename = $"{rawFilename}-{maxWidth}{extension}";

        return File(memoryStream, "image/jpeg", filename, fileInfo.LastModified, null);
    }

    return File(fileInfo.CreateReadStream(), "image/jpeg", fileInfo.Name, fileInfo.LastModified, null);
}

【问题讨论】:

    标签: c# http asp.net-core asp.net-core-mvc asp.net-core-webapi


    【解决方案1】:

    我认为您最好将您的逻辑应用于中间件。看看aspnet的静态文件中间件是如何工作的:

    https://github.com/dotnet/aspnetcore/blob/19d2f6124f5d04859e350d1f5a01e994e14ef1ce/src/Middleware/StaticFiles/src/StaticFileMiddleware.cs

    您的方法有两个问题:

    1. IFileProvider 不支持异步。你在做网络 调用哪个是异步的主要候选者
    2. 调整图像大小需要花费 相当数量的cpu。一个拥有 300.000 篇文章(大约 相同数量的图片)需要几天的高 CPU 在将所有内容缓存到磁盘之前使用两个 4 核 Web 服务器。

    所以我的建议是在磁盘上缓存图像(或使用输出缓存时的 blob 存储)-> https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-3.1

    【讨论】:

    • 关于 (1),我不确定为什么 IFileProvider 不支持异步。但我的一部分人认为这里可能不需要它,因为每个 HTTP 请求都会被并行评估,所以它不会阻塞主线程......不过我在这里可能是错的。关于(2),我所有的应用程序都将是图像重的。因此每个页面加载可能有 20-50 个 HTTP 缩略图请求。存储应用程序在具有 SSD 和 10GB RAM 的 5 核 VPS 上运行。您是否建议使用app.UseResponseCaching() 来减少我的服务器必须调整大小但动态图像很好的图像数量?
    • 动态调整大小是好的,只要您可以缓存它。最简单的是使用响应缓存,但缺点是我您的应用程序重新启动您的缓存丢失。所以磁盘缓存更加健壮。两者结合很好,可以处理很多请求。
    猜你喜欢
    • 2021-01-06
    • 1970-01-01
    • 2018-05-26
    • 2020-07-23
    • 2020-12-30
    • 1970-01-01
    • 1970-01-01
    • 2011-09-27
    • 2021-03-02
    相关资源
    最近更新 更多