StarBlog Blog Development Notes (25): Image APIs and File Upload
Preface There are two styles for designing file-upload APIs. One is to set up a single upload endpoint for the entire project, and everywhere else that needs files only stores a reference ID. The other is for each place that needs files to manage its own files separately. Both have their pros and cons. This project chose the latter style: the article images and photo modules need both CRUD and batch import, so managing files separately is better. Image APIs Back to the main topic—first, an introduction to the image-related APIs. Image list CRUD is a given, and paginated viewing of the image list is also available, but because
目录8 个章节
Preface
There are two styles for designing file-upload APIs. One is to set up a single upload endpoint for the entire project, and everywhere else that needs files only stores a reference ID. The other is for each place that needs files to manage its own files separately. Both have their pros and cons. This project chose the latter style: the article images and photo modules need both CRUD and batch import, so managing files separately is better.
Image APIs
Back to the main topic—first, an introduction to the image-related APIs.
Image List
CRUD is a given, and paginated viewing of the image list is also available. However, since filtering isn’t implemented, I didn’t define a ViewModel as a parameter.
Controller code StarBlog.Web/Apis/Blog/PhotoController.cs
[HttpGet]
public ApiResponsePaged<Photo> GetList(int page = 1, int pageSize = 10) {
var paged = _photoService.GetPagedList(page, pageSize);
return new ApiResponsePaged<Photo> {
Pagination = paged.ToPaginationMetadata(),
Data = paged.ToList()
};
}
It shares the same image-list logic with the blog frontend, so this part was extracted into a service. The code is as follows
StarBlog.Web/Services/PhotoService.cs
public IPagedList<Photo> GetPagedList(int page = 1, int pageSize = 10) {
return _photoRepo.Select.OrderByDescending(a => a.CreateTime)
.ToList().ToPagedList(page, pageSize);
}
Single Image
Getting a single image is similar to getting an article: pass in an ID; if it’s not found, return 404; if it is found, return the image object
[HttpGet("{id}")]
public ApiResponse<Photo> Get(string id) {
var photo = _photoService.GetById(id);
return photo == null
? ApiResponse.NotFound($"图片 {id} 不存在")
: new ApiResponse<Photo> {Data = photo};
}
Image Thumbnails
In part 20 of this series, this project already implemented image display optimization. See: Developing a Blog Project with .NetCore: StarBlog - (20) Image Display Optimization
Besides the thumbnail functionality provided by the ImageSharp component, I also wrote another method for generating thumbnails. This method has two characteristics
- It is generated and returned directly in memory, without writing a cache file
- It generates Progressive JPEG format, which ImageSharp currently does not support, and can improve frontend loading speed
Controller code
[HttpGet("{id}/Thumb")]
public async Task<IActionResult> GetThumb(string id, [FromQuery] int width = 300) {
var data = await _photoService.GetThumb(id, width);
return new FileContentResult(data, "image/jpeg");
}
Service code
/// <summary>
/// 生成Progressive JPEG缩略图 (使用 MagickImage)
/// </summary>
/// <param name="width">设置为0则不调整大小</param>
public async Task<byte[]> GetThumb(string id, int width = 0) {
var photo = await _photoRepo.Where(a => a.Id == id).FirstAsync();
using (var image = new MagickImage(GetPhotoFilePath(photo))) {
image.Format = MagickFormat.Pjpeg;
if (width != 0) {
image.Resize(width, 0);
}
return image.ToByteArray();
}
}
This MagickImage is written in C++ and references different native libraries on different platforms. You need to add configuration in the csproj so that the corresponding dependency libraries are included when publishing. And it seems there are pitfalls on CentOS…
<!-- 复制 Magick 库 -->
<PropertyGroup>
<MagickCopyNativeWindows>true</MagickCopyNativeWindows>
<MagickCopyNativeLinux>true</MagickCopyNativeLinux>
<MagickCopyNativeMacOS>true</MagickCopyNativeMacOS>
</PropertyGroup>
Other APIs
There are a few more APIs that are more or less the same as those introduced earlier. Repeating them wouldn’t add much; readers can check the source code if needed.
- Delete image
- Set and unset featured
- Rebuild image library data (update dimensions and other data for each image; generally not needed)
- Batch import (already covered in part 9 of this series). See: Developing a Blog Project with .NetCore: StarBlog - (9) Batch Image Import
Image File Upload
This is also the add-image API
First, define the DTO
public class PhotoCreationDto {
/// <summary>
/// 作品标题
/// </summary>
[Required(ErrorMessage = "作品标题不能为空")]
public string Title { get; set; }
/// <summary>
/// 拍摄地点
/// </summary>
[Required(ErrorMessage = "拍摄地点不能为空")]
public string Location { get; set; }
}
Controller code
[Authorize]
[HttpPost]
public ApiResponse<Photo> Add([FromForm] PhotoCreationDto dto, IFormFile file) {
var photo = _photoService.Add(dto, file);
return !ModelState.IsValid
? ApiResponse.BadRequest(ModelState)
: new ApiResponse<Photo>(photo);
}
Because some extra data needs to be sent along with the upload, parameters must be passed via FormData, so the [FromForm] attribute is used to mark this dto parameter
A parameter of type IFormFile can be used to get the uploaded file
Below is the service code
public Photo Add(PhotoCreationDto dto, IFormFile photoFile) {
var photoId = GuidUtils.GuidTo16String();
var photo = new Photo {
Id = photoId,
Title = dto.Title,
CreateTime = DateTime.Now,
Location = dto.Location,
FilePath = Path.Combine("photography", $"{photoId}.jpg")
};
var savePath = Path.Combine(_environment.WebRootPath, "media", photo.FilePath);
// 如果超出最大允许的大小,则按比例缩小
const int maxWidth = 2000;
const int maxHeight = 2000;
using (var image = Image.Load(photoFile.OpenReadStream())) {
if (image.Width > maxWidth)
image.Mutate(a => a.Resize(maxWidth, 0));
if (image.Height > maxHeight)
image.Mutate(a => a.Resize(0, maxHeight));
image.Save(savePath);
}
// 保存文件
using (var fs = new FileStream(savePath, FileMode.Create)) {
photoFile.CopyTo(fs);
}
// 读取图片的尺寸等数据
photo = BuildPhotoData(photo);
return _photoRepo.Insert(photo);
}
Some processing is done on the image here. Setting those details aside, for the uploaded file the most critical part is really just a few lines of save code
using (var fs = new FileStream("savePath", FileMode.Create)) {
photoFile.CopyTo(fs);
}
This completes the file upload API.
Series Articles
- Developing a Blog Project Based on .NetCore - StarBlog - (1) Why Write Your Own Blog?
- Developing a Blog Project Based on .NetCore - StarBlog - (2) Environment Setup and Creating the Project
- Developing a Blog Project Based on .NetCore - StarBlog - (3) Model Design
- Developing a Blog Project Based on .NetCore - StarBlog - (4) Batch Importing Markdown Blog Posts
- Developing a Blog Project Based on .NetCore - StarBlog - (5) Starting to Build the Web Project
- Developing a Blog Project Based on .NetCore - StarBlog - (6) Page Development: Blog Post List
- Developing a Blog Project Based on .NetCore - StarBlog - (7) Page Development: Post Detail Page
- Developing a Blog Project Based on .NetCore - StarBlog - (8) Displaying Category Hierarchy
- Developing a Blog Project Based on .NetCore - StarBlog - (9) Batch Importing Images
- Developing a Blog Project Based on .NetCore - StarBlog - (10) Image Waterfall Layout
- Developing a Blog Project Based on .NetCore - StarBlog - (11) Implementing Visit Statistics
- Developing a Blog Project Based on .NetCore - StarBlog - (12) Dynamic Compilation of Razor Pages
- Developing a Blog Project Based on .NetCore - StarBlog - (13) Adding Friend Links
- Developing a Blog Project Based on .NetCore - StarBlog - (14) Implementing Theme Switching
- Developing a Blog Project Based on .NetCore - StarBlog - (15) Generating Random-Sized Images
- Developing a Blog Project Based on .NetCore - StarBlog - (16) Some New Features (Monitoring/Statistics/Configuration/Initialization)
- Developing a Blog Project Based on .NetCore - StarBlog - (17) Automatically Downloading External Images in Posts
- Developing a Blog Project Based on .NetCore - StarBlog - (18) Packaging and Uploading Local Typora Posts
- Developing a Blog Project Based on .NetCore - StarBlog - (19) Exploring Markdown Rendering Solutions
- Developing a Blog Project Based on .NetCore - StarBlog - (20) Image Display Optimization
- Developing a Blog Project Based on .NetCore - StarBlog - (21) Starting to Develop RESTFul APIs
- Developing a Blog Project Based on .NetCore - StarBlog - (22) Developing Blog Post APIs
- Developing a Blog Project Based on .NetCore - StarBlog - (23) Post List API: Pagination, Filtering, Search, and Sorting
- Developing a Blog Project Based on .NetCore - StarBlog - (24) Unified API Response Format
- Developing a Blog Project Based on .NetCore - StarBlog - (25) Image API and File Upload
_EOF_
StarBlog Blog Development Notes (25): Image APIs and File Upload
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
StarBlog博客开发笔记(3):模型设计
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 ... 基于.NetCore开发博客项目 StarBlog - 接口返回值包装 基于.NetCore开发博客项目 StarBlog - 上传
2022-04-22 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(23):文章列表接口分页、过滤、搜索、排序
前言 上一篇留的坑,火速补上。 在之前的第6篇中,已经有初步介绍,本文做一些补充,已经搞定这部分的同学可以快速跳过,基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表 对标准的WebApi来说,分页、过滤、搜索、排序是很常见的功能,既可以方便用户查看数据,又可以提升程序性能。 通用请求参数 定义一个类来作为通用的请求参数 列表接口通用的参数是这几个:PageSi
2023-01-02 · StarBlog博客开发笔记 · 约 1 分钟
基于.NetCore开发博客项目 StarBlog - (31) 发布和部署
部署?老生常谈。dotnet8的AOT吹得天花乱坠,StarBlog还没用上,只能选框架依赖或自包含。supervisor、docker、流水线,随便选,反正都一样麻烦。
2024-02-08 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记:上传图片功能
后端实现,前端的后面说 可以先在swagger里测试 ImageSharp组件文档:https://docs.sixlabors.com/articles/imagesharp/gettingstarted.html service private string GetPhotoFilePath(Photo photo) { return Path.Combine(_environment
2022-02-28 · StarBlog博客开发笔记 · 约 1 分钟
讨论
共 0 条加载评论中…