Был вопрос на форумах MSDN, откуда и возникла идея статьи. Хотя пока в своей практике не довелось видеть случаев где подобное могло бы понадобиться. Но всё же, раз это кому-нибудь было нужно, думаю стоит показать
кам можно такое реализовать. Начнём с создания простого шаблонного проекта ASP.NET 4, которая предлагает нам Visual Studio 2010 по умолчанию. Добавим на страницу Default.aspx кнопку и рисунок.
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master"
AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="WebPageScreenShot._Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<h2>
Welcome to ASP.NET!
</h2>
<p>
To learn more about ASP.NET visit <a href="http://www.asp.net"
title="ASP.NET Website">
www.asp.net</a>.
</p>
<p>
You can also find <a href="http://go.microsoft.com/fwlink/?
LinkID=152368&clcid=0x409"
title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
</p>
<asp:Button ID="DrawImageButton" runat="server"
Text="Сохранить как рисунок." OnClick="DrawImageButton_Click" />
<asp:Image ID="DrawPageImage" runat="server" />
</asp:Content>
Далее, инкапсулируем код который будет создавать изображение страницы в отдельный класс и назовём его WebPageToImage. Основная идея генерации рисунка, заключается в использовании класса WebBrowser из WinForms (не забываем добавить в проект ссылку на сборку System.Windows.Forms.dll) , который и будет получать нашу разметку, в виде Html страницы, и сохранять его как рисунок с помощью метода DrawToBitmap(). А так как WebBrowser это всего-навсего обёртка вокруг IE, то необходимо, чтобы страница отображалась коректно в браузере Internet Explorer. Код нашего класса приведён ниже.
namespace WebPageScreenShot
{
public class WebPageToImage
{
private string url;
private string filePath = string.Empty;
public WebPageToImage(string url, string path)
{
this.url = url;
this.filePath = path;
}
public void Generate()
{
//Запускаем компонент браузера в отдельном потоке, так как это обязательно,
//в текущем потоке ASP.NET он просто не запустится.
Thread thread = new Thread(() =>
{
WebBrowser browser = new WebBrowser { ScrollBarsEnabled = false };
browser.Navigate(url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
//Не забываем своевременно освободить системные ресурсы,
//так как WebBrowser интенсивно их потребляет.
browser.Dispose();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
//Блокируем вызывающий поток, до завершения
//текущего, хотя это и не лучшая идея.
thread.Join();
}
private void WebBrowser_DocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser browser = (WebBrowser)sender;
browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width,
browser.Document.Body.ScrollRectangle.Bottom);
browser.ScrollBarsEnabled = false;
Bitmap image = new Bitmap(browser.Document.Body.ScrollRectangle.Width,
browser.Document.Body.ScrollRectangle.Bottom);
browser.BringToFront();
browser.DrawToBitmap(image, browser.Bounds);
SaveImage(image, filePath);
}
private void SaveImage(Bitmap image, string path)
{
//Создаём массив объектов с параметрами для передачи кодировщику.
//Используем только один параметр, для данного случая.
EncoderParameters encoderParameters = new EncoderParameters(1);
//Единственному элементу присваиваем новый параметр.
encoderParameters.Param[0] =
new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
//Получаем объект кодировщика с нужными нам свойствами.
ImageCodecInfo imageCodec = ImageCodecInfo.GetImageDecoders()
.Where(c => c.FormatID == ImageFormat.Jpeg.Guid).First();
//Сохраняем изображение.
image.Save(path, imageCodec, encoderParameters);
}
}
}
Осталось добавить код страницы, чтобы всё заработало.
namespace WebPageScreenShot
{
public partial class _Default : System.Web.UI.Page
{
private bool drawPageToImage = false;
private string fileName;
protected void Page_Load(object sender, EventArgs e)
{
}
protected void DrawImageButton_Click(object sender, EventArgs e)
{
fileName = Guid.NewGuid().ToString();
drawPageToImage = true;
DrawPageImage.ImageUrl = "~/Images/" + fileName + ".jpg";
}
protected override void Render(HtmlTextWriter writer)
{
if (drawPageToImage)
{
StringBuilder stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter);
base.Render(htmlTextWriter);
//Получаем разметку в виде строки.
string htmlString = stringBuilder.ToString();
//Пишем разметку в новый файл расположенный в корневой директории. Это важно,
//так как пути к css и другим файлам относительны, и если файл
//разместить в другом месте, страница будет отображаться неправильно
// и скриншот будет не тот.
File.WriteAllText(Server.MapPath("~/") + fileName + ".htm", htmlString);
writer.Write(htmlString);
string htmlPageUrl = Request.Url.GetLeftPart(UriPartial.Authority)
+ ResolveUrl("~/" + fileName + ".htm");
//Создаём объект нашего класса генератора.
WebPageToImage webSiteToImage =
new WebPageToImage(htmlPageUrl, Server.MapPath("~/Images/" + fileName + ".jpg"));
//Генерируем изображение.
webSiteToImage.Generate();
//Перемещаем файл разметки в новое хранилище, или можно даже удалить его.
Directory.Move(Server.MapPath("~/") + fileName + ".htm",
Server.MapPath("~/Pages/") + fileName + ".htm");
}
else
{
base.Render(writer);
}
}
}
}
А почему нужно было заморачиваться и писать разметку в отдельный файл, а потом его запрашивать? Ведь можно было запрашивать страницу напрямую из кода? Дело в том, что кода будет выполнена аутентификация и авторизация, а на странице будут отображены данные зависящие от текущей сессии, то данный вариант наиболее легковесный. Иначе нужно было бы заново выполнить вход и делать разделение данных текущей сессии, чтобы WebBrowser отображал идентичную той странице страницу, которая открыта у пользователя в данный момент. А это очень непросто и нереализуемо, если не используется БД для хранения сеанса пользователя. Ну а если нет необходимости в высшеперечисленном, то не нужно заниматься шаманством, а можно запрашивать страницу напрямую, и всё будет намного проще.