文章目录
-
- 概要
- 设计思路
- 具体代码实现
-
- 1、文件服务API接口
- 2、文件服务API接口调用工具类
- 服务调用示例
-
- 1、后端API接口
- 2、前端代码(Vue2+ElementUI实现)
- 3、示例效果
概要
说到文件服务可能很多朋友会选择集成minio这些框架,但是对于一些大部分的系统来说,不会用到很复杂的场景。本文将分享基于AspNet WebApi(.NET4.5)实现一个简单文件服务。
设计思路
1、所有文件放在网站的根目录下Uploads文件夹中(其实也考虑过放其他盘,但是我预览文件要通过URL访问,所以只有放网站目录下); 2、文件名格式为“文件ID.扩展名”,后面访问文件也是通过文件Id查找; 3、设计文件上传、获取文件路径、删除文件三个接口; 4、封装一个工具类,用于调用文件服务。
具体代码实现
1、文件服务API接口
1、单个文件限制最大100M,这个可以自己调整(这里需要注意WebApi项目本身也是对请求的报文有限制,这个怎么弄搜一下就行); 2、对于允许哪些扩展名的文件,配置文件里面配AllowedExtensions;
BaseController代码
public abstract class BaseController : ApiController
{
protected IHttpActionResult Success(string message = "", object data = null)
{
return Json(new { Code = 0, Message = message, Data = data });
}
protected IHttpActionResult Fail(string message = "", object data = null)
{
return Json(new { Code = 1, Message = message, Data = data });
}
}
Web.config
<appSettings>
<add key="AllowedExtensions" value="jpg,jpeg,png,gif,pdf,txt,doc,docx,xls,xlsx,zip,rar" />
</appSettings>
FileController
public class FileController : BaseController
{
private const string folder = "~/Uploads";
private readonly string uploadFolder;
private readonly long maxFileSize = 100 * 1024 * 1024;
private readonly string[] allowedExtensions;
public FileController()
{
allowedExtensions = ConfigurationManager.AppSettings["AllowedExtensions"].Split(',').Select(s => "." + s).ToArray();
uploadFolder = HttpContext.Current.Server.MapPath(folder);
if (!Directory.Exists(uploadFolder))
Directory.CreateDirectory(uploadFolder);
}
[HttpPost]
public async Task<IHttpActionResult> UploadFile()
{
if (!Request.Content.IsMimeMultipartContent())
return Fail("请选择文件上传");
var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);
if (!provider.FileData.Any())
return Fail("没有上传文件");
var resultFiles = new List<object>();
var savedFiles = new List<string>();
var baseUrl = $"{Request.RequestUri.Scheme}://{Request.RequestUri.Authority}";
var relativePath = folder.TrimStart('~');
try
{
foreach (var fileData in provider.FileData)
{
var tempPath = fileData.LocalFileName;
var originalName = fileData.Headers.ContentDisposition.FileName.Trim('\\"');
var extension = Path.GetExtension(originalName).ToLower();
if (!allowedExtensions.Contains(extension))
throw new Exception($"不支持的文件类型: {extension}");
var fileInfo = new FileInfo(tempPath);
if (fileInfo.Length > maxFileSize)
throw new Exception($"文件大小超过100MB: {originalName}");
var fileId = Guid.NewGuid().ToString();
var newFileName = fileId + extension;
var savePath = Path.Combine(uploadFolder, newFileName);
File.Move(tempPath, savePath);
savedFiles.Add(savePath);
var fileUrl = $"{baseUrl}{relativePath}/{newFileName}";
resultFiles.Add(new { FileId = fileId, FilePath = fileUrl });
}
return Success(data: resultFiles);
}
catch (Exception ex)
{
foreach (var savedFile in savedFiles)
{
try { File.Delete(savedFile); } catch { }
}
foreach (var fileData in provider.FileData)
{
try { File.Delete(fileData.LocalFileName); } catch { }
}
return Fail($"文件上传失败: {ex.Message}");
}
}
[HttpPost]
public IHttpActionResult GetFilePaths(string[] fileIds)
{
var baseUrl = $"{Request.RequestUri.Scheme}://{Request.RequestUri.Authority}";
var relativePath = folder.TrimStart('~');
var resultFiles = new List<object>();
foreach (string fileId in fileIds)
{
var filePath = Directory.GetFiles(uploadFolder, fileId + ".*").FirstOrDefault();
if (filePath == null)
return Fail($"文件不存在{fileId}");
var fileName = Path.GetFileName(filePath);
resultFiles.Add(new { FileId = fileId, FilePath = $"{baseUrl}{relativePath}/{fileName}" });
}
return Success(data: resultFiles);
}
[HttpPost]
public IHttpActionResult DeleteFiles(string[] fileIds)
{
if (fileIds == null || fileIds.Length == 0)
return Fail("请提供要删除的文件ID");
using (var scope = new TransactionScope())
{
foreach (var fileId in fileIds)
{
var filePath = Directory.GetFiles(uploadFolder, fileId + ".*").SingleOrDefault();
if (filePath == null)
return Fail($"文件不存在: {fileId}");
File.Delete(filePath);
}
scope.Complete();
}
return Success();
}
}
2、文件服务API接口调用工具类
public class FileUploader
{
private string fileServiceUrl;
private HttpClient http;
public FileUploader(string fileServiceUrl)
{
this.fileServiceUrl = fileServiceUrl;
http = new HttpClient();
}
public async Task<dynamic> Upload(MultipartFileData[] files)
{
using (var form = new MultipartFormDataContent())
{
foreach (var file in files)
{
var fileBytes = File.ReadAllBytes(file.LocalFileName);
var content = new ByteArrayContent(fileBytes);
form.Add(content, "file", file.Headers.ContentDisposition.FileName.Trim('\\"'));
}
var url = fileServiceUrl + "/api/File/UploadFile";
var response = await http.PostAsync(url, form);
var json = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
}
}
public async Task<dynamic> GetFilePaths(string[] fileIds)
{
var url = fileServiceUrl + "/api/File/GetFilePaths";
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(fileIds),
Encoding.UTF8, "application/json");
var response = await http.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
}
public async Task<dynamic> DeleteFiles(string[] fileIds)
{
var url = fileServiceUrl + "/api/File/DeleteFiles";
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(fileIds),
Encoding.UTF8, "application/json");
var response = await http.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
}
}
服务调用示例
1、我设计的都是通过后端调文件服务接口,前端不直接调用文件服务 2、文件上传都是先保存在系统临时目录,所以一定要记得清除啊。。。 3、前端示例代码Vue2写的 4、有些细枝末节的都没有粘了啊,应该可以看懂,EF自己配一下
1、后端API接口
public class MyTestController : ApiController
{
private FileUploader fileUploader;
public MyTestController()
{
fileUploader = new FileUploader(ConfigurationManager.AppSettings["FileServiceUrl"]);
}
[HttpPost]
public async Task<IHttpActionResult> UploadTest()
{
var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);
if (!provider.FileData.Any())
return BadRequest("没有上传文件");
try
{
var result = await fileUploader.Upload(provider.FileData.ToArray());
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
finally
{
foreach (var file in provider.FileData)
{
if (!File.Exists(file.LocalFileName))
continue;
File.Delete(file.LocalFileName);
}
}
}
[HttpGet]
public async Task<IHttpActionResult> QueryUserDatas()
{
user[] users;
using (var dbContext = new jonas_testEntities())
users = dbContext.user.ToArray();
var result = await fileUploader.GetFilePaths(users.Select(u => u.IconImageId).ToArray());
if (result.Code != 0)
return Json(result);
var fileInfos = ((IEnumerable<dynamic>)result.Data).Select(d => new { d.FileId, d.FilePath }).ToArray();
List<QueryUserDatasResultItem> list = new List<QueryUserDatasResultItem>();
foreach (var user in users)
{
QueryUserDatasResultItem model = new QueryUserDatasResultItem
{
Account = user.Account,
RealName = user.RealName
};
model.IconImageUrl = fileInfos.Single(s => s.FileId == user.IconImageId).FilePath;
list.Add(model);
}
return Json(list);
}
[HttpPost]
public async Task<IHttpActionResult> AddUser()
{
if (!Request.Content.IsMimeMultipartContent())
return Json(new { Code = 1, Message = "请求格式错误" });
var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);
try
{
var formData = provider.FormData;
var user = new user
{
Account = formData["Account"],
RealName = formData["RealName"]
};
if (provider.FileData.Count > 0)
{
var fileData = provider.FileData[0];
var result = await fileUploader.Upload(new[] { fileData });
if (result.Code != 0)
return Json(result);
user.IconImageId = result.Data[0].FileId;
}
using (var dbContext = new jonas_testEntities())
{
if (dbContext.user.Any(u => u.Account == user.Account))
return Json(new { Code=1,Message="账号已经存在"});
dbContext.user.Add(user);
await dbContext.SaveChangesAsync();
}
return Json(new { Code = 0 });
}
catch (Exception ex)
{
return Json(new { Code = 1, Message = ex.Message });
}
finally
{
foreach (var fileData in provider.FileData)
{
try
{
if (File.Exists(fileData.LocalFileName))
{
File.Delete(fileData.LocalFileName);
}
}
catch
{
}
}
}
}
[HttpPost]
public async Task<IHttpActionResult> DeleteUser(string account)
{
user user = null;
using (var dbContext = new jonas_testEntities())
{
user = dbContext.user.SingleOrDefault(u => u.Account == account);
if (user == null)
return Json(new { Code = 1, Message = "用户不存在" });
dbContext.user.Remove(user);
await dbContext.SaveChangesAsync();
}
await fileUploader.DeleteFiles(new string[] { user.IconImageId });
return Json(new { Code = 0 });
}
}
2、前端代码(Vue2+ElementUI实现)
<template>
<div id="app">
<el–table :data="tableDatas" style="width: 800px;" stripe border>
<el–table–column label="账号" prop="Account" width="150">
</el–table–column>
<el–table–column label="真实姓名" prop="RealName" width="150">
</el–table–column>
<el–table–column label="头像" width="100">
<template slot–scope="scope">
<span style="color: #409EFF; cursor: pointer;" @click="previewIconHandler(scope.row.IconImageUrl)">预览</span>
</template>
</el–table–column>
<el–table–column width="150">
<template #header>
<el–button type="primary" size="medium" @click="dialogAdd.show = true">新增</el–button>
</template>
<template slot–scope="scope">
<el–button type="danger" size="small" @click="removeUserHandler(scope.row.Account)">删除</el–button>
</template>
</el–table–column>
</el–table>
<el–dialog title="预览头像" width="500px" :visible.sync="dialogPreviewIcon.show" :append–to–body="true"
:modal–append–to–body="false" :lock–scroll="false">
<div style="width: 100%;height: 500px;">
<img :src="dialogPreviewIcon.src" style="width: 100%;height: 100%;">
</div>
</el–dialog>
<el–dialog title="新增用户" width="800px" :visible.sync="dialogAdd.show" :append–to–body="true"
:modal–append–to–body="false" :lock–scroll="false">
<el–form :model="dialogAdd.userInfo" label–width="150px" class="demo-ruleForm">
<el–form–item label="账号">
<el–input v–model="dialogAdd.userInfo.Account"></el–input>
</el–form–item>
<el–form–item label="真实姓名">
<el–input v–model="dialogAdd.userInfo.RealName"></el–input>
</el–form–item>
<el–form–item label="头像">
<el–upload class="upload-demo" ref="upload" action="" :on–remove="removeOrChangeHandler"
:file–list="dialogAdd.files" :auto–upload="false" :on–change="removeOrChangeHandler">
<el–button slot="trigger" size="small" type="primary">选取文件</el–button>
</el–upload>
</el–form–item>
<el–form–item>
<el–button type="primary" @click="submitFormHandler">提交</el–button>
</el–form–item>
</el–form>
</el–dialog>
</div>
</template>
<script>
import axios from 'axios';
const baseUrl = 'http://localhost:4623'
export default {
data() {
return {
tableDatas: [],
dialogPreviewIcon: {
show: false,
src: ''
},
dialogAdd: {
show: false,
userInfo: {
Account: '',
RealName: ''
},
files: []
}
}
},
methods: {
async queryDatas() {
const { data: res } = await axios.get(baseUrl + '/api/MyTest/QueryUserDatas')
this.tableDatas = res
},
previewIconHandler(IconImageUrl) {
this.dialogPreviewIcon.src = IconImageUrl
this.dialogPreviewIcon.show = true
},
async submitFormHandler() {
console.log(this.dialogAdd)
if (this.dialogAdd.userInfo.Account === '' || this.dialogAdd.userInfo.RealName === '' || this.dialogAdd.files.length === 0)
return alert('请填写完整信息')
let formdata = new FormData()
formdata.append('file', this.dialogAdd.files[0].raw)
formdata.append('Account', this.dialogAdd.userInfo.Account)
formdata.append('RealName', this.dialogAdd.userInfo.RealName)
const { data: res } = await axios.post(baseUrl + '/api/MyTest/AddUser', formdata, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.Code != 0)
return alert(res.Message)
this.dialogAdd.show = false
this.queryDatas()
},
async removeUserHandler(account) {
const { data: res } = await axios.post(baseUrl + '/api/MyTest/DeleteUser?account=' + account)
if (res.Code != 0)
return alert(res.Message)
this.queryDatas()
},
removeOrChangeHandler(file, files) {
this.dialogAdd.files = files
},
},
created() {
this.queryDatas()
}
}
</script>
<style lang="less"></style>
3、示例效果

![P11246 [GESP202409 六级] 小杨和整数拆分-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108100631-6af049a75855c-220x150.png)


![打卡信奥刷题(3678)用C++实现信奥题 P12274 [蓝桥杯 2024 国 Python B] 设计-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108053830-6af00ad6bdf8d-220x150.png)