Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

SQL Server 2000 Reporting Services. (Part 2)

Введение.

Довольно много времени прошло с момента написания первой статьи. Я планировал написать вторую статью быстрее, но жизнь внесла определенные коррективы. Но, как говорится, лучше поздно, чем никогда. Сразу хочется поблагодарить Вас за отзывы и замечания по первой статье.  Мне приятно, что мой материал оставил приятное впечатление. Это вдохновляет на дальнейшую работу.

О чем будем читать?

В данной статье мне хотелось бы глубже погрузиться в мир RS (Reporting Services) и коснуться некоторых моментов управления «изнутри», т.е. при помощи Web методов.

Что будем писать?

В качестве примера, я решил реализовать небольшой RS Explorer, который мог бы построить нам дерево отчетов и поддерживал функции рендеринга отчетов во встроенном окне при помощи com компонента: Microsoft Web Browser.  Кроме того, я добавлю еще функцию экспорта в форматы, поддерживаемые RS без отображения самого отчета.  Так же в этом примере мы увидим пример использования параметров отчетов по умолчанию, и изменения их до вызова отчета.

Реализация.

Начну с того, что при реализации данного примера я использовал компоненты стороннего производителя. Зачем я это сделал?  Во-первых, хочется разнообразия, во-вторых, я привык к их архитектуре, так как давно ими пользуюсь. Можно добавить в третьих, хочется разнообразия. Кроме того,  высока вероятность дальнейшего развития этого проекта.

В любом случае, как полный исходный код проекта, так и скомпилированный пример, будут доступны на нашем портале для загрузки. Исходный код Вы сможете легко адаптировать к любым Вашим компонентам.

Цели и задачи определены. Приступим.

В дизайнере «накидываем» компонентов и строим будущий каркас приложения. Здесь все стандартно. Справа дерево. Под деревом расположилось маленькое окошко Description, куда мы будем выводить различную, сопутствующую информацию. Слева окно com компонента  Microsoft Windows Browser.

Для тех, кто не знает, как вытащить компонент Microsoft Web Browser сделаю небольшой экскурс. Открываем окошко ToolBox. Выбираем какой-нибудь раздел  в Visual Studio .Net 2003 (Я выбрал Components) и, нажав правую клавишу мыши, выбираем пункт меню “Add/Remove Items”. После чего у Вас откроется окошко с возможностью выбора компонент для дизайнера. После открытия окошка переходим на закладку COM Components и ищем там Microsoft Web Browser. Это стандартный COM объект – он не нуждается в отдельной установке. После нажатия Ok Вы увидите, как появиться компонент в ToolBox.

Хотелось бы отметить, что в VS .Net 2005 этот компонент,  уже в виде .Net компонента, находится в окне ToolBox.

Будем считать, что дизайн будущего приложения уже построен. Теперь нам нужно заполнение дерева.

Не забываем определить Credentials и Url при инициализации нашей ссылки на экземпляр объекта ReportingService:

  private void FormMain_Load(object sender, EventArgs e) 
  { 
         if(!DesignMode) 
         { 
               ... 
               rs = new ReportingService(); 
               rs.Credentials = CredentialCache.DefaultCredentials; 
               this.rs.Url = this.barItemURL.EditValue + "/ReportService.asmx";  
         } 
  } 
  

Так как наши компоненты поддерживают Binding для объектов типа DataSet, я предлагаю воспользоваться этим замечательным качеством и создать простую, универсальную таблицу для возможности использования ее в будущем для навигации по дереву.

 

Следуя логике повторного использования кода, мы берем метод, который был предложен в первой статье для получения списка отчетов и, внеся некоторые модификации, получаем весь список CatalogItems. В данном методе реализовано заполнение таблицы по методу Id-ParentId для построения компонентом дерева. Как это сделать? Есть множество способов и я думаю,  что реализация не вызовет у Вас каких либо затруднений. Я использовал свойство CatalogItem – Path (путь к объекту на RS). Результат «вылился» в такую группу методов.

Основной метод, заполняющий таблицу из DataSet.

  #region [ BuildTree ] 
  public void BuildTree() 
  { 
         this.Cursor = Cursors.WaitCursor; 
         CatalogItem[] items; 
         ReportParameter[] parameters = null;  
         try 
         { 
               items = rs.ListChildren("/", true); // получаем список объектов по данному пути 
               this.ds.Reports.Clear(); 
               foreach(CatalogItem i in items) 
               { 
                      RServDS.ReportsRow drNew = this.ds.Reports.NewReportsRow(); 
                      drNew.ID = i.Name; 
                      if(i.Path.LastIndexOf('/') > 0) 
  drNew.ParentID = i.Path.Substring( i.Path.Substring(0, i.Path.LastIndexOf('/')).LastIndexOf('/') + 1, i.Path.LastIndexOf('/') - 1); 
                      else 
                             drNew.SetParentIDNull(); 
                      switch(i.Type) 
                      { 
                             case ItemTypeEnum.DataSource: 
                                    drNew.ImageIndex = 0; 
                                    break; 
                             case ItemTypeEnum.Folder: 
                                    drNew.ImageIndex = 1; 
                                    break; 
                             case ItemTypeEnum.Report: 
                                    parameters = GetListParameters(i.Path); 
                                    drNew.ImageIndex = 2; 
                                    break; 
                             case ItemTypeEnum.LinkedReport: 
                                    drNew.ImageIndex = 3; 
                                    break; 
                             case ItemTypeEnum.Resource: 
                                   drNew.ImageIndex = 4; 
                                    break; 
                             case ItemTypeEnum.Unknown: 
                                    drNew.ImageIndex = 5; 
                                    break; 
                             default : 
                             break; 
                      } 
                      drNew.Description = i.Description; 
                      drNew.Name = i.Name; 
                      drNew.Path = i.Path; 
                      this.ds.Reports.AddReportsRow(drNew); 
                      AddParametrToParent(parameters, drNew); 
               } 
         } 
         catch (Exception ex) 
         { 
               this.Cursor = Cursors.Default; 
               MessageBox.Show(ex.Message); 
         } 
         this.Cursor = Cursors.Default; 
  } 
  #endregion 
  

Метод, получающий список параметров для конкретного отчета:

  #region [ GetListParameters ]
  public ReportParameter[] GetListParameters(string sReport)
  {
         return rs.GetReportParameters(sReport, null, false, null, null);
  }
  #endregion
  

Метод, дополняющий записи в таблицу из DataSet для параметров:

  #region [ AddParametrToParent ] 
  private void AddParametrToParent(ReportParameter[] parameters, RServDS.ReportsRow drNew) 
  { 
         if(parameters != null && parameters.Length > 0) 
         { 
               foreach(ReportParameter rp in parameters) 
               { 
                      RServDS.ReportsRow drParam = this.ds.Reports.NewReportsRow(); 
                      drParam.ID = rp.Name; 
                      drParam.ParentID = drNew.ID; 
                      drParam.ImageIndex = 6; 
                      drParam.Name = rp.Name; 
  drParam.Path = (rp.DefaultValues != null && rp.DefaultValues.Length > 0) ?   rp.DefaultValues[0]  : String.Empty; 
  drParam.Description = (rp.DefaultValues != null && rp.DefaultValues.Length > 0) ? "Current value: " + rp.DefaultValues[0]  : "No default value"; 
                      this.ds.Reports.AddReportsRow(drParam); 
               } 
         } 
  } 
  #endregion 
  

Надо сказать, что в последнем методе я слукавил и в поле Path записал значения «по умолчанию» для параметров данного отчета. Мне не хотелось добавлять еще одно поле для этих значений, хотя по большому счету добавить было бы более правильно.

Теперь есть реализация заполнения дерева.

Немного теории.

Переходим к самому интересному, и, пожалуй, самому тонкому моменту:  рендерингу отчетов. Здесь нужно сделать краткое отступление и сослаться на теорию.  

Различают два основных способа взаимодействия с Reporting Services. Первый - это доступ через ссылку (URL Access) и второй, непосредственный, с использованием методов Reporting Service Web Service.

Основой первого метода, как Вы уже догадались, есть «правильно» построенная строка вызова URL. Базовый синтаксис этой строки выглядит следующим образом:

http://server/virtualroot?[/pathinfo]&[prefix:]param=value[&[prefix:]param=value]...n]

В котором имеем следующее:

server – здесь все просто: это имя сервера на котором собственно и предоставляется доступ к Reporting Services.

Virtualroot – виртуальная директория, настроенная Вами при установке RS. По умолчанию это директория ReportServer.

pathinfo – это название самого отчета, которое сконфигурировано в административном модуле. 

[prefix:]param=value[&[prefix:]param=value]... – это набор все возможных параметров при генерации отчета. На них мы остановимся чуть подробнее, потому что как раз они и заслуживают больше всего нашего внимания.

Поработаем сначала с Internet Explorer. Для начала проверим папку с RS – в моем случае это: http://freelancer/ReportServer

Получаем картинку, представленную мной. Отличие будет в зависимости от созданной иерархии на Вашем сервере:

 

Если увидели подобную картинку, значит у Вас все настроено правильно. И RS работает нормально.

Для начала хочется посмотреть содержимое папки Catalog. Для этого формируем строку:

http://freelancer/ReportServer?%2fCatalog&rs:Command=ListChildren

rs:Command=ListChildren – как  Вы успели заметить, эта команда отвечает за получения списка для директории.

Далее. Допустим, я хочу сразу же загрузить отчет под названием Product Catalog, находящийся в папке Catalog.

Для этого формируем строку с названием нужного нам отчета и даем команду генерации отчета: rs:Command=Render. При этом нам главное не забыть указать кроме папки, где лежит наш отчет еще и имя отчета. Получаем вот такую строку.

http://freelancer/ReportServer?%2fCatalog%2fProduct+Catalog&rs:Command=Render

В результате чего нам сформируется отчет:

Если же мы хотим просмотреть содержимое DataSource, мы формируем строку следующего формата:

http://freelancer/ReportServer?%2fAdventureWorks&rs:Command=GetDataSourceContents

Обратите внимание, здесь использована еще  одна команда, выводящая непосредственно содержимое  DataSource: rs:Command=GetDataSourceContents.

После выполнения данного запроса на выходе получим XML структуру следующего вида:

  <DataSourceDefinition>
    <Extension>SQL</Extension> 
    <ConnectString>data source="(local)";persist security info=False;initial catalog=AdventureWorks2000</ConnectString> 
    <CredentialRetrieval>Integrated</CredentialRetrieval> 
    <Enabled>True</Enabled> 
    </DataSourceDefinition>
  

Вы можете также самостоятельно поэкспериментировать с командами, переходя в Internet Explorer прямо по ссылкам.

На чем мне еще хотелось бы акцентировать Ваше внимание это на генерацию строки с параметрами для отчета.  rs – это команды управления и просмотра отчетов, а rc – команды это как раз именно тот тип, через который Вы сможете управлять параметрами вызываемых отчетов.

Краткий обзор команд и параметров.

То, что может быть использовано с командой типа rs:

  • Command. Это активные команды генерации.
    • GetDataSourceContents – показывает содержимое DataSources (пример был приведен выше)
    • GetResourceContents – то же что и GetDataSourceContents, но для ресурсов RS.
    • ListChildren – показывает содержимое папок.
    • Render – вызывает генерацию конкретного отчета.
  • Format. Это группа команд для форматов генерации отчетов.
    • Web форматы:
      • HTML3.2
      • HTML4.0
      • MHTML
    • Форматы для печати:
      • IMAGE
      • PDF
    • Форматы данных:
      •  EXCEL
      • CSV
      • XML
    • Специальный формат
        NULL  - используется для NULL генерации отчета. Такой формат, например, удобно использовать для создания отчета в кеше.
  • Snapshot. Используется для создания моментальных снимков.

Пример использования rc параметров может служить, например, «погашение» окна с параметрами для отчета. (&rc:Parameters=False)

http://freelancer/ReportServer?%2fSales+Report%2fProduct+Line+Sales&rs:Command=Render&rc:Parameters=False

или, например, генерации отчета в файл типа JPEG. (&rc:OutputFormat=JPEG):

http://freelancer/ReportServer?%2fSales+Report%2fProduct+Line+Sales&rs:Command=Render&rs:Format=IMAGE&rc:Parameters=False&rc:OutputFormat=JPEG

Кроме того, существуют еще префиксы dsu и dsp. Эти префиксы используются для парольного доступа к БД. Например, если мы хотим создать подключение к БД AdventureWorks2000, с именем guest и паролем guestPass, к нашему URL добавится следующая конструкция:

&dsu:AdventureWorks2000=guest&dsp:AdventureWorks2000=guestPass

Но здесь есть нюанс. Для работы с этими параметрами Вам нужно будет использовать криптование на базе SSL (Secure Socket Layers).

И последнее, на чем бы хотелось остановить Ваше внимание, это на вызове отчета с предустановленными параметрами. Здесь все просто:

http://freelancer/ReportServer?%2fSales+Report%2fProduct+Line+Sales&rs:Command=Render&StartDate=01/10/2005&EndDate=01/11/2005 

Единственная сложность в том, что нужно заранее знать названия параметров.

Продолжение реализации.

Из этого небольшого экскурса теории Вы заметили, что основой генерации отчета при помощи URL является знание и правильная комбинация параметров и команд. В своем приложении я сформировал универсальный метод генерации строки для вызова отчета. Параметры хранятся в хеш массиве.  Для начала определим метод установки параметров в этом массиве:

  #region [ SetParameter ] 
  /// <summary> 
  /// Add or remove url access string properties.  
  /// </summary> 
  /// <param name="name"></param> 
  /// <param name="value"></param> 
  private void SetParameter(string name, string value) 
  { 
         try 
         { 
               // Remove if value is null or empty. Value is null of the property grid value 
               // is null or empty. Empty or null removes the property from the Hashtable. 
               if(value == null | value == String.Empty ) 
                             { 
                      this._properties.Remove(name); 
               } 
               else 
               { 
                      if(this._properties.ContainsKey(name)) 
                      { 
                             // Change if key exists 
                             this._properties[name] = value; 
                      } 
                      else 
                      { 
                             // Add if key does not exist 
                             this._properties.Add(name, value); 
                      } 
               } 
               // Build a new url string 
               this.BuildUrlString(); 
         } 
         // Catch and handle a more specific exception in a propduction application. 
         catch(Exception ex) 
         { 
               // Sample throws the exception to the client 
               throw ex; 
         } 
  } 
  #endregion 
  

Далее сам метод построения адреса генерации отчета:

  #region [ BuildUrlString ] 
  /// <summary> 
  /// Add URL access command for rendering a report and any 
  /// additional parameters. 
  /// </summary> 
  public string BuildUrlString() 
  { 
         //     Url: Full report url. 
  //     ServerUrl: Server url such as http://localhost/reportserver 
         //     ReportPath: Report path such as /SampleReports/Company Sales 
  string _url = this.barItemURL.EditValue + "?" +  
         this.GetCurrentRow().Path.Replace(' ', '+') +  
                "&rs:Command=Render" + this.EmumProperties(this._properties); 
         return _url; 
  } 
  #endregion 
  

В этом методе используется метод для доступа к BindingContext. Это позволяет работать с текущей, выбранной строкой.

  #region [ GetCurrentRow ] 
  public RServDS.ReportsRow GetCurrentRow() 
  { 
         try 
         { 
         // Опять парни из Developer Express нахомутали с биндингом для своего компонента. ПОтому приходися 
         // заменять нормальный алгоритм получения стандартной строки на "через одно место" :(  
  //return (RServDS.ReportsRow)((DataRowView)this.BindingContext[this.ds.Reports].Current).Row; 
                if(this.treeList.FocusedNode != null) 
  return (RServDS.ReportsRow)((DataRowView)this.treeList.GetDataRecordByNode(this.treeList.FocusedNode)).Row; 
                else 
                      return null; 
         } 
         catch (Exception ex) 
         { 
               MessageBox.Show(ex.Message, "Ошибка!!!"); 
               return null; 
         } 
  } 
  #endregion 
  

Кроме того, необходим метод инициализации параметров:

  #region [ EmumProperties ] 
  /// <summary> 
  /// Enumerate Hashtable and create report server access specific string. 
  /// </summary> 
  /// <param name="properties"></param> 
  /// <returns></returns> 
  private string EmumProperties(Hashtable properties) 
  { 
         string paramsString = String.Empty; 
         // Enumerate properties and create report server specific string. 
         IDictionaryEnumerator customPropEnumerator = properties.GetEnumerator(); 
         while ( customPropEnumerator.MoveNext() ) 
         { 
               paramsString += "&"  
                      + customPropEnumerator.Key  
                      + "=" + customPropEnumerator.Value; 
         } 
         return paramsString; 
  } 
  #endregion
  

Все для генерации отчетов у нас готово. Мы можем убедиться в этом, поработав с получившимся интерфейсом. Я не буду сейчас останавливаться на, формах-спутниках, обработке нажатий мыши и т.д. – я думаю, что в коде Вы легко сможете разобраться. После двойного клика мышкой на отчет Product Catalog в папке Catalog результат должен выглядеть следующим образом:

Работа с WS Reporting Services

Выше мы рассмотрели работу с URL строкой для генерации отчетов. Сейчас я предлагаю сделать еще один эксперимент. Мы попробуем сгенерировать отчеты в любой допустимый для RS выходной формат без использования URL строки, а исключительно при помощи Web методов.

Для этого создадим универсальный метод для сохранения файла отчета:

  #region [ SaveFile ] 
  public void SaveFile(string sReport, string sFormat, string sExt) 
  { 
         Stream myStream ; 
         BinaryWriter bw; 
         string encoding, mimeType; 
         Warning[] warnings; 
         string[] streamIds; 
         ParameterValue[] parametersUsed; 
         saveFileDialog.Filter = sExt; 
         saveFileDialog.FilterIndex = 1 ; 
         saveFileDialog.RestoreDirectory = true ; 
         if(saveFileDialog.ShowDialog() == DialogResult.OK) 
         { 
               if((myStream = saveFileDialog.OpenFile()) != null) 
               { 
                      byte[] data = rs.Render(sReport, sFormat, null, null, 
                             GetParametersValue(this._properties), null, null,  
                             out encoding, out mimeType, out parametersUsed,  
                             out warnings, out streamIds); 
                      bw = new BinaryWriter(myStream); 
                      bw.Write(data, 0, data.Length); 
                      myStream.Close(); 
                      MessageBox.Show("File written to: " + saveFileDialog.FileName); 
               } 
         } 
  } 
  #endregion 
  

Для него потребуется еще один метод генерирующий значения параметров из хеш массива.

  #region [ GetParametersValue ] 
  private ParameterValue[] GetParametersValue(Hashtable properties) 
  { 
         ParameterValue[] retValues = new ParameterValue[properties.Count]; 
         // Enumerate properties and create report server specific string. 
         IDictionaryEnumerator customPropEnumerator = properties.GetEnumerator(); 
         int i = 0; 
         while ( customPropEnumerator.MoveNext() ) 
         { 
               retValues[i] = new ParameterValue(); 
               retValues[i].Name = customPropEnumerator.Key.ToString(); 
               retValues[i].Value = customPropEnumerator.Value.ToString(); 
               i++; 
         } 
         return retValues; 
  } 
  #endregion 
  

В данном примере за основу взят web метод Render. Поэкспериментировав с оболочкой, нажимая правую клавишу мышки над отчетами в дереве, Вы сможете сохранить файлы экспорта в различных, доступных форматах.

Послесловие.

Очень надеюсь на то, что мне удалось и в этой статье передать Вам частичку своих знаний, которые помогут Вам создавать еще более удобные приложения со столь удачными продуктами Microsoft как Reporting Services. Возможно, я буду развивать данный продукт, наращивая в нем функциональность, но мне также очень хотелось бы услышать и Ваше мнение по данному продукту.

Загрузить исходный код Вы можете здесь.

Загрузить скомпилированный модуль Вы можете здесь.

С уважением, Serg Vorontsov aka V©R©N.


Может пригодится:


Автор: Serg Vorontsov
Прочитано: 5979
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру