Ваша целевая веб-страница использует элемент управления SSRS ReportViewer для управления отображением отчетов. Этот элемент управления в значительной степени опирается на Состояние сеанса ASP.Net для отображения отчета в фоновом режиме с помощью вызовов Reserved.ReportViewerWebControl.axd
обработчик ресурсов.
Это означает, что для использования указанной axd
ссылки, которую вы определили, вы должны сначала инициировать создание и кэширование контента в контексте сеанса, прежде чем его можно будет загрузить, а затем вы должны загрузить его из тот же контекст.
- Мы не можем просто запустить страницу один раз и выяснить URL-адрес, мы должны найти способ сделать это программно, используя один и тот же сеанс между запросами.
Элемент управления ReportViewer делает это с помощью javascript при нажатии кнопки загрузки, что означает отсутствие простой ссылки на Reserved.ReportViewerWebControl.axd
для удаления из HTML. Это означает, что мы должны выполнить тот же сценарий вручную или смоделировать пользователя, щелкающего по ссылке.
В этом решении будут использованы некоторые методы очистки экрана (UX Automation) для имитации нажатия кнопки экспорта и захватарезультат, но я бы избегал этого, если бы вы могли.
Вы действительно должны попытаться связаться с разработчиком напрямую для получения рекомендаций, возможно, они реализовали некоторые простые параметры URL для экспорта напрямую без необходимости автоматизации интерфейса.
Концепция относительно проста:
- Создание сеанса веб-браузера на странице отчета
- Нажмите кнопку экспорта в CSV
- это попытается открыть еще одну ссылку в новом окне, которую мы должны отключить!
- Захватить URL из нового окна
- Скачать файл экспортаиспользуя тот же контекст сеанса
- Мы не можем использовать элемент управления веб-браузера для этого, потому что его интерфейс управляется пользовательским интерфейсом.
Мы не можем использовать HttpWebRequest
или WebClient
для выполнения javascript для HTMl DOM, для этого нужно использовать веб-браузер. Другая проблема, которая возникает, заключается в том, что мы не можем просто использовать события NewWindow
или FileDownload
WebBrowser в элементе управления, поскольку эти события не предоставляют такую информацию, как URL-адрес для новых окон, источник загрузки файла или цель. Вместо этого мы должны ссылаться на внутренний COM-браузер (фактически IE) и использовать собственное событие NewWindow3
для захвата URL-адреса на Reserved.ReportViewerWebControl.axd
, чтобы мы могли загрузить его вручную.
Я использую эти основные ссылки, чтобы объяснить методику
Наконецкак я упоминал выше, мы не можем использовать веб-браузер для прямой загрузки файла с URL-адреса, так как он откроет диалоговое окно SAVE AS в новом веб-браузере или сохранит его непосредственно в настроенную папку «Загрузки». Как описано в справочной статье, мы используем метод GetGlobalCookies
от Эрики Чинчио, который можно найти в отличной статье, предоставленной @Pedro Leonardo (доступно здесь )
Я положил всеэто простое консольное приложение, которое вы можете запустить, просто измените URL вашего отчета, название ссылки экспорта и путь сохранения:
Ниже показано, как я получил ссылку, которую я хотел скачать,Точный заголовок и состав ссылки будут варьироваться в зависимости от реализации:
class Program
{
[STAThread]
static void Main(string[] args)
{
SaveReportToDisk("http://localhost:13933/reports/sqlversioninfo", "CSV (comma delimited)", "C:\\temp\\reportDump.csv");
}
/// <summary>
/// Automate clicking on the 'Save As' drop down menu in a report viewer control embedded at the specified URL
/// </summary>
/// <param name="sourceURL">URL that the report viewer control is hosted on</param>
/// <param name="linkTitle">Title of the export option that you want to automate</param>
/// <param name="savepath">The local path to save to exported report to</param>
static void SaveReportToDisk(string sourceURL, string linkTitle, string savepath)
{
WebBrowser wb = new WebBrowser();
wb.ScrollBarsEnabled = false;
wb.ScriptErrorsSuppressed = true;
wb.Navigate(sourceURL);
//wait for the page to load
while (wb.ReadyState != WebBrowserReadyState.Complete) { Application.DoEvents(); }
// We want to find the Link that is the export to CSV menu item and click it
// this is the first link on the page that has a title='CSV', modify this search if your link is different.
// TODO: modify this selection mechanism to suit your needs, the following is very crude
var exportLink = wb.Document.GetElementsByTagName("a")
.OfType<HtmlElement>()
.FirstOrDefault(x => (x.GetAttribute("title")?.Equals(linkTitle, StringComparison.OrdinalIgnoreCase)).GetValueOrDefault());
if (exportLink == null)
throw new NotSupportedException("Url did not resolve to a valid Report Viewer web Document");
bool fileDownloaded = false;
// listen for new window, using the COM wrapper so we can capture the url
(wb.ActiveXInstance as SHDocVw.WebBrowser).NewWindow3 +=
(ref object ppDisp, ref bool Cancel, uint dwFlags, string bstrUrlContext, string bstrUrl) =>
{
Cancel = true; //should block the default browser from opening the link in a new window
Task.Run(async () =>
{
await DownloadLinkAsync(bstrUrl, savepath);
fileDownloaded = true;
}).Wait();
};
// execute the link
exportLink.InvokeMember("click");
//wait for the page to refresh
while (!fileDownloaded) { Application.DoEvents(); }
}
private static async Task DownloadLinkAsync(string documentLinkUrl, string savePath)
{
var documentLinkUri = new Uri(documentLinkUrl);
var cookieString = GetGlobalCookies(documentLinkUri.AbsoluteUri);
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = documentLinkUri })
{
cookieContainer.SetCookies(documentLinkUri, cookieString);
var response = await client.GetAsync(documentLinkUrl);
if (response.IsSuccessStatusCode)
{
var stream = await response.Content.ReadAsStreamAsync();
// Response can be saved from Stream
using (Stream output = File.OpenWrite(savePath))
{
stream.CopyTo(output);
}
}
}
}
// from Erika Chinchio which can be found in the excellent article provided by @Pedro Leonardo (available here: http://www.codeproject.com/Tips/659004/Download-of-file-with-open-save-dialog-box),
[System.Runtime.InteropServices.DllImport("wininet.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
static extern bool InternetGetCookieEx(string pchURL, string pchCookieName,
System.Text.StringBuilder pchCookieData, ref uint pcchCookieData, int dwFlags, IntPtr lpReserved);
const int INTERNET_COOKIE_HTTPONLY = 0x00002000;
private static string GetGlobalCookies(string uri)
{
uint uiDataSize = 2048;
var sbCookieData = new System.Text.StringBuilder((int)uiDataSize);
if (InternetGetCookieEx(uri, null, sbCookieData, ref uiDataSize,
INTERNET_COOKIE_HTTPONLY, IntPtr.Zero)
&&
sbCookieData.Length > 0)
{
return sbCookieData.ToString().Replace(";", ",");
}
return null;
}
}
Причина, по которой я советую поговорить с разработчиком передСнижение размера экрана кроличьей норы заключается в том, что в качестве стандарта при использовании элемента управления для просмотра отчетов я всегда стараюсь реализовать SSRS собственные rc:
и rs:
параметры URL или по крайней мере убедиться, что я предоставляю способЧтобы экспортировать отчеты напрямую через URL.
Вы не можете использовать эти параметры "из коробки", они предназначены для использования при запросах SSRSСервер напрямую, чего нет в вашем примере.
Я не придумал это сам, не знаю, из какого ресурса я это узнал, но это означает, что есть шанс, что другие пришли к аналогичному выводу. Я реализую это главным образом, чтобы я мог использовать эти концепции в остальной части приложения. Но и в том, что касается отчетов, одной из причин, по которой мы выбираем SSRS и RDL в качестве решения для создания отчетов, является его универсальность, мы пишем определение отчетов, элементы управления позволяют пользователям использовать их по мере необходимости. Если мы ограничили возможность экспорта отчетов пользователем, мы действительно недостаточно использовали эту среду.