
免费 ASP.NET MVC 文件管理器,内置 PDF 查看器
构建免费 ASP.NET MVC 文件管理器以显示 PDF
您希望拥有一个页面,列出服务器上存储的文件,让用户可以下载或删除,并且在不离开站点的情况下直接在浏览器中打开任何 PDF。下面的指南将向您展示如何使用纯 ASP.NET MVC、内置的会话缓存以及 Doconut 的通用文档查看器来实现这一点。
1. 搭建最小化 MVC 应用
在 Visual Studio 中创建一个新的 MVC 项目(任意版本,包括免费的 Community 版)。
添加一个 FileController 来承载文件管理相关的操作:
public class FileController : Controller
{
private readonly string _root = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "Docs");
public IActionResult Index()
{
var files = Directory.GetFiles(_root).Select(Path.GetFileName).ToList();
return View(files);
}
public IActionResult Download(string name)
{
var path = Path.Combine(_root, name);
if (!System.IO.File.Exists(path)) return NotFound();
var mime = "application/octet-stream";
return PhysicalFile(path, mime, name);
}
public IActionResult Delete(string name)
{
var path = Path.Combine(_root, name);
if (System.IO.File.Exists(path)) System.IO.File.Delete(path);
return RedirectToAction(nameof(Index));
}
public async Task<IActionResult> ViewPdf(string name)
{
var path = Path.Combine(_root, name);
if (!System.IO.File.Exists(path)) return NotFound();
// 在此创建查看器令牌(参见第 2 节)
var token = await _viewer.OpenDocumentAsync(
path,
new Doconut.PdfConfig { AllowSearch = true },
new DocOptions(),
CancellationToken.None);
ViewBag.Token = token;
ViewBag.BasePath = "doconut"; // 与中间件分支匹配
ViewBag.ResPath = "doconut-res/images";// 嵌入式资源路径
return View();
}
private readonly Doconut.Viewer _viewer;
public FileController(Doconut.Viewer viewer) => _viewer = viewer;
}
该控制器完成三件事:
- Index – 读取
App_Data/Docs文件夹并将文件名传递给视图。 - Download / Delete – 使用内置的
PhysicalFile辅助方法实现的标准 MVC 操作。 - ViewPdf – 使用 Doconut 打开选中的 PDF,获取安全令牌并将其传递给 Razor 视图。
2. 配置会话状态和 Doconut 服务
Doconut 的查看器依赖 ASP.NET 会话状态来保护标识文档的令牌。在 Program.cs(或 .NET 6 项目的 Startup.cs)中添加所需的服务:
var builder = WebApplication.CreateBuilder(args);
// MVC 服务
builder.Services.AddControllersWithViews();
// 会话缓存 – 用于令牌验证
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
// Doconut 注册 – 指向许可证文件
builder.Services.AddDoconut(options =>
{
options.LicensePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "Doconut.Viewer.lic");
options.MiddlewarePath = "/doconut";
options.ResourcesPath = "/doconut-res";
options.UnsafeMode = false; // 保持默认安全设置
});
var app = builder.Build();
// 会话必须在查看器中间件之前运行
app.UseSession();
// 必须将 Doconut 中间件映射到上面配置的路径
// 参考 Doconut 文档获取确切的映射调用方式(例如 app.Map("/doconut", …))
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
app.Run();
关键要点:
AddDistributedMemoryCache提供AddSession所需的内存存储。UseSession()必须 在 Doconut 中间件之前调用,否则会出现运行时错误 “Session middleware not configured.”MiddlewarePath和ResourcesPath是协同字符串——实际的中间件分支必须与您配置的路径相匹配。
3. 创建文件管理器视图
创建 Views/File/Index.cshtml。它列出文件并提供下载、删除以及 PDF 查看操作。
@model List<string>
<h2>Document Library</h2>
<table>
<thead><tr><th>Name</th><th>Actions</th></tr></thead>
<tbody>
@foreach (var f in Model)
{
<tr>
<td>@f</td>
<td>
<a asp-action="Download" asp-route-name="@f">Download</a> |
<a asp-action="Delete" asp-route-name="@f">Delete</a> |
@if (Path.GetExtension(f).Equals(".pdf", StringComparison.OrdinalIgnoreCase))
{
<a asp-action="ViewPdf" asp-route-name="@f">View PDF</a>
}
</td>
</tr>
}
</tbody>
</table>
当用户点击 View PDF 时,ViewPdf 操作会创建令牌并返回第二个视图,该视图承载查看器小部件。
4. 渲染 Doconut 查看器
创建 Views/File/ViewPdf.cshtml。页面只需要一个容器元素;JavaScript 小部件从嵌入资源中加载。
@{
var token = ViewBag.Token as string;
var basePath = ViewBag.BasePath as string;
var resPath = ViewBag.ResPath as string;
}
<h2>PDF Preview</h2>
<div id="div_ctlDoc"></div>
<script src="@($"{basePath}/docViewer.js")"></script>
<link rel="stylesheet" href="@($"{basePath}/docViewer.css")" />
<script>
$('#div_ctlDoc').docViewer({
AccessToken: '@token',
BasePath: '@basePath',
ResPath: '@resPath',
FitType: 'width',
showThumbs: true,
autoLoad: true,
onError: function (msg) { console.error(msg); }
});
</script>
小部件读取您提供的 AccessToken,访问 /doconut/DocImage.axd 端点,并将每页作为图像流式传输。由于令牌绑定到当前 ASP.NET 会话,用户无法复制 URL 并在没有相同会话 Cookie 的其他浏览器中查看文档。
5. 处理令牌过期和安全性
Doconut 的默认令牌超时时间为 60 分钟(可通过 DocOptions.TimeOut 配置)。如果查看器收到 “未找到文档会话” 或 “您无权查看此页面”,则会话要么已过期,要么请求来自不同的浏览器上下文。
常见的解决办法:
- 刷新页面 – 控制器将生成一个新的令牌。
- 延长超时 – 在打开文档时传入自定义
DocOptions:
var options = new DocOptions { TimeOut = TimeSpan.FromHours(2) };
var token = await _viewer.OpenDocumentAsync(
path,
new Doconut.PdfConfig { AllowSearch = true },
options,
CancellationToken.None);
- 跨源设置 – 确保会话 Cookie 的
SameSite属性与前端域名兼容。默认的Lax对大多数同源门户都有效。
6. 扩展管理器(可选)
核心文件管理器已经可用,但您可能想添加:
- 上传 – 一个简单的表单,提交
IFormFile并保存到_root。 - 文件夹导航 – 修改
Index操作以接受相对路径,并使用Path.GetFullPath+StartsWith(_root)检查防止目录遍历。 - 搜索 – 在将文件列表传递给视图之前按名称过滤
files。
所有这些扩展都保持在相同的 MVC 模式中,并继续受同一会话保护的 Doconut 查看器的益处。
7. 您已实现的成果
- 一个 免费 的 ASP.NET MVC 文件管理器 – 无需第三方 UI 库,也没有授权费用。
- 通过 Doconut 的通用查看器实现 浏览器内 PDF 渲染,避免在客户端安装 Office 或 Adobe。
- 基于令牌的安全访问,利用 ASP.NET 会话状态,防止任意 URL 共享。
- 一个 单一代码库,兼容 .NET 6 与 .NET 8,并可仅更改注册调用(
AddDoconut、UseSession)后迁移到 Razor Pages 或 Minimal API。
将应用部署到 IIS、Azure App Service 或 Docker – 相同的 Program.cs 配置在任何环境下都能工作,因为 Doconut 运行在 .NET 6+ 上且不需要任何本机 Office 组件。