(ASP.NET) ASP.NET (2007 год)

Изготовление отчетов в 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>&nbsp;
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>&nbsp;
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="&lt;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-фонты. Она не только позволяет корректировать каждую букву фонта, но и имеет сотни параметров конфигурации конвертирования.



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/asp2/25/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>