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博客开发笔记(27):使用JWT保护接口
前言 这是StarBlog系列在2023年的第二篇更新😂 这几个月都在忙,更新变得很不勤快,但是拖着不更新我的心里更慌,很久没写,要开头就变得很难😑 说回正题,之前的文章里,我们已经把博客关键的接口都开发完成了,但还少了一个最关键的「认证授权」,少了这东西,网站就跟筛子一样,谁都可以来添加和删除数据,乱套了~ 关于「认证授权」的知识,会比较复杂,要学习这块的话,建议分几步: 基础概念 AspN
2023-05-03 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(5):开始搭建Web项目
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入 基于.NetCore开发博客项目 StarBl
2023-02-08 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(12):Razor页面动态编译
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入 基于.NetCore开发博客项目 StarBl
2022-06-19 · StarBlog博客开发笔记 · 约 1 分钟
基于.NetCore开发博客项目 StarBlog - (32) 第一期完结
本文记录一下 StarBlog 项目的当前状态与接下来 v2 版本的开发规划。
2024-12-02 · StarBlog博客开发笔记 · 约 1 分钟
讨论
共 0 条加载评论中…