使用.NET Core 3.1通过静态URL为数据库中存储的图像提供服务

目录

介绍

解决方案

创建解决方案

添加Swagger

添加实体框架

实现用于上传图像的API端点

实现中间件以从数据库中检索图像

如何测试

可能的改进


在实现支持上传二进制内容(例如PDF文件或Avatar图像)的Web应用程序时,需要做出的常见选择是将二进制内容存储在文件系统,云存储中还是数据库中。本文不会讨论什么情况下最好的解决方案。但是,直接将二进制内容数据存储在数据库中具有强大的优势。其中之一就是能够将其他内容与二进制内容一起存储在单个数据库事务中。将所有内容数据放在一个位置还可以简化备份的还原。

介绍

今天,我们将创建一个具有单个端点的简单API,用于上传图像。为此,我们将在Visual Studio 2019中使用ASP.NET Core Web应用程序/ API模板。为了简化测试,我们还将在我们的解决方案中添加SwaggerEntity框架。结果是我们可以使用如下端点来上传图像:

解决方案

要继续阅读本文,您需要在本地环境中安装Visual Studio 2019.NET Core 3.1

创建解决方案

第一步是在Visual Studio中创建一个新项目,然后选择ASP.NET Core Web Application模板。给项目取一个新名称,我选择Get-images-from-db-by-url,然后在以下屏幕上选择API ”,如下面的屏幕快照所示:

使用此模板可以得到一个简单的API,该API返回一些表示天气预报的JSON数据。现在,我们将把这个基本解决方案变成更有趣的东西。

首先,我们更改Get-images-from-db-by-url项目的属性设置,以便在调试时为您显示应用程序的根目录/,而不是/weatherforecast” URL。您可以在此处找到此设置:

weatherforecast在启动浏览器复选框右侧的字段中,用空字符串替换。这将使Visual Studio使用/” URL而不是/weatherforecast” URL启动应用程序。

现在,我们将做两件事,然后再开始介绍有趣的东西:

  1. 添加Swagger可以简化将图像上传到后端的过程。
  2. 添加实体框架以支持从数据库写入/检索数据。

添加Swagger

Swagger是一种工具,可将自动生成的文档和用户界面添加到您的API。通过在包管理器控制台中运行以下命令,将Swagger安装到项目中。

Install-Package Swashbuckle.AspNetCore

之后,您现在需要对Startup.cs文件进行两项更改。您的Startup.cs文件中应该已经有一个用于ConfigureServices的方法。添加services.AddSwaggerGen()到它里面。结果应如下所示:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSwaggerGen();
}

在名为Configure的方法中,应添加以下两行:

app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    c.RoutePrefix = string.Empty;
});

现在,您可以尝试调试该应用程序,希望您会看到呈现给您的醒目的UI界面。

添加实体框架

通过在包管理器控制台中运行以下两个命令,将实体框架添加到项目中:

Install-Package Microsoft.EntityFrameworkCore.Sqlite
Install-Package Microsoft.EntityFrameworkCore.Tools

然后创建一个名为Models的新文件夹,并在该文件夹中创建一个新的Image类。在这种情况下,该image类将代表单个图像。在这里,我们需要具有的属性Id,一个表示二进制图像数据的data属性和一个存储文件suffix的属性。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Get_images_from_db_by_url.Models
{
    public class Image
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Key]
        public Guid Id { get; set; }
        public byte[] Data { get; set; }
        public string Suffix { get; set; }
    }
}

在完成实体框架的设置之前,我们还需要创建一个DBContext类。将以下类添加到项目中:

using Microsoft.EntityFrameworkCore;
using Get_images_from_db_by_url.Models;

namespace Get_images_from_db_by_url
{
    public class EFContext : DbContext
    {
        public DbSet<img /> Images { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder options) => 
                                       options.UseSqlite("Data Source=database.db");
    }
}

现在,运行以下两个命令在您的解决方案中创建初始迁移和database.db文件:

Add-Migration InitialCreate
Update-Database

实现用于上传图像的API端点

希望该解决方案现在应该可以正确构建。在继续之前,我们应该稍微清理一下现有的解决方案。首先,将Controllers/WeatherForecastController.cs文件重命名为Controllers/ImageController.cs,同时更新类的名称以匹配新文件名。然后删除文件WeatherForecast.cs

ImageController内容替换为以下内容:

using System;
using System.IO;
using System.Threading.Tasks;
using Get_images_from_db_by_url.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Get_images_from_db_by_url.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ImageController : ControllerBase
    {

        [HttpPost("image")]
        public async Task<guid> Image(IFormFile image)
        {
            using (var db = new EFContext())
            {
                using (var ms = new MemoryStream())
                {
                    image.CopyTo(ms);
                    var img = new Image()
                    {
                        Suffix = Path.GetExtension(image.FileName),
                        Data = ms.ToArray()
                    };

                    db.Images.Add(img);
                    await db.SaveChangesAsync();
                    return img.Id;
                }
            }
        }
    }
}

该方法Image以一个IFormFile作为参数,使用内存流对象读取它并将结果存储在数据库中,然后将Id(生成的GUID)返回给用户。

现在,按下试用按钮后,您的swagger界面应如下所示:

在上传图像之后,您应该得到一个GUID作为回报:

实现中间件以从数据库中检索图像

.NET 4.X中,您将使用HTTP处理程序来实现此功能,但是在.NET Core中,处理程序的概念已全部消失。我们想要的是拦截请求,对其进行分析,如果请求符合某些预定义的条件,我们希望将图像从数据库中返回给用户。

我们要处理的条件是URL路径是否包含/dynamic/images/,以及所请求的资源的扩展名为pngjpg还是gif

在项目中创建一个新文件夹,并将其命名为Middleware。在这个新文件夹中,创建两个新类,DynamicImageMiddlewareDynamicImageProvider

DynamicImageProvider的内容应为以下内容:

using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Get_images_from_db_by_url.Middleware
{
    public class DynamicImageProvider
    {
        private readonly RequestDelegate next;
        public IServiceProvider service;
        private string pathCondition = "/dynamic/images/";

        public DynamicImageProvider(RequestDelegate next)
        {
            this.next = next;
        }

        private static readonly string[] suffixes = new string[] {
            ".png",
            ".jpg",
            ".gif"
        };

        private bool IsImagePath(PathString path)
        {
            if (path == null || !path.HasValue)
                return false;

            return suffixes.Any
                   (x => path.Value.EndsWith(x, StringComparison.OrdinalIgnoreCase));
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                var path = context.Request.Path;
                if (IsImagePath(path) && path.Value.Contains(pathCondition))
                {
                    byte[] buffer = null;
                    var id = Guid.Parse(Path.GetFileName(path).Split(".")[0]);
                    
                    using (var db = new EFContext())
                    {
                        var imageFromDb = 
                           await db.Images.SingleOrDefaultAsync(x => x.Id == id);
                        buffer = imageFromDb.Data;
                    }

                    if (buffer != null && buffer.Length > 1)
                    {
                        context.Response.ContentLength = buffer.Length;
                        context.Response.ContentType = "image/" + 
                                            Path.GetExtension(path).Replace(".","");
                        await context.Response.Body.WriteAsync(buffer, 0, buffer.Length);
                    }
                    else
                        context.Response.StatusCode = 404;
                }
            }
            finally
            {
                if (!context.Response.HasStarted)
                    await next(context);
            }
        }
    }
}

这里没有复杂的事情发生。方法Invoke(HttpContext context)将通过HttpContext.NET Core框架执行,您可以访问requestresponse对象。

我们只对URL路径包含 /dynamic/images且扩展名为pngjpggif的请求感兴趣。

我们从路径中检索图像GUID,为数据库检索图像,然后设置响应内容。由于响应对象需要一个流,因此在这种情况下,我们需要利用内存流。

本节finally的内容非常重要,因为您不想在写入响应后将响应发送到链中的任何其他中间件。但是,如果您没有这样做,则希望它使用next(context)方法继续其正常路线。

下一步是添加DymaicImageMiddleware.cs文件的内容:

using Microsoft.AspNetCore.Builder;

namespace Get_images_from_db_by_url.Middleware
{
    public static class DynamicImageMiddleware
    {
        public static IApplicationBuilder UseDynamicImageMiddleware
                                          (this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<dynamicimageprovider>();
        }
    }
}

该文件包含在Startup.cs文件中使用的扩展方法。

Startup.cs文件中使用扩展方法,在Configure方法中使用扩展名方法,对于我来说,在app.UseRouting()行之后。

app.UseDynamicImageMiddleware();

如何测试

您可以按照以下步骤进行测试:

  1. 使用Swagger UIImage POST端点上传图像
  2. 记下上传文件的扩展名和API返回的GUID
  3. 执行/dynamic/images/[GUID].[EXTENSION]就像我的例子一样:https://localhost:44362/dynamic/images/36400564-b28d-45a3-a259-0afbb8c5ec51.png,希望您将从静态URL获得看起来像是静态图像的东西,但实际上,该图像是从数据库中的一张表中提供的。

可能的改进

本文介绍的这种方法的优点之一是,通过静态URL提供的图像可在浏览器中缓存。您可以通过向response对象添加Expires标头来控制缓存,如下所示:

string ExpireDate = DateTime.UtcNow.AddDays(10)
                            .ToString("ddd, dd MMM yyyy HH:mm:ss", 
                                      System.Globalization.CultureInfo.InvariantCulture);
context.Response.Headers.Add("Expires", ExpireDate + " GMT");

此外,如果需要,可以在服务器端使用内存缓存。一个常见的用例是通过在查询字符串中提供图像的长度和宽度来支持在服务器端调整图像的大小。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值