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博客开发笔记(1):项目介绍
前言 OK,我也来造轮子了 博客系统从一开始用WordPress,再到后来用hexo、hugo之类的静态博客生成放github托管,一直在折腾 折腾是为了更好解决问题,最终还是打算自己花时间搞一个好了 本系列文章将记录博客的开发过程~ 后端基于最新的.Net6和Asp.Net Core框架,遵循RESTFul接口规范,前端基于Vue+ElementUI开发,可作为 .Net Core 入门项目学习
2022-04-05 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(11):实现访问统计
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入 基于.NetCore开发博客项目 StarBl
2022-06-20 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记:部署
Nginx 反向代理 后端配置中间件 app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); Nginx配置 server { listen
2022-12-05 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(29):添加RSS订阅功能
RSS 的对立面是算法推荐,像微信公众号、知乎、微博、今日头条等平台。 且不说算法推送平台广告多,迁移麻烦的问题。算法推荐的特点是,你不需要刻意选择,算法会根据你的喜好,给你推送内容。这样一来,你几乎没有选择的余地,在不断被「喂饱」中逐渐失去判断的能力。
2023-07-04 · StarBlog博客开发笔记 · 约 1 分钟
讨论
共 0 条加载评论中…