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

下面我将详细介绍这两种方案,并提供完整的代码示例。

asp.net mvc 网页截屏
(图片来源网络,侵删)

服务端截屏 (推荐用于后台任务、报告生成等)

这种方法的核心思想是:你的 ASP.NET MVC 应用程序作为“指挥官”,调用一个在服务器上运行的无头浏览器,让它打开一个 URL,等待页面加载完成,然后截图并保存。

准备工作:安装必要的 NuGet 包

我们将使用 PuppeteerSharp,它是对 Google Chrome DevTools Protocol 的 .NET 封装,是目前最流行、最稳定的无头浏览器方案之一。

在你的 ASP.NET MVC 项目中,通过 NuGet 包管理器控制台安装:

Install-Package PuppeteerSharp

注意:首次运行时,PuppeteerSharp 会自动下载指定版本的 Chromium 浏览器,这个过程可能会花费几分钟时间,并且会在你的项目目录下创建一个 node_modules 文件夹来存放浏览器文件。

asp.net mvc 网页截屏
(图片来源网络,侵删)

实现步骤

创建一个截图服务

为了代码的整洁和可重用性,我们最好将截图逻辑封装在一个服务类中。

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 中调用服务

asp.net mvc 网页截屏
(图片来源网络,侵删)

我们在一个 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
同源策略 无限制,可截屏任何网站 有限制,通常只能截屏同源或允许跨域的页面
用户体验 需要等待服务器处理,可能有延迟 即时,在用户浏览器中快速完成
服务器负载 较高,每个截图都会启动一个浏览器进程 较低,只接收图片数据
适用场景 后台自动生成报告、监控、爬虫、生成分享图 用户主动点击分享、保存页面为图片、网页标注工具

如何选择?

  • 如果你的需求是自动化,比如定时抓取某个网站的内容生成报告,或者生成一个固定的“网站快照”,请选择服务端截屏
  • 如果你的需求是交互式的,比如用户点击一个“保存为图片”按钮来分享当前页面,请选择客户端截屏