Изготовление отчетов в ASP.NET2
Одна из самых востребованных и распростаненных задач в любой компании - печать различных буклетов, рекламных предложений, прайс-листов и тд. При подготовке этих документов для типографии есть ряд хитростей:
- Техника работы с такого рода документами должна включать этап РУЧНОЙ корректировки.
- Во всякой компании существуют ОФИЦИАЛЬНЫЕ издания и РАБОЧИЕ варианты, в которых есть свежайшая, но пока официально не опубликованная для всех информация.
- Все такого рода буклеты состоят из МНОЖЕСТВА разделов, представляющий собой различные отборы по базе и статические компоненты, например обложки. Все это должно быть сброшюровано с единой нумерацией страниц.
- Со временем эти списки могут меняться и технология должна подразумевать возможность изменения структуры этого буклета и включения в него раздичных дополнительных списков и фрагментов.
Всем этим условиям отвечает описанная технология.
Вся страничка для удобства разделена на отдельные табы. Это, с одной стороны, позволяет разделить функиональность администрирования буклетов, а с другой стороны - это довольно жестокая необходимость, ибо все компоненты тут перенасыщены клиентским кодом и уживаются друг с другом плохо. Так вот, я выделил в этой задачке четыре таба, которые показаны на рисунке (сорри за дизайн, возможно позже я переделаю скрины - на момент этого описания его еще просто не существует):
Переключение табов выполняет вот такой небольшой контрольчик, который имитирует TabStrip для виндузовых приложений (клиентский TabStrip WebControl я по целому ряду причин не вижу смысла тут использовать). Текст этого контрольчика выглядит не просто, а очень просто (что, однако, не помешает использовать вам его в своих приложениях при необходимости):
00001: <%@ Control Language="VB" AutoEventWireup="false" CodeFile="TabStrip.ascx.vb" Inherits="TabStrip" %> 00002: <table> 00003: <tr> 00004: <td style="height: 26px"> 00005: <asp:Button ID="bt0" runat="server" Text="Check service" Width="130px" /></td> 00006: <td style="height: 26px"> 00007: <asp:Button ID="bt1" runat="server" Text="Upload booklet part" Width="130px" /></td> 00008: <td style="height: 26px"> 00009: <asp:Button ID="bt2" runat="server" Text="Get report from DB" Width="130px" /></td> 00010: <td style="height: 26px; width: 133px;"> 00011: <asp:Button ID="bt3" runat="server" Text="ReportList" Width="130px" /></td> 00012: </tr> 00013: </table>
00001: Partial Class TabStrip 00002: Inherits System.Web.UI.UserControl 00003: 00004: <ComponentModel.Category("My")> _ 00005: <ComponentModel.Description("Имя странички, куда надо редиректится")> _ 00006: Public Property PagePlaceName() As String 00007: Get 00008: PagePlaceName = ViewState("PagePlaceName") 00009: End Get 00010: Set(ByVal value As String) 00011: ViewState("PagePlaceName") = value 00012: End Set 00013: End Property 00014: 00015: Public Enum ViewNumber 00016: V0 = 0 00017: V1 = 1 00018: V2 = 2 00019: V3 = 3 00020: End Enum 00021: 00022: Protected Sub bt0_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles bt0.Click 00023: Session("TabStrip_Selected") = ViewNumber.V0 00024: Server.Transfer(ViewState("PagePlaceName") & "?T=" & ViewNumber.V0) 00025: End Sub 00026: 00027: Protected Sub bt1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles bt1.Click 00028: Session("TabStrip_Selected") = ViewNumber.V1 00029: Server.Transfer(ViewState("PagePlaceName") & "?T=" & ViewNumber.V1) 00030: End Sub 00031: 00032: Protected Sub bt2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles bt2.Click 00033: Session("TabStrip_Selected") = ViewNumber.V2 00034: Server.Transfer(ViewState("PagePlaceName") & "?T=" & ViewNumber.V2) 00035: End Sub 00036: 00037: Protected Sub bt3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles bt3.Click 00038: Session("TabStrip_Selected") = ViewNumber.V3 00039: Server.Transfer(ViewState("PagePlaceName") & "?T=" & ViewNumber.V3) 00040: End Sub 00041: 00042: Protected Sub TabStrip_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00043: If Not IsPostBack Then 00044: bt0.BorderStyle = BorderStyle.Outset 00045: bt1.BorderStyle = BorderStyle.Outset 00046: bt2.BorderStyle = BorderStyle.Outset 00047: bt3.BorderStyle = BorderStyle.Outset 00048: bt0.ForeColor = Drawing.Color.Black 00049: bt1.ForeColor = Drawing.Color.Black 00050: bt2.ForeColor = Drawing.Color.Black 00051: bt3.ForeColor = Drawing.Color.Black 00052: Dim S As ViewNumber 00053: If Session("TabStrip_Selected") IsNot Nothing Then 00054: S = Session("TabStrip_Selected") 00055: Select Case S 00056: Case ViewNumber.V0 : bt0.BorderStyle = BorderStyle.Inset : bt0.ForeColor = Drawing.Color.Blue 00057: Case ViewNumber.V1 : bt1.BorderStyle = BorderStyle.Inset : bt1.ForeColor = Drawing.Color.Blue 00058: Case ViewNumber.V2 : bt2.BorderStyle = BorderStyle.Inset : bt2.ForeColor = Drawing.Color.Blue 00059: Case ViewNumber.V3 : bt3.BorderStyle = BorderStyle.Inset : bt3.ForeColor = Drawing.Color.Blue 00060: End Select 00061: End If 00062: End If 00063: End Sub 00064: End Class
Итак, теперь понятно, что главная страничка выглядит из четырех фрагментов:
00001: <%@ Page Language="VB" MasterPageFile="~/MasterPages/Manager.master" AutoEventWireup="false" CodeFile="Admin.aspx.vb" Inherits="Manager_PDF_Uploader" ValidateRequest="false" title="Untitled Page" %> 00002: 00003: <%@ Register Src="TabStrip.ascx" TagName="TabStrip" TagPrefix="uc3" %> 00004: <%@ Register Src="DynamicReport.ascx" TagName="DynamicReport" TagPrefix="uc2" %> 00005: <%@ Register Src="StoredPDF.ascx" TagName="GetPDF" TagPrefix="uc1" %> 00006: 00007: 00008: <asp:Content ID="Content2" ContentPlaceHolderID="cphMainContent" Runat="Server"> 00009: <uc3:TabStrip ID="TabStrip1" runat="server" PagePlaceName="Admin.aspx" /> 00010: <table><tr><td style="width: 95px">Semester</td><td> 00011: <asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="ShowWebSemester" 00012: DataTextField="Semester_lang" DataValueField="SemesterID" Width="300px" AutoPostBack="True" > 00013: </asp:DropDownList></td></tr> 00014: </table> 00015: 00016: <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> 00017: <asp:View ID="View0" runat="server"> 00018: <uc1:GetPDF ID="GetPDF1" runat="server" /> 00019: </asp:View> 00020: 00021: <asp:View ID="View1" runat="server"> ...... 00093: 00094: </asp:View> 00095: <asp:View ID="View2" runat="server"> 00096: <uc2:DynamicReport ID="DynamicReport1" runat="server" /> 00097: 00098: 00099: </asp:View> 00100: <asp:View ID="View3" runat="server"> 00101: <asp:TextBox ID="TextBox4" runat="server" MaxLength="1000" Rows="20" TextMode="MultiLine" ValidationGroup="xml" Width="50%"></asp:TextBox> 00102: <br /> 00103: <asp:Button ID="bLoad" runat="server" Text="Load" /> 00104: <asp:Button id="bSave" runat="server" Text="Save" ValidationGroup="xml" /> 00105: 00106: <asp:RequiredFieldValidator ID="RequiredFieldValidator5" runat="server" ControlToValidate="TextBox4" 00107: ErrorMessage="*" ValidationGroup="xml"></asp:RequiredFieldValidator> 00108: <asp:Label ID="ErrXML" runat="server"></asp:Label></asp:View> 00109: 00110: </asp:MultiView> 00111: </asp:Content>
переключение которых на главной форме выполняется всего навсего одной строчкой - номер 36:
00001: Imports CrystalDecisions.CrystalReports.Engine, CrystalDecisions.Shared 00002: 00003: Partial Class Manager_PDF_Uploader 00004: Inherits System.Web.UI.Page 00005: 00006: Protected Sub Manager_PDF_Uploader_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00007: 'параметры для обращения к BLL 00008: If Session("CourseListMode") Is Nothing Then Session("CourseListMode") = SALWeb.Courses.BusinessLogicLayer.Course_Mode.FillBySemester 00009: If Session("SemesterID") Is Nothing Then 00010: Dim CMD As New siSMWeb.ExecSQL_RDR() 00011: Dim RDR = CMD.ExecSQL("dbo.nc_SemesterSelectCurrent") 00012: If RDR.Read Then 00013: Session("SemesterID") = RDR("SemesterID").ToString 00014: End If 00015: CMD.Close() 00016: End If 00017: 'Инициализация контрола 00018: If DropDownList1.SelectedItem IsNot Nothing Then 00019: GetPDF1.SemesterID = DropDownList1.SelectedValue 00020: End If 00021: 'табличка уже загруженных компонентов 00022: If Not IsPostBack Then 00023: Me.DataBind() 00024: End If 00025: 'Если это был постбек от кристала - то восстановить его обязательно, чтоб он не потерял состояние 00026: If IsPostBack Then 00027: For Each OneItem As String In Request.Form 00028: If OneItem.Contains("CrystalReportViewer") Then 00029: 'надо восстановить состояние контрола 00030: DynamicReport1.LoadReport = True 00031: End If 00032: Next 00033: End If 00034: If Not IsPostBack Then 00035: 'был редирект (или самое начало) - переключаем Multiview 00036: MultiView1.ActiveViewIndex = CInt(Request.QueryString("T")) 00037: End If 00038: End Sub 00039: 00040: Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList1.SelectedIndexChanged 00041: Session("SemesterID") = DropDownList1.SelectedValue 00042: End Sub
Кроме того, в дизайн тайме надо задать атрибут контрола - имя странички, на которой он будет переключать MultiView. Кроме мультивью, на этой форме расположен только комбешник с выбором семестров, который устанавливает Сессион в строке 41, которую будут при необходимости читать плагины, формирующие данные из базы.
Итак, вводная часть закончена, перейдем непосредственно к описанию придуманной мной технологии.
Как я говорил вначале, одним из условий решения этой задачи является ее масштабируемость, те возможность расширять и расширять без ограничений перечни формируемых отчетов и делать со временем все более и более информационно-насыщенные буклеты.
Для реализации этой задачи я придумал ряд решений:
- Список отчетов, который пользователь может сам расширять до бесконечности.
- Редактор этого списка, который вы видите на последней вкладке справа.
- Плагины, которые можно добавлять неограниченно, которые формируют рекордсеты - не затрагивая собственно движка отчета.
- Параметры в конфигурационном списке отчетов, которые можно произвольно добавлять для каждого вновь создаваемого отчета.
Начнем с редактора отчетов - последняя вкладка справа на этой станичке ReportList. Код этой вкладки строки 00099-00108. А текст будет выглядеть так:
00074: Protected Sub bLoad_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles bLoad.Click 00075: Dim SR As New System.IO.StreamReader(Server.MapPath("~\App_Code\CrystalBLL\CrystalSourceList.xml")) 00076: TextBox4.Text = SR.ReadToEnd 00077: SR.Close() 00078: End Sub 00079: 00080: Protected Sub bSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles bSave.Click 00081: Dim ValidationConfig As New System.IO.StringReader(TextBox4.Text) 00082: Dim XmlConfigFile As New System.Xml.XmlTextReader(ValidationConfig) 00083: ' 00084: Dim XSD As System.Xml.XmlReaderSettings = New System.Xml.XmlReaderSettings 00085: XSD.ValidationType = System.Xml.ValidationType.Schema 00086: Dim CallBackDelegate As New System.Xml.Schema.ValidationEventHandler(AddressOf ValidationCallback) 00087: AddHandler XSD.ValidationEventHandler, CallBackDelegate 00088: XSD.Schemas.Add(Nothing, System.Xml.XmlReader.Create(Server.MapPath("~\App_Code\CrystalBLL\CrystalSourceList1.xsd"))) 00089: ' 00090: Dim Validator As System.Xml.XmlReader = System.Xml.XmlReader.Create(XmlConfigFile, XSD) 00091: ' 00092: Try 00093: While Validator.Read : End While 00094: Catch ex As Exception 00095: ErrXML.Text = ex.Message 00096: Exit Sub 00097: Finally 00098: XmlConfigFile.Close() 00099: End Try 00100: ' 00101: If ErrorList = "" Then 00102: Dim SW As New System.IO.StreamWriter(Server.MapPath("~\App_Code\CrystalBLL\CrystalSourceList.xml")) 00103: SW.Write(TextBox4.Text) 00104: SW.Close() 00105: ErrXML.Text = "" 00106: Else 00107: ErrXML.Text = ErrorList 00108: End If 00109: End Sub 00110: 00111: 00112: Dim ErrorList As String = "" 00113: Private Sub ValidationCallback(ByVal sender As Object, ByVal args As System.Xml.Schema.ValidationEventArgs) 00114: ErrorList &= args.Message 00115: End Sub 00116: 00117: End Class
Как видите, код без особых изысков и основное его назначение - проверить составить юзером XML на соответствие схеме. Если это не так - расплата произойдет немедленно. XMLDataSource, читающий список этих отчетов для комбешника (который мы увидим дальше) - ругнется и приложение упадет немедленно.
Схема, не позволяющая дополнять дополнительные атрибуты (параметры отчетов), но не позволяющая упасть приложению, выглядит так:
00001: <?xml version="1.0" encoding="utf-8"?> 00002: <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> 00003: <xs:element name="Crystal"> 00004: <xs:complexType> 00005: <xs:sequence> 00006: <xs:element maxOccurs="unbounded" name="Report"> 00007: <xs:complexType> 00008: <xs:attribute name="ReportSourceFile" type="xs:string" use="required" /> 00009: </xs:complexType> 00010: </xs:element> 00011: </xs:sequence> 00012: </xs:complexType> 00013: </xs:element> 00014: </xs:schema>
А для того, чтобы в конфигурационном файле можно было бы задавать ДОПОЛНИТЕЛЬНЫЕ параметры отчетов, после восьмой строки схемы нужно добавить строку:
<xs:anyAttribute processContents="lax"/>
Теперь рассмотрим вкладку Get Report From DB. Это основная рабочая вкладка для работы с динамически-формируемыми из базы отчетами. Список этих отчетов может дополняться произвольно в любой момент. В применении, например, к институту эти перечни отчетов могут быть такие например: списки аудиторий, расписание занятий, списки катерогий курсов (факультетов), списки преподавателей, список сотрудников администрации и так далее. Все они собираются в итоге в единый буклет. Поэтому для каждого списка предусмотрен начальный номер странички.
Как я уже говорил, каждый отдельный фрагмент списка выпускается по своему - один на основе просто хранимой процедуры в базе, другой на основе обращения к бизнес-обьекту, третий просто выводит некоторые списки из базы. Все эти отдельные отчеты формируются независимо друг от друга и мы, редактируя в последней вкладке список отчетов, можем например просто вычекнуть некий список из общего перечня. Или сделать взамен его новый. Или задать какой-то параметр отчета - например форматирование в два столбца или форматирование в три столбца. При этом каждый отдельный формат отчета и его плагин совершенно независим от другого списка.
Вывод всех полученных списков обеспечивает контрол DynamicReport, который обеспечивает получение PDF-файла на клиентской машине и автоматически работает по тому списку, который мы редактировали на предыдущей вкладке.
Рассмотрим следующий компонент системы - Upload Booklet Part. Как вы поняли, в результате работы модуля формирования отчета на клиентской машине в Adobe Acrobat у нас оказываются все выгруженные из базы и последовательно пронумерованные странички отчетов. Кроме того, могут быть какие-то дополнительные странички, например обложки или рекламные вставки. Одно из условий проектирования этого движка - возможность ручной корректироваки отчетов. Именно для этого они выгружаются в Акробат на клиенте, где из них составляется вручную уже полнокомплектный буклет. Поэтому следующая часть будет посвящена описанию того движка, который позволяет эти сформированные брошюры (или их части) загружать в базу и помечат их либо официальными (ShowWeb=True) - доступным всем, либо просто рабочими, доступными для выгрузки и просмотра только уполномоченным лицам.
На форме эта вкладка View1 выглядит так:
00022: <table> 00023: <tr> 00024: <td> 00025: Booklet Part<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1" 00026: ErrorMessage="*" ValidationGroup="upload"></asp:RequiredFieldValidator></td> 00027: <td style="width: 93px"> 00028: <asp:TextBox ID="TextBox1" runat="server" MaxLength="50" Width="300px"></asp:TextBox></td> 00029: </tr> 00030: <tr> 00031: <td> 00032: StartPage 00033: <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="TextBox2" 00034: ErrorMessage="*" ValidationGroup="upload"></asp:RequiredFieldValidator> 00035: <asp:RangeValidator ID="RangeValidator1" runat="server" ControlToValidate="TextBox2" 00036: ErrorMessage="*" MaximumValue="200" MinimumValue="0" ValidationGroup="upload"></asp:RangeValidator> 00037: </td> 00038: <td style="width: 93px"> 00039: <asp:TextBox ID="TextBox2" runat="server" Width="300px"></asp:TextBox></td> 00040: </tr> 00041: <tr> 00042: <td> 00043: EndPage+1<asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ControlToValidate="TextBox3" 00044: ErrorMessage="*" ValidationGroup="upload"></asp:RequiredFieldValidator> 00045: <asp:RangeValidator ID="RangeValidator2" runat="server" ControlToValidate="TextBox3" 00046: ErrorMessage="*" MaximumValue="200" MinimumValue="1" ValidationGroup="upload"></asp:RangeValidator> 00047: <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToCompare="TextBox2" 00048: ControlToValidate="TextBox3" ErrorMessage="*" Operator="GreaterThanEqual" ValidationGroup="upload"></asp:CompareValidator></td> 00049: <td style="width: 93px"> 00050: <asp:TextBox ID="TextBox3" runat="server" Width="300px"></asp:TextBox></td> 00051: </tr> 00052: <tr> 00053: <td> 00054: PDF-file<asp:RequiredFieldValidator ID="RequiredFieldValidator4" runat="server" ControlToValidate="FileUpload1" 00055: ErrorMessage="*" ValidationGroup="upload"></asp:RequiredFieldValidator> 00056: <asp:Label ID="Label1" runat="server" ForeColor="Red" Text="PDF only" Visible="False"></asp:Label></td> 00057: <td style="width: 93px"> 00058: <asp:FileUpload ID="FileUpload1" runat="server" Width="300px" /></td> 00059: </tr> 00060: <tr> 00061: <td> 00062: <asp:CheckBox ID="CheckBox1" runat="server" Text="ShowWeb" /></td> 00063: <td style="width: 93px"> 00064: <asp:Button ID="Button1" runat="server" Text="Upload" ValidationGroup="upload" /> 00065: </td> 00066: </tr> 00067: </table> 00068: <asp:SqlDataSource ID="ShowWebSemester" runat="server" ConnectionString="<%$ ConnectionStrings:siSchoolManagerConnectionString %>" 00069: SelectCommand="nc_SemesterSelect" SelectCommandType="StoredProcedure"></asp:SqlDataSource> 00070: <asp:Literal ID="Literal1" runat="server" Text="<br><hr><br>"></asp:Literal> 00071: <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" BorderColor="Black" BorderStyle="Solid" BorderWidth="1px" CellPadding="1" CellSpacing="1" AutoGenerateColumns="False"> 00072: <Columns> 00073: <asp:BoundField DataField="Part" HeaderText="Part" SortExpression="Part" /> 00074: <asp:BoundField DataField="StartPage" HeaderText="StartPage" SortExpression="StartPage" /> 00075: <asp:BoundField DataField="EndPage" HeaderText="EndPage" SortExpression="EndPage" /> 00076: <asp:BoundField DataField="CreateDate" HeaderText="CreateDate" SortExpression="CreateDate" /> 00077: <asp:CheckBoxField DataField="ShowWeb" HeaderText="ShowWeb" SortExpression="ShowWeb" /> 00078: <asp:ButtonField ButtonType="Button" Text="Button" /> 00079: <asp:TemplateField> 00080: <ItemTemplate> 00081: <asp:HiddenField ID="HiddenField1" runat="server" Value='<%# Bind("i", "{0}") %>' /> 00082: </ItemTemplate> 00083: </asp:TemplateField> 00084: </Columns> 00085: </asp:GridView> 00086: <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:siSchoolManagerConnectionString %>" 00087: SelectCommand="nc_Booklet_GetDescr" SelectCommandType="StoredProcedure"> 00088: <SelectParameters> 00089: <asp:ControlParameter ControlID="DropDownList1" Name="SemesterID" PropertyName="SelectedValue" 00090: Type="String" /> 00091: </SelectParameters> 00092: </asp:SqlDataSource>
Как видите, это обычный аплоадер плюс редактируемая сетка, в одно из скрытых полей которой я вложил номер редактируемой строки в базе.
00044: Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 00045: If System.IO.Path.GetExtension(FileUpload1.FileName).ToLower <> ".pdf" Then 00046: Label1.Visible = True 00047: Exit Sub 00048: End If 00049: If FileUpload1.PostedFile.ContentType <> "application/pdf" Then 00050: Label1.Visible = True 00051: Exit Sub 00052: End If 00053: Dim Len1 As Integer = FileUpload1.PostedFile.InputStream.Length 00054: Dim Buf1(Len1) As Byte 00055: FileUpload1.PostedFile.InputStream.Read(Buf1, 0, Len1) 00056: Dim X As New siSMWeb.ExecSQL_RDR("@SemesterID", DropDownList1.SelectedValue, "@Part", TextBox1.Text, "@PDF", Buf1, "@StartPage", TextBox2.Text, "@EndPage", TextBox3.Text, "@ShowWeb", CheckBox1.Checked) 00057: X.ExecSQL("dbo.nc_Booklet_Ins") 00058: Buf1 = Nothing 00059: GridView1.DataBind() 00060: End Sub
На форме эта функциональность обеспечивается всего двумя процедурами загрузки документа в базу и выгрузки его оттуда.
00044: Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 00045: If System.IO.Path.GetExtension(FileUpload1.FileName).ToLower <> ".pdf" Then 00046: Label1.Visible = True 00047: Exit Sub 00048: End If 00049: If FileUpload1.PostedFile.ContentType <> "application/pdf" Then 00050: Label1.Visible = True 00051: Exit Sub 00052: End If 00053: Dim Len1 As Integer = FileUpload1.PostedFile.InputStream.Length 00054: Dim Buf1(Len1) As Byte 00055: FileUpload1.PostedFile.InputStream.Read(Buf1, 0, Len1) 00056: Dim X As New siSMWeb.ExecSQL_RDR("@SemesterID", DropDownList1.SelectedValue, "@Part", TextBox1.Text, "@PDF", Buf1, "@StartPage", TextBox2.Text, "@EndPage", TextBox3.Text, "@ShowWeb", CheckBox1.Checked) 00057: X.ExecSQL("dbo.nc_Booklet_Ins") 00058: Buf1 = Nothing 00059: GridView1.DataBind() 00060: End Sub 00061: 00062: Protected Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles GridView1.RowCommand 00063: Dim i As Integer = CType(GridView1.Rows(e.CommandArgument).Cells(6).Controls(1), System.Web.UI.WebControls.HiddenField).Value 00064: Dim CMD As New siSMWeb.ExecSQL_RDR("@i", i) 00065: Dim RDR = CMD.ExecSQL("dbo.nc_Booklet_GetPDF") 00066: If RDR.Read Then 00067: Dim M = New System.IO.MemoryStream(RDR.GetSqlBytes(0).Buffer, 0, RDR.GetSqlBytes(0).Buffer.Length - 1) 00068: Response.ContentType = "application/pdf" 00069: M.WriteTo(Response.OutputStream) 00070: Response.End() 00071: End If 00072: End Sub
Поскольку народ иногда путается как работать с бинарными документами, я приведу тут структуру базы и тексты этих двух процедур.
00001: CREATE TABLE [dbo].[Booklet]( 00002: [i] [int] IDENTITY(1,1) NOT NULL, 00003: [SemesterID] [uniqueidentifier] NOT NULL, 00004: [Part] [nvarchar](50) NOT NULL, 00005: [StartPage] [int] NOT NULL, 00006: [EndPage] [int] NOT NULL, 00007: [CreateDate] [datetime] NOT NULL, 00008: [PDF] [varbinary](max) NOT NULL, 00009: [ShowWeb] [bit] NOT NULL 00010: ) ON [PRIMARY] 00011: 00012: CREATE procedure [dbo].[nc_Booklet_Ins] 00013: @SemesterID uniqueidentifier, 00014: @Part nvarchar(50), 00015: @PDF varbinary(max), 00016: @StartPage int, 00017: @EndPage int, 00018: @ShowWeb bit 00019: as 00020: INSERT [Booklet]([SemesterID],[Part],[PDF],[StartPage],[EndPage],[CreateDate],ShowWeb) 00021: VALUES (@SemesterID ,@Part, @PDF, @StartPage, @EndPage, getdate(),@ShowWeb) 00022: 00023: Create procedure [dbo].[nc_Booklet_GetPDF] 00024: @i int 00025: as 00026: SELECT PDF FROM [Booklet]where i=@i 00027: 00028: CREATE procedure [dbo].[nc_Booklet_GetPart] 00029: @SemesterID uniqueidentifier, 00030: @ShowWeb bit 00031: as 00032: SELECT i,[Part] FROM [Booklet] B1 00033: where SemesterID=@SemesterID and ShowWeb=@ShowWeb and 00034: not exists(select 1 from [Booklet] B2 where B1.i<B2.i and B1.Part=B2.Part and B1.SemesterID=B2.SemesterID) 00035: Order by StartPage 00036: 00037: CREATE procedure [dbo].[nc_Booklet_GetDescr] 00038: @SemesterID uniqueidentifier 00039: as 00040: SELECT [Part],[StartPage],[EndPage],[CreateDate],ShowWeb,i FROM [Booklet] B1 00041: where SemesterID=@SemesterID and 00042: not exists(select 1 from [Booklet] B2 where B1.i<B2.i and B1.Part=B2.Part and B1.SemesterID=B2.SemesterID) 00043: Order by StartPage
Кроме того, как видите, в этих процедурах есть небольша хитрость в виде коррелированной процедуры - будет всегда виден только один (последний) буклет с одним и тем же именем, но все старые буклеты не уничтожаются и сохраняются для истории - мало ли какая-ошибка может быть допущена при ручной корректировке - и администратор всегда может достать из базы какой-то старый рабочий документ.
Наконец, рассмотрим самую первую вкладку View0 Check Service, которая представляет собой собственно интерфейс пользователя, позволяющий получить из базы только итоговые, утвержденные документы, полностью до этого вручную отредактированные и сброшюрованные.
Поскольку этот сервис может быть размещен в любом месте сайта - он вынесен в отдельный контрол StoredPDF, который представляет собой вот такой текст формы:
00001: <%@ Control Language="VB" AutoEventWireup="false" CodeFile="StoredPDF.ascx.vb" Inherits="Manager_PDF_StoredPDF" %> 00002: 00003: <table> 00004: <tr> 00005: <td style="width: 93px"> 00006: <asp:Label ID="Label2" runat="server" Text="Booklet Part"></asp:Label> </td> 00007: <td style="width: 93px"> 00008: <asp:DropDownList ID="DropDownList2" runat="server" DataSourceID="BookletPart" DataTextField="Part" 00009: DataValueField="i" AppendDataBoundItems="True" AutoPostBack="True"> 00010: <asp:ListItem Value="0">selecting...</asp:ListItem> 00011: </asp:DropDownList></td> 00012: <td align="right" style="width: 93px"> 00013: <asp:Button ID="Button1" runat="server" Text="Get" Visible="False" /></td> 00014: </tr> 00015: </table> 00016: <asp:SqlDataSource ID="BookletPart" runat="server" ConnectionString="<%$ ConnectionStrings:siSchoolManagerConnectionString %>" 00017: SelectCommand="nc_Booklet_GetPart" SelectCommandType="StoredProcedure" DataSourceMode="DataReader"> 00018: <SelectParameters> 00019: <asp:ControlParameter ControlID="HiddenField1" Name="SemesterID" PropertyName="Value" 00020: Type="String" /> 00021: <asp:Parameter DefaultValue="True" Name="ShowWeb" Type="Boolean" /> 00022: </SelectParameters> 00023: </asp:SqlDataSource> 00024: <asp:HiddenField ID="HiddenField1" runat="server" />
и вот такой код:
00001: Partial Class Manager_PDF_StoredPDF 00002: Inherits System.Web.UI.UserControl 00003: 00004: Public Property SemesterID() As String 00005: Get 00006: SemesterID = HiddenField1.Value 00007: End Get 00008: Set(ByVal value As String) 00009: HiddenField1.Value = value 00010: End Set 00011: End Property 00012: 00013: Protected Sub Manager_PDF_GetPDF_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00014: If IsPostBack Then 00015: If Me.Request.Params.Get("__EVENTTARGET").EndsWith("DropDownList2") Then 00016: ElseIf Me.Request.Params.Get("__EVENTTARGET").EndsWith("Button1") Then 00017: Else : DropDownList2_Reload() 'это постебк не из этого контрола - перезагружаем все 00018: End If 00019: Else 00020: DropDownList2_Reload() 00021: End If 00022: End Sub 00023: 00024: Private Sub DropDownList2_Reload() 00025: Dim StartItem As New System.Web.UI.WebControls.ListItem("selecting...", 0) 00026: DropDownList2.Items.Clear() 00027: DropDownList2.Items.Add(StartItem) 00028: DropDownList2.DataBind() 00029: If DropDownList2.Items.Count = 1 Then 00030: DropDownList2.Visible = False 00031: Label2.Visible = False 00032: Else 00033: DropDownList2.Visible = True 00034: Label2.Visible = True 00035: End If 00036: End Sub 00037: 00038: Protected Sub DropDownList2_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList2.SelectedIndexChanged 00039: If DropDownList2.SelectedValue <> 0 Then 00040: Session("BookletPart") = DropDownList2.SelectedValue 00041: Button1.Visible = True 00042: Else 00043: Button1.Visible = False 00044: End If 00045: End Sub 00046: 00047: Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 00048: Dim CMD As New siSMWeb.ExecSQL_RDR("@i", Session("BookletPart")) 00049: Dim RDR = CMD.ExecSQL("dbo.nc_Booklet_GetPDF") 00050: If RDR.Read Then 00051: Dim M = New System.IO.MemoryStream(RDR.GetSqlBytes(0).Buffer, 0, RDR.GetSqlBytes(0).Buffer.Length - 1) 00052: Response.ContentType = "application/pdf" 00053: M.WriteTo(Response.OutputStream) 00054: Response.End() 00055: End If 00056: End Sub 00057: 00058: End Class
Само по себе форматирование отчетов - это отдельная техника.Например для данного отчета, мне понадобилось множество формул форматирования, и даже формула, управляющая видимостью отдельных секций отчета:
Теперь пару ложек дегтя в эту бочку меда. Большинство типографий работают на технике макинтош и принимают в печать только документы на шрифтах ADOBE. Эти OpenType шрифты обозначаются в построителе отчетов VS2005 специальными символами:
Так вот, ложку дегтя устроил MS, специально поддерживая только продвигаемые им фонты TrueType и из принципиальных соображений не поддерживая фонты компании Adobe. В этом случае экспорт отчета в PDF выполнить просто не удается.
Однако на других фонтах вся описанная выше технология работает превосходно, учтите только, что импортированные фонты имеют обычно альтернативный NTFS-поток, который выводит дополнительное сообщение о необходимости разблокировки фонта и разрешения его использования на данной машине. Отрезать этот потоки массово для всех импортированных шрифтов можно специальной утилиткой.
Собственно для преобразования фонтов существует множество утилит различных функциональных возможностей - некоторые охватывают все существующие форматы фонтов, но OTF и TTF в таких утилитах рассматривается как один формат. Есть прога FontLab Studio, специально предназначенная для конфертирования OTF в TTF-фонты. Она не только позволяет корректировать каждую букву фонта, но и имеет сотни параметров конфигурации конвертирования.
|