免费 ASP.NET MVC 文件管理器,内置 PDF 查看器
← Back to Blog••3 min read

免费 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;
}

该控制器完成三件事:

  1. Index – 读取 App_Data/Docs 文件夹并将文件名传递给视图。
  2. Download / Delete – 使用内置的 PhysicalFile 辅助方法实现的标准 MVC 操作。
  3. 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 组件。

#ASP.NET MVC#File Manager#PDF Viewer#.NET#Doconut#文件管理器#PDF 查看器