- 服务端截屏:在服务器上使用无头浏览器(Headless Browser)技术,如 Selenium、Puppeteer.NET 或 Playwright,渲染整个页面并将其保存为图片,这种方式不依赖客户端,可以截屏任何动态加载的内容(包括 AJAX 请求后的结果),但服务器上需要安装相应的浏览器。
- 客户端截屏:在用户的浏览器中使用 JavaScript 库,如
html2canvas,将页面 DOM 元素转换为图片,这种方式不依赖服务器,但有同源策略限制,且只能截屏用户当前浏览器窗口中已加载完成的内容。
下面我将详细介绍这两种方案,并提供完整的代码示例。

服务端截屏 (推荐用于后台任务、报告生成等)
这种方法的核心思想是:你的 ASP.NET MVC 应用程序作为“指挥官”,调用一个在服务器上运行的无头浏览器,让它打开一个 URL,等待页面加载完成,然后截图并保存。
准备工作:安装必要的 NuGet 包
我们将使用 PuppeteerSharp,它是对 Google Chrome DevTools Protocol 的 .NET 封装,是目前最流行、最稳定的无头浏览器方案之一。
在你的 ASP.NET MVC 项目中,通过 NuGet 包管理器控制台安装:
Install-Package PuppeteerSharp
注意:首次运行时,PuppeteerSharp 会自动下载指定版本的 Chromium 浏览器,这个过程可能会花费几分钟时间,并且会在你的项目目录下创建一个 node_modules 文件夹来存放浏览器文件。

实现步骤
创建一个截图服务
为了代码的整洁和可重用性,我们最好将截图逻辑封装在一个服务类中。
在 Services 文件夹下创建一个 ScreenshotService.cs 文件:
using PuppeteerSharp;
using System;
using System.IO;
using System.Threading.Tasks;
namespace YourProjectName.Services
{
public class ScreenshotService
{
public async Task<string> TakeScreenshotOfUrl(string url, int width = 1280, int height = 720)
{
// 使用 using 语句确保浏览器和页面实例被正确释放
using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true, // 无头模式运行
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" } // Linux 环境下可能需要
});
using var page = await browser.NewPageAsync();
// 设置视口大小
await page.SetViewportAsync(new ViewPortOptions
{
Width = width,
Height = height
});
// 导航到指定 URL
// 等待直到 'networkidle2',即网络连接空闲至少 500ms,适合等待 AJAX 加载完成
await page.GoToAsync(url, WaitUntilNavigation.Networkidle2);
// 定义截图保存路径
var screenshotPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", "screenshots", $"screenshot_{Guid.NewGuid()}.png");
// 确保目录存在
Directory.CreateDirectory(Path.GetDirectoryName(screenshotPath));
// 截图并保存
await page.ScreenshotAsync(screenshotPath);
return $"/screenshots/{Path.GetFileName(screenshotPath)}"; // 返回相对路径,方便前端访问
}
}
}
在 Controller 中调用服务

我们在一个 Controller 中创建一个 Action 来触发截图。
using System.Web.Mvc;
using YourProjectName.Services;
using System.Threading.Tasks;
namespace YourProjectName.Controllers
{
public class HomeController : Controller
{
private readonly ScreenshotService _screenshotService;
// 通过依赖注入注入服务
public HomeController(ScreenshotService screenshotService)
{
_screenshotService = screenshotService;
}
public ActionResult Index()
{
return View();
}
[HttpPost]
public async Task<ActionResult> GenerateScreenshot(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
ViewBag.Message = "请输入有效的 URL。";
return View("Index");
}
try
{
// 确保 URL 是完整的
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
{
url = "http://" + url;
}
var imagePath = await _screenshotService.TakeScreenshotOfUrl(url);
ViewBag.Message = "截图成功!";
ViewBag.ImagePath = imagePath; // 将图片路径传递给视图
return View("Index");
}
catch (Exception ex)
{
// 记录日志 ex
ViewBag.Message = $"截图失败: {ex.Message}";
return View("Index");
}
}
}
}
创建视图
在 Views/Home 文件夹下创建 Index.cshtml 视图:
@{
ViewBag.Title = "网页截屏示例";
}
<h2>网页截屏工具</h2>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="urlInput">请输入要截图的网页地址:</label>
<input type="text" id="urlInput" class="form-control" placeholder=" https://www.google.com" value="@(ViewBag.ImagePath != null ? "" : "https://www.asp.net")">
</div>
<button id="captureBtn" class="btn btn-primary">生成截图</button>
</div>
</div>
<hr />
@if (ViewBag.Message != null)
{
<div class="alert alert-info">
@ViewBag.Message
</div>
}
@if (ViewBag.ImagePath != null)
{
<div class="row">
<div class="col-md-12">
<h3>截图结果:</h3>
<img src="@ViewBag.ImagePath" alt="Generated Screenshot" class="img-responsive img-thumbnail" />
</div>
</div>
}
@section Scripts {
<script>
$(document).ready(function () {
$("#captureBtn").click(function () {
var url = $("#urlInput").val();
if (!url) {
alert("请输入URL");
return;
}
$.ajax({
type: "POST",
url: "@Url.Action("GenerateScreenshot", "Home")",
data: { url: url },
success: function (response) {
// 页面会由服务器端重新渲染,显示新结果
location.reload();
},
error: function (xhr, status, error) {
alert("请求失败: " + error);
}
});
});
});
</script>
}
配置 Web.config (可选)
如果你的应用运行在 IIS 上,并且遇到权限问题,可能需要修改 Web.config,确保应用池身份有权限写入 wwwroot/screenshots 目录。
<system.web>
...
<identity impersonate="true" /> <!-- 以当前用户身份运行,可能需要更高权限 -->
</system.web>
客户端截屏 (适用于用户主动操作、分享功能等)
这种方法利用 JavaScript 在用户的浏览器中完成截图,然后将图片数据(Base64)发送回服务器保存。
准备工作:引入 html2canvas 库
将 html2canvas 库添加到你的项目中,可以通过 CDN 或 npm 安装。
在 _Layout.cshtml 的 <head> 或 <body> 末尾的 @RenderSection("scripts", required: false) 中引入 CDN 链接:
<!-- 在 _Layout.cshtml 中 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
实现步骤
修改 Controller 以接收图片数据
using System;
using System.IO;
using System.Web;
using System.Web.Mvc;
using System.Drawing;
using System.Drawing.Imaging;
namespace YourProjectName.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult SaveScreenshot(string imageData)
{
if (string.IsNullOrWhiteSpace(imageData))
{
return Json(new { success = false, message = "未收到图片数据。" });
}
try
{
// 移除 data:image/png;base64, 前缀
var base64Data = imageData.Substring(imageData.IndexOf(",") + 1);
byte[] imageBytes = Convert.FromBase64String(base64Data);
// 保存路径
var screenshotPath = Path.Combine(Server.MapPath("~/"), "wwwroot", "screenshots", $"client_screenshot_{Guid.NewGuid()}.png");
Directory.CreateDirectory(Path.GetDirectoryName(screenshotPath));
System.IO.File.WriteAllBytes(screenshotPath, imageBytes);
// 返回相对路径,方便前端显示
var relativePath = "/screenshots/" + Path.GetFileName(screenshotPath);
return Json(new { success = true, imagePath = relativePath });
}
catch (Exception ex)
{
return Json(new { success = false, message = "保存图片失败: " + ex.Message });
}
}
}
}
修改视图,添加截图按钮和区域
@{
ViewBag.Title = "客户端截屏示例";
}
<h2>客户端截屏工具</h2>
<div class="row">
<div class="col-md-12">
<div id="captureArea" style="border: 2px dashed #ccc; padding: 20px; margin-bottom: 20px;">
<h3>这是要截图的区域</h3>
<p>你可以在这里放置任何 HTML 内容,比如图片、文本、样式等。</p>
<p>当前时间: @DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")</p>
<img src="https://via.placeholder.com/150" alt="Placeholder" />
</div>
<button id="clientCaptureBtn" class="btn btn-success">客户端截图并保存</button>
</div>
</div>
<div id="resultArea" style="margin-top: 20px; display: none;">
<h3>截图结果:</h3>
<img id="resultImage" src="" alt="Client Screenshot" class="img-responsive img-thumbnail" />
</div>
@section Scripts {
<script>
$(document).ready(function () {
$("#clientCaptureBtn").click(function () {
var button = $(this);
button.prop('disabled', true).text('截图中...');
// 使用 html2canvas 截图指定区域
html2canvas(document.getElementById('captureArea')).then(canvas => {
// 将 canvas 转换为 Data URL (Base64)
var imageData = canvas.toDataURL('image/png');
// 发送到服务器
$.ajax({
type: "POST",
url: "@Url.Action("SaveScreenshot", "Home")",
data: { imageData: imageData },
success: function (response) {
if (response.success) {
$("#resultImage").attr("src", response.imagePath);
$("#resultArea").show();
alert("截图成功并已保存到服务器!");
} else {
alert(response.message);
}
},
error: function (xhr, status, error) {
alert("请求失败: " + error);
},
complete: function () {
button.prop('disabled', false).text('客户端截图并保存');
}
});
}).catch(err => {
console.error("html2canvas error:", err);
alert("截图失败,请检查控制台。");
button.prop('disabled', false).text('客户端截图并保存');
});
});
});
</script>
}
两种方案对比总结
| 特性 | 服务端截屏 (PuppeteerSharp) | 客户端截屏 (html2canvas) |
|---|---|---|
| 依赖环境 | 服务器需安装 .NET 和 Chromium | 客户端浏览器需支持 JavaScript |
| 同源策略 | 无限制,可截屏任何网站 | 有限制,通常只能截屏同源或允许跨域的页面 |
| 用户体验 | 需要等待服务器处理,可能有延迟 | 即时,在用户浏览器中快速完成 |
| 服务器负载 | 较高,每个截图都会启动一个浏览器进程 | 较低,只接收图片数据 |
| 适用场景 | 后台自动生成报告、监控、爬虫、生成分享图 | 用户主动点击分享、保存页面为图片、网页标注工具 |
如何选择?
- 如果你的需求是自动化,比如定时抓取某个网站的内容生成报告,或者生成一个固定的“网站快照”,请选择服务端截屏。
- 如果你的需求是交互式的,比如用户点击一个“保存为图片”按钮来分享当前页面,请选择客户端截屏。
