You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 5 Web API如何配置不同路由处理数字查询与图片传输

前言

为熟悉“Web APIs”相关技术,我使用Visual Studio 2022创建了一个ASP.NET Core Web API项目。

Template

我已将项目迁移至.NET 5版本。

目前MyControllers.cs中已编写处理Get与Post请求的相关代码。

我删除了Visual Studio自动生成的WeatherForecast.cs文件,同时在launchSettings.json中配置启动时浏览器默认跳转至MyController对应路由。

为练习依赖注入(Dependency Injection),我在解决方案中新增了一个名为TextRepository的类库项目。我此前通过另一个Visual Studio项目生成了包含0–99数字的文本文件,MyController.cs的Get method会读取该文本文件并返回内容,调用接口时浏览器即可展示对应数字列表,我同时定义了接口INumberRepository实现仓储层抽象。

text file

为巩固相关知识点,我额外创建了ImageRepository仓储,目标是扫描Pictures文件夹下的所有图片并存入List集合,为此我通过NuGet包管理器安装了System.Drawing.Common依赖。

Solution Explorer

咨询问题

我目前在浏览器端针对不同业务场景调用API时遇到问题:我希望保留https://localhost:44355/api/My 路由的原有功能,即调用现有Get方法在浏览器中展示数字列表;请问如何配置独立的路由链接实现图片数据传输?我希望明确API路由的实现方式:是否需要编写第二个Get函数?以及如何正确返回图片的字节数据?

补充说明:我已准备2张分辨率为1920像素×1080像素的测试图片用于功能验证。

blue and black images

WebApplication2 项目

Startup.cs 服务注册代码

services.AddScoped<TextRepository.INumberRepository, TextRepository.NumberTextFileRepository>();
services.AddScoped<ImageRepository.IImageTransferRepository, ImageRepository.ImageTransferRepository>();

MyController.cs 控制器代码

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using TextRepository;
using ImageRepository;

namespace WebApplication2.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MyController : ControllerBase
    {
        private readonly INumberRepository numberRepository;
        private readonly IImageTransferRepository imageRepository;
        public MyController(INumberRepository numberRepository, IImageTransferRepository imageTransferRepository)
        {
            this.numberRepository = numberRepository;
            this.imageRepository = imageTransferRepository;
        }
        [HttpGet]
        public ActionResult<List<int>> Get()
        {
            List<int> numbers = this.numberRepository.GetNumbers();
            List<System.Drawing.Bitmap> foundImages = this.imageRepository.GetImages();
            return Ok(numbers);
        }
        [HttpPost]
        public IActionResult Post([FromBody] DataTransferObject transferObject)
        {
            return Ok(transferObject.PassedString + $" {transferObject.Zahl}");
        }
    }
}

TextRepository 类库

TextFileParser.cs

using System;
using System.Collections.Generic;
using System.IO;

namespace TextRepository
{
    public class TextFileParser
    {
        public static List<int> ReadAllData(string path)
        {
            if (!File.Exists(path))
            {
                throw new FileNotFoundException(path);
            }
            string[] allLines = System.IO.File.ReadAllLines(path);
            if (allLines == null)
            {
                throw new Exception("War null");
            }
            if (allLines.Length < 1)
            {
                throw new Exception("Die Datei enthält 0 Zeilen.");
            }
            if (allLines.Length == 1)
            {
                return new List<int>();
            }
            List<int> numbers = new List<int>();
            for (int i = 1; i < allLines.Length; i++)
            {
                if (int.TryParse(allLines[i], out int result))
                {
                    numbers.Add(result);
                }
                else
                {
                    Console.WriteLine($"In der Textdatei, in Zeile {i + 1}, ist eine inkorrekte Zahl aufgetreten.");
                    continue;
                }
            }
            return numbers;
        }
    }
}

NumberTextFileRepository

using System.Collections.Generic;

namespace TextRepository
{
    public class NumberTextFileRepository : INumberRepository
    {
        public List<int> GetNumbers()
        {
            System.IO.DirectoryInfo Root = new System.IO.DirectoryInfo(System.IO.Directory.GetCurrentDirectory());
            return TextFileParser.ReadAllData(Root.Parent.FullName + "\\Textdatei.txt");
        }
    }
}

INumberRepository.cs

using System.Collections.Generic;

namespace TextRepository
{
    public interface INumberRepository
    {
        List<int> GetNumbers();
    }
}

ImageTransferRepository 类库

ImageTransferRepository.cs

using System;
using System.Collections.Generic;

namespace ImageRepository
{
    public class ImageTransferRepository : IImageTransferRepository
    {
        public List<System.Drawing.Bitmap> GetImages()
        {
            return ImageTransfer.GetImagesFromFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));
        }
    }
}

ImageTransfer.cs

using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace ImageRepository
{
    public class ImageTransfer
    {
        public static List<System.Drawing.Bitmap> GetImagesFromFolder(string path)
        {
            if (!Directory.Exists(path))
            {
                throw new FileNotFoundException(path);
            }

            List<FileInfo> fileInfos = new List<FileInfo>();
            fileInfos.AddRange(new DirectoryInfo(path).EnumerateFiles().Where(f => IsValidFile(f)));
            fileInfos = fileInfos.OrderBy(x => x.CreationTime).ToList(); // The newest image should be at the top of the list.

            return (from FileInfo file in fileInfos select new System.Drawing.Bitmap(file.FullName)).ToList();
        }
        private static bool IsValidFile(FileInfo File)
        {
            return File.FullName.ToLower().EndsWith(".bmp") ^ File.FullName.ToLower().EndsWith(".jpeg") ^ File.FullName.ToLower().EndsWith(".jpg") ^ File.FullName.ToLower().EndsWith(".png");
        }
    }
}

IImageTransferRepository.cs

using System.Collections.Generic;
using System.Drawing;

namespace ImageRepository
{
    public interface IImageTransferRepository
    {
        List<Bitmap> GetImages();
    }
}

解答

核心结论

  • 原有/api/My路由的数字列表功能完全不需要改动,只需要新增独立的Action处理图片请求即可,不会产生路由冲突
  • 必须新增独立的Get方法处理图片逻辑,ASP.NET Core的特性路由支持在同一个HttpGet标记下通过路由模板区分不同接口
  • 不要直接返回Bitmap对象,需要转成字节数组通过File方法返回,设置正确的MIME类型后浏览器可直接渲染

现有代码修正点

  1. 删除原有Get()方法中无意义的图片加载代码:List<System.Drawing.Bitmap> foundImages = this.imageRepository.GetImages();,这行代码会在每次请求数字列表时全量加载Pictures文件夹下的所有图片到内存,造成不必要的资源浪费。
  2. 修复ImageTransfer.cs中文件后缀判断的逻辑错误:原代码用异或运算符^判断后缀,逻辑存在隐患,替换为或运算符||。
  3. 调整仓储层返回值:不要直接返回Bitmap列表,Bitmap属于非托管资源,直接序列化会报错且存在内存泄漏风险,改为返回图片字节数组+对应MIME类型的结构。

具体实现步骤

第一步:调整ImageRepository层代码

修改IImageTransferRepository.cs,定义图片数据结构,更新接口约定:

using System.Collections.Generic;

namespace ImageRepository
{
    public class ImageData
    {
        public byte[] Content { get; set; }
        public string ContentType { get; set; }
        public string FileName { get; set; }
    }

    public interface IImageTransferRepository
    {
        List<ImageData> GetImages();
        ImageData GetImage(int index);
    }
}

修改ImageTransfer.cs,直接读取文件为字节数组,避免不必要的Bitmap资源加载(如果后续需要做图片裁剪、压缩等处理再使用Bitmap,处理完成后要立即调用Dispose()方法释放资源):

using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace ImageRepository
{
    public class ImageTransfer
    {
        public static List<ImageData> GetImagesFromFolder(string path)
        {
            if (!Directory.Exists(path))
            {
                throw new FileNotFoundException(path);
            }

            List<FileInfo> fileInfos = new List<FileInfo>();
            fileInfos.AddRange(new DirectoryInfo(path).EnumerateFiles().Where(f => IsValidFile(f)));
            fileInfos = fileInfos.OrderBy(x => x.CreationTime).ToList();

            List<ImageData> result = new List<ImageData>();
            foreach (var file in fileInfos)
            {
                string ext = file.Extension.ToLower();
                string contentType = ext switch
                {
                    ".png" => "image/png",
                    ".jpg" => "image/jpeg",
                    ".jpeg" => "image/jpeg",
                    ".bmp" => "image/bmp",
                    _ => "application/octet-stream"
                };
                result.Add(new ImageData
                {
                    Content = File.ReadAllBytes(file.FullName),
                    ContentType = contentType,
                    FileName = file.Name
                });
            }
            return result;
        }
        private static bool IsValidFile(FileInfo File)
        {
            string ext = File.Extension.ToLower();
            return ext == ".bmp" || ext == ".jpeg" || ext == ".jpg" || ext == ".png";
        }
    }
}

修改ImageTransferRepository.cs实现新接口,增加简单缓存避免重复读取磁盘:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ImageRepository
{
    public class ImageTransferRepository : IImageTransferRepository
    {
        private List<ImageData> _imagesCache;
        public List<ImageData> GetImages()
        {
            _imagesCache ??= ImageTransfer.GetImagesFromFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));
            return _imagesCache;
        }

        public ImageData GetImage(int index)
        {
            var images = GetImages();
            if (index < 0 || index >= images.Count) return null;
            return images[index];
        }
    }
}

第二步:在MyController中新增图片接口

原有数字列表和Post接口完全保留,新增两个Get接口处理图片请求,路由独立:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using TextRepository;
using ImageRepository;

namespace WebApplication2.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MyController : ControllerBase
    {
        private readonly INumberRepository numberRepository;
        private readonly IImageTransferRepository imageRepository;
        public MyController(INumberRepository numberRepository, IImageTransferRepository imageTransferRepository)
        {
            this.numberRepository = numberRepository;
            this.imageRepository = imageTransferRepository;
        }

        // 原有接口完全不动,GET /api/My 依旧返回数字列表
        [HttpGet]
        public ActionResult<List<int>> Get()
        {
            List<int> numbers = this.numberRepository.GetNumbers();
            return Ok(numbers);
        }

        // 新增:获取图片列表接口,路由为 GET /api/My/images
        [HttpGet("images")]
        public ActionResult<List<object>> GetImageList()
        {
            var images = this.imageRepository.GetImages();
            return Ok(images.Select((img, index) => new { Index = index, img.FileName }).ToList());
        }

        // 新增:获取单张图片字节接口,路由为 GET /api/My/images/{id}
        [HttpGet("images/{id:int}")]
        public IActionResult GetImageById(int id)
        {
            var targetImg = this.imageRepository.GetImage(id);
            if (targetImg == null) return NotFound();
            // 通过File方法返回字节数据,设置正确的MIME类型,浏览器可直接渲染
            return File(targetImg.Content, targetImg.ContentType);
        }

        [HttpPost]
        public IActionResult Post([FromBody] DataTransferObject transferObject)
        {
            return Ok(transferObject.PassedString + $" {transferObject.Zahl}");
        }
    }
}

验证方式

  • 访问https://localhost:44355/api/My:和原有逻辑一致,返回0-99的数字列表
  • 访问https://localhost:44355/api/My/images:返回Pictures文件夹下所有图片的索引和文件名
  • 访问https://localhost:44355/api/My/images/0:直接在浏览器中渲染第一张测试图片,/api/My/images/1对应第二张1920*1080测试图

注意事项

  • 不建议一次性返回所有图片的字节数据,单张按需请求可以减少带宽占用和加载时间
  • System.Drawing.Common仅支持Windows系统,如果后续需要部署到Linux环境,需要替换为SixLabors.ImageSharp等跨平台图像处理库
  • 如果图片文件体积较大,可以调整ASP.NET Core的响应缓冲配置,避免大文件传输时的内存占用过高问题

内容的提问来源于stack exchange,提问作者Daniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 14:48:35