Формирование Excel-отчетов
На этой страничке я опишу один маленький, но важный вопросик из моего приложения для тестиорвания работников РЖД (скрины этого проекта). Некая тестовая версия этого моего приложения осталась у меня на хостинге - rzd.vb-net.com. В этом приложении вообще много интересных моментов, например момент расчета времени для тестирования - синхронизатор серверного отсчета времени и клиентского устаревания странички через SetTimeOut, и много чего еще интересного почти на каждой из форм. Но я опишу на своем хомячке лишь одну форму. Потому что я увидел десяток вопросов в рунете - как вызвать Excel или Word в Web-приложении или Web-службе - и в основном был ответ - это невозможно, это нарушает безопасность.
Это конечно же - бред. DCOM-конфигуратор именно предназначен для описания конкретики этого вызова - и мы посмотрим на этой страничке и как это настраивать и как писать собственно код странички, формирующей вот такой Excel-отчет из базы.
Изначально, вот эту страничку, которую вы видите на рисунке (и можете войти в нее по ссылке выше - зайти под начальником станции и в результаты испытаний->просмотреть->РБУ-10 - только дату отчета конечную выберете больше чем начальную) - так вот, эту страничку я сначала написал на отчете Кристала. Вообще у меня много раз получалось сформировать на кристале отличные отчеты - но в этом случае все уперлось и ни в какую у меня не получилось добится требуемого форматирования. Тогда я оставил в покое кристалл и докрутил на этой же страничке формирование отчета в Excel - и это сразу устроило заказчика.
Мы рассмотрим сначала как сформировать такой отчет, а потом посмотрим, как настроить DCOM и настроить все неоходимое для работы этой страничке на хостинге.
Итак, форма, скрин которой вы видите - просто формирует параметры вызова формы RBU10.aspx, которую мы и будем рассматривать подробнее, включая настройку ее работы на хостинге.
Сама по себе форма RBU10.aqspx выглядит практически пустой - содержит пару запросов в базу и обьявление сборки кристала.
00001: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="RBU10.aspx.vb" Inherits="RBU10" %> 00002: 00003: <%@ Register assembly="CrystalDecisions.Web, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304" namespace="CrystalDecisions.Web" tagprefix="CR" %> 00004: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 00005: 00006: <html xmlns="http://www.w3.org/1999/xhtml"> 00007: <head runat="server"> 00008: <title>Печать РБУ-10</title> 00009: </head> 00010: <body> 00011: <form id="form1" runat="server"> 00012: <div> 00013: <CR:CrystalReportViewer ID="CrystalReportViewer1" runat="server" EnableDatabaseLogonPrompt="False" EnableParameterPrompt="False" /> 00014: </div> 00015: <asp:Label ID="Err1" runat="server" ForeColor="Red" ></asp:Label> 00016: 00017: <asp:SqlDataSource ID="GetCheckPlan0" runat="server" 00018: ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>" 00019: SelectCommand="CheckPlan0" SelectCommandType="StoredProcedure"> 00020: <SelectParameters> 00021: <asp:QueryStringParameter Name="Group" QueryStringField="Group" /> 00022: <asp:QueryStringParameter Name="StartDate" Type="DateTime" QueryStringField="StartDate" /> 00023: <asp:QueryStringParameter Name="StopDate" Type="DateTime" QueryStringField="StopDate" /> 00024: </SelectParameters> 00025: </asp:SqlDataSource> 00026: 00027: <asp:SqlDataSource ID="CheckPlan0_OneUser" runat="server" 00028: ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>" 00029: SelectCommand="CheckPlan0_OneUser" SelectCommandType="StoredProcedure"> 00030: <SelectParameters> 00031: <asp:QueryStringParameter Name="Group" QueryStringField="Group" /> 00032: <asp:QueryStringParameter Name="ToUser" Type="String" QueryStringField="J" /> 00033: </SelectParameters> 00034: </asp:SqlDataSource> 00035: 00036: <asp:SqlDataSource ID="GetSchemaName" runat="server" 00037: ConnectionString="<%$ ConnectionStrings:SQLServer_ConnectionStrings %>" 00038: SelectCommand="select top 1 * from [Schema] where id=@id"> 00039: <SelectParameters> 00040: <asp:Parameter Name="id" Type="String" /> 00041: </SelectParameters> 00042: </asp:SqlDataSource> 00043: 00044: 00045: </form> 00046: </body> 00047: 00048: </html>
А вот код у этой формы более интересный. Приведу его целиком.
00001: 'параметры формы 00002: 'Debug=1 - перечисление колонок отчета для построителя отчета 00003: 'GroupID=fa541063-fa8b-4d90-b4e8-d27c32808b4c - ID группы - передается в отборы 00004: 'GroupName=СеверныйПост - имя группы - печатается на формах 00005: 'StartDate=30.12.2008 00006: 'StopDate=21.02.2012 00007: 'может быть задано J=57717A5E-69CB-4DD0-9831-3682F01CA696 - гуид юзера - тогда нужен отчет только по одному юзеру 00008: 'Mode="Crystal" or Mode="Excel" 00009: Partial Class RBU10 00010: Inherits System.Web.UI.Page 00011: 00012: 00013: Protected Sub RBU10_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00014: If Request.QueryString("GroupID") = "" Then 00015: Err1.Text = "Не задан обязательный параметр GroupID" 00016: Exit Sub 00017: End If 00018: If Request.QueryString("GroupName") = "" Then 00019: Err1.Text = "Не задан обязательный параметр GroupName" 00020: Exit Sub 00021: End If 00022: If Request.QueryString("StartDate") = "" Then 00023: Err1.Text = "Не задан обязательный параметр StartDate" 00024: Exit Sub 00025: End If 00026: If Request.QueryString("StopDate") = "" Then 00027: Err1.Text = "Не задан обязательный параметр StopDate" 00028: Exit Sub 00029: End If 00030: 'If Request.QueryString("Mode") = "" Then 00031: ' Err1.Text = "Не задан обязательный параметр Mode" 00032: ' Exit Sub 00033: 'End If 00034: 'все данные из плана 00035: Dim DV As Data.DataView 00036: If Request.QueryString("J") Is Nothing Then 00037: 'отбор по группе 00038: If Roles.IsUserInRole(User.Identity.Name, "SuperAdmin") Or Roles.IsUserInRole(User.Identity.Name, "Admin") Or Roles.IsUserInRole(User.Identity.Name, "Tutor") Or Roles.IsUserInRole(User.Identity.Name, "SubTutor") Then 00039: GetCheckPlan0.SelectParameters("Group").DefaultValue = Request.QueryString("GroupID") 00040: GetCheckPlan0.SelectParameters("StartDate").DefaultValue = Request.QueryString("StartDate") 00041: GetCheckPlan0.SelectParameters("StopDate").DefaultValue = Request.QueryString("StopDate") 00042: Try 00043: DV = GetCheckPlan0.Select(New DataSourceSelectArguments) 00044: Catch X As SystemException 00045: Err1.Text = "Ошибка отбора. Проверьте COLLATE базы." & vbCrLf & X.ToString 00046: Exit Sub 00047: End Try 00048: Else 00049: CrystalReportViewer1.Visible = False 00050: Err1.Text = "У вас недостаточно прав для получения этого отчета." 00051: Exit Sub 00052: End If 00053: Else 00054: 'отбор по одному юзеру 00055: CheckPlan0_OneUser.SelectParameters("Group").DefaultValue = Request.QueryString("GroupID") 00056: CheckPlan0_OneUser.SelectParameters("ToUser").DefaultValue = Request.QueryString("i") 00057: Try 00058: DV = CheckPlan0_OneUser.Select(New DataSourceSelectArguments) 00059: Catch X As SystemException 00060: Err1.Text = "Ошибка отбора. Проверьте COLLATE базы." & vbCrLf & X.ToString 00061: Exit Sub 00062: End Try 00063: End If 00064: If DV Is Nothing Then 00065: Err1.Text = "Нет результатов по данному отбору GroupName=" & Request.QueryString("GroupName") & ", StartDate=" & Request.QueryString("StartDate") & ", StopDate=" & Request.QueryString("StopDate") 00066: If Request.QueryString("J") IsNot Nothing Then 00067: Err1.Text &= "<br>" & "По пользователю " & Request.QueryString("J") 00068: End If 00069: Exit Sub 00070: End If 00071: If DV.Count = 0 Then 00072: Err1.Text = "Нет результатов по данному отбору GroupName=" & Request.QueryString("GroupName") & ", StartDate=" & Request.QueryString("StartDate") & ", StopDate=" & Request.QueryString("StopDate") 00073: If Request.QueryString("J") IsNot Nothing Then 00074: Err1.Text &= "<br>" & "По пользователю " & Request.QueryString("J") 00075: End If 00076: Exit Sub 00077: End If 00078: Dim DT As Data.DataTable = DV.ToTable 00079: 'плюс наименование билета 00080: DT.Columns.Add("Schema", GetType(System.String)) 00081: 'плюс все из профиля 00082: DT.Columns.Add("RailWay", GetType(System.String)) 00083: DT.Columns.Add("Department", GetType(System.String)) 00084: DT.Columns.Add("Station", GetType(System.String)) 00085: DT.Columns.Add("Location", GetType(System.String)) 00086: DT.Columns.Add("FIO", GetType(System.String)) 00087: DT.Columns.Add("Telefon", GetType(System.String)) 00088: DT.Columns.Add("ForNew", GetType(System.String)) 00089: DT.Columns.Add("ForOld", GetType(System.String)) 00090: DT.Columns.Add("Num", GetType(System.Int32)) 00091: DT.Columns.Add("TestResult_String", GetType(System.String)) 00092: DT.Columns.Add("SummaryResume_String", GetType(System.String)) 00093: DT.Columns.Add("StartDate_String", GetType(System.String)) 00094: DT.Columns.Add("PlainDate_String", GetType(System.String)) 00095: For Each OneRow As Data.DataRow In DT.Rows 00096: ' 00097: Dim MyTypedProfile As ProfileCommon = ProfileBase.Create(Membership.GetUser(New Guid(OneRow("ToUser").ToString)).UserName) 00098: If MyTypedProfile IsNot Nothing Then 00099: OneRow("Location") = MyTypedProfile.Location 00100: OneRow("FIO") = MyTypedProfile.FIO 00101: OneRow("Telefon") = MyTypedProfile.Telefon 00102: OneRow("ForNew") = MyTypedProfile.ForNew 00103: OneRow("ForOld") = MyTypedProfile.ForOld 00104: 'текстовые названия станций 00105: Dim RZD As New GetRZD_Names(MyTypedProfile) 00106: OneRow("RailWay") = RZD.RailWay 00107: OneRow("Department") = RZD.Department 00108: OneRow("Station") = RZD.Station 00109: 'прочее предформатирование для отчета 00110: If Not IsDBNull(OneRow("TestResult")) Then 00111: If OneRow("TestResult") Then 00112: OneRow("TestResult_String") = "сдал" 00113: Else 00114: OneRow("TestResult_String") = "не сдал" 00115: End If 00116: End If 00117: If IsDBNull(OneRow("StartDate")) Then 00118: OneRow("StartDate_String") = "не сдавал" 00119: End If 00120: If Not IsDBNull(OneRow("SummaryResume")) Then 00121: If OneRow("SummaryResume") = 1 Then 00122: OneRow("SummaryResume_String") = "сдал" 00123: ElseIf OneRow("SummaryResume") = 2 Then 00124: OneRow("SummaryResume_String") = "пересдача" 00125: ElseIf OneRow("SummaryResume") = 3 Then 00126: OneRow("SummaryResume_String") = "не сдал" 00127: End If 00128: End If 00129: If Not IsDBNull(OneRow("PlainDate")) Then 00130: OneRow("PlainDate_String") = String.Format("{0:dd.MM.yy}", OneRow("PlainDate")) 00131: End If 00132: End If 00133: ' 00134: GetSchemaName.SelectParameters("ID").DefaultValue = OneRow("ToSchema").ToString 00135: Dim DT1 As Data.DataView = GetSchemaName.Select(New DataSourceSelectArguments) 00136: If DT1 IsNot Nothing Then 00137: If DT1.Count > 0 Then 00138: OneRow("Schema") = DT1(0)("Title") 00139: End If 00140: End If 00141: Next 00142: ' 00143: 'это отладчик построителя форм 00144: If Request.QueryString("Debug") <> "" Then 00145: Response.Write("<Html><body><table border=1>") 00146: For Each OneColumn As Data.DataColumn In DT.Columns 00147: Response.Write("<tr><td>" & OneColumn.ColumnName & "</td><td>" & OneColumn.DataType.Name & "</td></tr>") 00148: Next 00149: Response.Write("</table></body></html>") 00150: Exit Sub 00151: End If 00152: ' 00153: 'пересортируем отчет по фамилиям 00154: Dim DV2 As Data.DataView = DT.DefaultView 00155: DV2.Sort = "UserName,PlainDate" 00156: ' 00157: 'перенумеруем 00158: If DV2.Count > 0 Then 00159: Dim J As Integer = 1 00160: DV2(0)("Num") = J 00161: For i As Integer = 0 To DV2.Count - 2 00162: If DV2(i + 1)("UserName") <> DV2(i)("UserName") Then 00163: J += 1 00164: DV2(i + 1)("Num") = J 00165: End If 00166: Next 00167: End If 00168: ' 00169: 'почистить лишние пробелы в строках 00170: For i As Integer = 0 To DV.Count - 1 00171: If Not IsDBNull(DV2(i)("NoPlanComment")) Then DV2(i)("NoPlanComment") = DV(i)("NoPlanComment").ToString.Trim ' String 00172: If Not IsDBNull(DV2(i)("Comment")) Then DV2(i)("Comment") = DV2(i)("Comment").ToString.Trim ' String 00173: If Not IsDBNull(DV2(i)("Schema")) Then DV2(i)("Schema") = DV2(i)("Schema").ToString.Trim ' String 00174: If Not IsDBNull(DV2(i)("RailWay")) Then DV2(i)("RailWay") = DV2(i)("RailWay").ToString.Trim ' String 00175: If Not IsDBNull(DV2(i)("Department")) Then DV2(i)("Department") = DV2(i)("Department").ToString.Trim ' String 00176: If Not IsDBNull(DV2(i)("Station")) Then DV2(i)("Station") = DV2(i)("Station").ToString.Trim ' String 00177: If Not IsDBNull(DV2(i)("Location")) Then DV2(i)("Location") = DV2(i)("Location").ToString.Trim ' String 00178: If Not IsDBNull(DV2(i)("FIO")) Then DV2(i)("FIO") = DV2(i)("FIO").ToString.Trim ' String 00179: If Not IsDBNull(DV2(i)("Telefon")) Then DV2(i)("Telefon") = DV2(i)("Telefon").ToString.Trim ' String 00180: Next 00181: 00182: ' 00183: If Request.QueryString("Mode") = "Excel" Then 00184: GoExcel(DV2) 00185: Else 00186: GoCrystal(DV2) 00187: End If 00188: End Sub 00189: 00190: Sub GoCrystal(ByVal DV As Data.DataView) 00191: '------------------------- 00192: 'теперь берем бланк отчета 00193: Dim MyReport As New CrystalDecisions.CrystalReports.Engine.ReportDocument 00194: MyReport.Load(Server.MapPath("RBU10.rpt")) 00195: 'заталкиваем в бланк сформированные на этой форме данные 00196: MyReport.SetDataSource(DV) 00197: CrystalReportViewer1.ReportSourceID = "" 00198: 'и заталкиваем в кристал-вьювеер отчет уже с данными 00199: CrystalReportViewer1.ReportSource = MyReport 00200: 'передаем во вьюверер параметры этой формы 00201: If CrystalReportViewer1.ParameterFieldInfo("GroupName") IsNot Nothing Then SetReportParm("GroupName", Request.QueryString("GroupName")) 00202: If CrystalReportViewer1.ParameterFieldInfo("StartDate") IsNot Nothing Then SetReportParm("StartDate", Request.QueryString("StartDate")) 00203: If CrystalReportViewer1.ParameterFieldInfo("StopDate") IsNot Nothing Then SetReportParm("StopDate", Request.QueryString("StopDate")) 00204: 'и рисуем вьювеером отчет на форме 00205: CrystalReportViewer1.DataBind() 00206: End Sub 00207: 00208: Sub GoExcel(ByVal DV As Data.DataView) 00209: 'Сначала открываем бланк 00210: Dim RBU10_TempName As String = "RBU10_" & Guid.NewGuid.ToString & ".xls" 00211: Dim RBU10_FullTempName As String = System.IO.Path.Combine(Server.MapPath("Excel_Temp"), RBU10_TempName) 00212: ' 00213: If Not My.Computer.FileSystem.FileExists(System.IO.Path.Combine(Server.MapPath("~"), "RBU10_blank.xls")) Then 00214: Err1.Text = "Отсуствует файл шаблона RBU10_blank.xls" 00215: Exit Sub 00216: Else 00217: My.Computer.FileSystem.CopyFile(System.IO.Path.Combine(Server.MapPath("~"), "RBU10_blank.xls"), RBU10_FullTempName) 00218: End If 00219: Dim XL As New Microsoft.Office.Interop.Excel.Application 00220: Dim XL_WB As Microsoft.Office.Interop.Excel.Workbook = XL.Workbooks.Open(RBU10_FullTempName, 0, False) 00221: Dim XL_Page As Microsoft.Office.Interop.Excel.Worksheet = XL_WB.ActiveSheet 00222: ' 00223: 'собственно обработка 00224: If Request.QueryString("ExcelDebug") IsNot Nothing Then 00225: 'сырой вывод базы 00226: XL_Page.Cells(9, 1) = "id(Guid)" 00227: XL_Page.Cells(9, 2) = "ToFirst(Guid)" 00228: XL_Page.Cells(9, 3) = "ToUser(Guid)" 00229: XL_Page.Cells(9, 4) = "ToSchema(Guid)" 00230: XL_Page.Cells(9, 5) = "IsPlan(Boolean)" 00231: XL_Page.Cells(9, 6) = "IsNoPlan(Boolean)" 00232: XL_Page.Cells(9, 7) = "NoPlanComment(String)" 00233: XL_Page.Cells(9, 8) = "PlainDate(DateTime)" 00234: XL_Page.Cells(9, 9) = "StartDate(DateTime)" 00235: XL_Page.Cells(9, 10) = "TestGuid(Guid)" 00236: XL_Page.Cells(9, 11) = "TestResult(Boolean)" 00237: XL_Page.Cells(9, 12) = "Comment(String)" 00238: XL_Page.Cells(9, 13) = "SummaryResume(Int32)" 00239: XL_Page.Cells(9, 14) = "ForBeginnersPO(DateTime)" 00240: XL_Page.Cells(9, 15) = "ForBeginnersCT(DateTime)" 00241: XL_Page.Cells(9, 16) = "ForBeginnersPC(DateTime)" 00242: XL_Page.Cells(9, 17) = "UserName(String)" 00243: XL_Page.Cells(9, 18) = "Schema(String)" 00244: XL_Page.Cells(9, 19) = "RailWay(String)" 00245: XL_Page.Cells(9, 20) = "Department(String)" 00246: XL_Page.Cells(9, 21) = "Station(String)" 00247: XL_Page.Cells(9, 22) = "Location(String)" 00248: XL_Page.Cells(9, 23) = "FIO(String)" 00249: XL_Page.Cells(9, 24) = "Telefon(String)" 00250: XL_Page.Cells(9, 25) = "ForNew(String)" 00251: XL_Page.Cells(9, 26) = "ForOld(String)" 00252: XL_Page.Cells(9, 27) = "Num(Int32)" 00253: XL_Page.Cells(9, 28) = "TestResult_String(String)" 00254: XL_Page.Cells(9, 29) = "SummaryResume_String(String)" 00255: XL_Page.Cells(9, 30) = "StartDate_String(String)" 00256: XL_Page.Cells(9, 31) = "PlainDate_String(String)" 00257: XL_Page.Cells(9, 32) = "GroupID" 00258: XL_Page.Cells(9, 33) = "GroupName" 00259: XL_Page.Cells(9, 34) = "StartDate" 00260: XL_Page.Cells(9, 35) = "StopDate" 00261: For i As Integer = 0 To DV.Count - 1 00262: If Not IsDBNull(DV(i)("id")) Then XL_Page.Cells(10 + i, 1) = DV(i)("id").ToString() ' Guid 00263: If Not IsDBNull(DV(i)("ToFirst")) Then XL_Page.Cells(10 + i, 2) = DV(i)("ToFirst").ToString ' Guid 00264: If Not IsDBNull(DV(i)("ToUser")) Then XL_Page.Cells(10 + i, 3) = DV(i)("ToUser").ToString ' Guid 00265: If Not IsDBNull(DV(i)("ToSchema")) Then XL_Page.Cells(10 + i, 4) = DV(i)("ToSchema").ToString ' Guid 00266: If Not IsDBNull(DV(i)("IsPlan")) Then XL_Page.Cells(10 + i, 5) = DV(i)("IsPlan").ToString ' Boolean 00267: If Not IsDBNull(DV(i)("IsNoPlan")) Then XL_Page.Cells(10 + i, 6) = DV(i)("IsNoPlan").ToString ' Boolean 00268: If Not IsDBNull(DV(i)("NoPlanComment")) Then XL_Page.Cells(10 + i, 7) = DV(i)("NoPlanComment").ToString ' String 00269: If Not IsDBNull(DV(i)("PlainDate")) Then XL_Page.Cells(10 + i, 8) = DV(i)("PlainDate").ToString ' DateTime 00270: If Not IsDBNull(DV(i)("StartDate")) Then XL_Page.Cells(10 + i, 9) = DV(i)("StartDate").ToString ' DateTime 00271: If Not IsDBNull(DV(i)("TestGuid")) Then XL_Page.Cells(10 + i, 10) = DV(i)("TestGuid").ToString ' Guid 00272: If Not IsDBNull(DV(i)("TestResult")) Then XL_Page.Cells(10 + i, 11) = DV(i)("TestResult").ToString ' Boolean 00273: If Not IsDBNull(DV(i)("Comment")) Then XL_Page.Cells(10 + i, 12) = DV(i)("Comment").ToString ' String 00274: If Not IsDBNull(DV(i)("SummaryResume")) Then XL_Page.Cells(10 + i, 13) = DV(i)("SummaryResume").ToString ' Int32 00275: If Not IsDBNull(DV(i)("ForBeginnersPO")) Then XL_Page.Cells(10 + i, 14) = DV(i)("ForBeginnersPO").ToString ' DateTime 00276: If Not IsDBNull(DV(i)("ForBeginnersCT")) Then XL_Page.Cells(10 + i, 15) = DV(i)("ForBeginnersCT").ToString ' DateTime 00277: If Not IsDBNull(DV(i)("ForBeginnersPC")) Then XL_Page.Cells(10 + i, 16) = DV(i)("ForBeginnersPC").ToString ' DateTime 00278: If Not IsDBNull(DV(i)("UserName")) Then XL_Page.Cells(10 + i, 17) = DV(i)("UserName").ToString ' String 00279: If Not IsDBNull(DV(i)("Schema")) Then XL_Page.Cells(10 + i, 18) = DV(i)("Schema").ToString ' String 00280: If Not IsDBNull(DV(i)("RailWay")) Then XL_Page.Cells(10 + i, 19) = DV(i)("RailWay").ToString ' String 00281: If Not IsDBNull(DV(i)("Department")) Then XL_Page.Cells(10 + i, 20) = DV(i)("Department").ToString ' String 00282: If Not IsDBNull(DV(i)("Station")) Then XL_Page.Cells(10 + i, 21) = DV(i)("Station").ToString ' String 00283: If Not IsDBNull(DV(i)("Location")) Then XL_Page.Cells(10 + i, 22) = DV(i)("Location").ToString ' String 00284: If Not IsDBNull(DV(i)("FIO")) Then XL_Page.Cells(10 + i, 23) = DV(i)("FIO").ToString ' String 00285: If Not IsDBNull(DV(i)("Telefon")) Then XL_Page.Cells(10 + i, 24) = DV(i)("Telefon").ToString ' String 00286: If Not IsDBNull(DV(i)("ForNew")) Then XL_Page.Cells(10 + i, 25) = DV(i)("ForNew").ToString ' String 00287: If Not IsDBNull(DV(i)("ForOld")) Then XL_Page.Cells(10 + i, 26) = DV(i)("ForOld").ToString ' String 00288: If Not IsDBNull(DV(i)("Num")) Then XL_Page.Cells(10 + i, 27) = DV(i)("Num").ToString ' Int32 00289: If Not IsDBNull(DV(i)("TestResult_String")) Then XL_Page.Cells(10 + i, 28) = DV(i)("TestResult_String").ToString ' String 00290: If Not IsDBNull(DV(i)("SummaryResume_String")) Then XL_Page.Cells(10 + i, 29) = DV(i)("SummaryResume_String").ToString ' String 00291: If Not IsDBNull(DV(i)("StartDate_String")) Then XL_Page.Cells(10 + i, 30) = DV(i)("StartDate_String").ToString ' String 00292: If Not IsDBNull(DV(i)("PlainDate_String")) Then XL_Page.Cells(10 + i, 31) = DV(i)("PlainDate_String").ToString ' String 00293: XL_Page.Cells(10 + i, 32) = Request.QueryString("GroupID") 00294: XL_Page.Cells(10 + i, 33) = Request.QueryString("GroupName") 00295: XL_Page.Cells(10 + i, 34) = Request.QueryString("StartDate") 00296: XL_Page.Cells(10 + i, 35) = Request.QueryString("StopDate") 00297: Next 00298: Else 00299: 'чистовой отчет 00300: XL_Page.Cells(8, 14) = "Служебные пометки АС ДО" : XL_Page.Cells(8, 14).Font.Bold = True : XL_Page.Cells(8, 14).Font.Color = RGB(255, 0, 0) 00301: XL_Page.Cells(9, 14) = "RailWay" : XL_Page.Cells(9, 14).Font.Bold = True 00302: XL_Page.Cells(9, 15) = "Department" : XL_Page.Cells(9, 15).Font.Bold = True 00303: XL_Page.Cells(9, 16) = "Station" : XL_Page.Cells(9, 16).Font.Bold = True 00304: XL_Page.Cells(9, 17) = "ToUser" : XL_Page.Cells(9, 17).Font.Bold = True 00305: XL_Page.Cells(9, 18) = "UserName" : XL_Page.Cells(9, 18).Font.Bold = True 00306: XL_Page.Cells(9, 19) = "ToSchema" : XL_Page.Cells(9, 19).Font.Bold = True 00307: XL_Page.Cells(9, 20) = "Schema" : XL_Page.Cells(9, 20).Font.Bold = True 00308: XL_Page.Cells(9, 21) = "id" : XL_Page.Cells(9, 21).Font.Bold = True 00309: XL_Page.Cells(9, 22) = "TestGuid" : XL_Page.Cells(9, 22).Font.Bold = True 00310: XL_Page.Cells(9, 23) = "ToFirst" : XL_Page.Cells(9, 23).Font.Bold = True 00311: XL_Page.Cells(9, 24) = "GroupID" : XL_Page.Cells(9, 24).Font.Bold = True 00312: XL_Page.Cells(9, 25) = "GroupName" : XL_Page.Cells(9, 25).Font.Bold = True 00313: XL_Page.Cells(9, 26) = "StartDate" : XL_Page.Cells(9, 26).Font.Bold = True 00314: XL_Page.Cells(9, 27) = "StopDate" : XL_Page.Cells(9, 27).Font.Bold = True 00315: For i As Integer = 0 To DV.Count - 1 00316: If Not IsDBNull(DV(i)("Num")) Then 00317: 'новый юзер 00318: XL_Page.Cells(10 + i, 1) = DV(i)("Num").ToString ' Int32 00319: XL_Page.Cells(10 + i, 11) = "один раз в два года" 00320: For j As Integer = 1 To 100 00321: XL_Page.Cells(10 + i, j).Font.Bold = True 00322: Next 00323: End If 00324: 00325: If Not IsDBNull(DV(i)("Location")) Then XL_Page.Cells(10 + i, 2) = DV(i)("Location").ToString 00326: If Not IsDBNull(DV(i)("FIO")) Then XL_Page.Cells(10 + i, 3) = DV(i)("FIO").ToString ' String 00327: If Not IsDBNull(DV(i)("ForBeginnersPO")) Then XL_Page.Cells(10 + i, 4) = DV(i)("ForBeginnersPO").ToString ' DateTime 00328: If Not IsDBNull(DV(i)("ForBeginnersCT")) Then XL_Page.Cells(10 + i, 5) = DV(i)("ForBeginnersCT").ToString ' DateTime 00329: If Not IsDBNull(DV(i)("ForBeginnersPC")) Then XL_Page.Cells(10 + i, 6) = DV(i)("ForBeginnersPC").ToString ' DateTime 00330: If DV(i)("IsPlan") Then 00331: If Not IsDBNull(DV(i)("PlainDate_String")) Then XL_Page.Cells(10 + i, 7) = DV(i)("PlainDate_String").ToString ' String 00332: Else 00333: If Not IsDBNull(DV(i)("PlainDate_String")) Then XL_Page.Cells(10 + i, 12) = DV(i)("PlainDate_String").ToString ' String 00334: End If 00335: If Not IsDBNull(DV(i)("StartDate_String")) Then XL_Page.Cells(10 + i, 8) = DV(i)("StartDate_String").ToString ' String 00336: If Not IsDBNull(DV(i)("TestResult_String")) Then XL_Page.Cells(10 + i, 9) = DV(i)("TestResult_String").ToString ' String 00337: If Not IsDBNull(DV(i)("SummaryResume_String")) Then XL_Page.Cells(10 + i, 10) = DV(i)("SummaryResume_String").ToString ' String 00338: If Not IsDBNull(DV(i)("Comment")) Then XL_Page.Cells(10 + i, 13) = DV(i)("Comment").ToString ' String 00339: If Not IsDBNull(DV(i)("RailWay")) Then XL_Page.Cells(10 + i, 14) = DV(i)("RailWay").ToString ' String 00340: If Not IsDBNull(DV(i)("Department")) Then XL_Page.Cells(10 + i, 15) = DV(i)("Department").ToString ' String 00341: If Not IsDBNull(DV(i)("Station")) Then XL_Page.Cells(10 + i, 16) = DV(i)("Station").ToString ' String 00342: If Not IsDBNull(DV(i)("ToUser")) Then XL_Page.Cells(10 + i, 17) = DV(i)("ToUser").ToString ' Guid 00343: If Not IsDBNull(DV(i)("UserName")) Then XL_Page.Cells(10 + i, 18) = DV(i)("UserName").ToString ' String 00344: If Not IsDBNull(DV(i)("ToSchema")) Then XL_Page.Cells(10 + i, 19) = DV(i)("ToSchema").ToString ' Guid 00345: If Not IsDBNull(DV(i)("Schema")) Then XL_Page.Cells(10 + i, 20) = DV(i)("Schema").ToString ' String 00346: If Not IsDBNull(DV(i)("id")) Then XL_Page.Cells(10 + i, 21) = DV(i)("id").ToString() ' Guid 00347: If Not IsDBNull(DV(i)("TestGuid")) Then XL_Page.Cells(10 + i, 22) = DV(i)("TestGuid").ToString ' Guid 00348: If Not IsDBNull(DV(i)("ToFirst")) Then XL_Page.Cells(10 + i, 23) = DV(i)("ToFirst").ToString ' Guid 00349: XL_Page.Cells(10 + i, 24) = Request.QueryString("GroupID") 00350: XL_Page.Cells(10 + i, 25) = Request.QueryString("GroupName") 00351: XL_Page.Cells(10 + i, 26) = Request.QueryString("StartDate") 00352: XL_Page.Cells(10 + i, 27) = Request.QueryString("StopDate") 00353: Next 00354: End If 00355: 'сохраняем на диск 00356: XL_WB.Close(True) 00357: XL = Nothing 00358: ' 00359: 'и записываем все в поток браузера 00360: Response.ContentType = "attachment" 00361: Response.Charset = "utf-8" 00362: Response.AppendHeader("content-disposition", "attachment; filename=" & RBU10_TempName) 00363: Context.Response.BinaryWrite(My.Computer.FileSystem.ReadAllBytes(RBU10_FullTempName)) 00364: 00365: End Sub 00366: 00367: Private Sub SetReportParm(ByVal ReportParmName As String, ByVal Value As Object) 00368: Dim ExecParms As CrystalDecisions.Shared.ParameterValues = New CrystalDecisions.Shared.ParameterValues() 00369: Dim OnePrm As CrystalDecisions.Shared.ParameterDiscreteValue = New CrystalDecisions.Shared.ParameterDiscreteValue() 00370: OnePrm.Value = Value 00371: ExecParms.Add(OnePrm) 00372: CrystalReportViewer1.ParameterFieldInfo(ReportParmName).CurrentValues = ExecParms 00373: End Sub 00374: 00375: 00376: End Class
Как видите, тут есть и код работы с кристалом (строки 190-206, 367-373), который у меня многократно разжеван на сайте (и тут мы его по новой жевать не будем), код запроса в базу и корректировки сырых данных из базы (строки 1-188) и код работы с Excel (строки 208-365).
В строке 35 обьявляется датавью, который заполняется запросом из базы. Затем он расширяется в строке 78-95 еще на несколько колонок (для данных, которые хранятся в профиле пользователя). Затем в строках 95-180 все это подчищается, пересортировывается, коды заменяются на слова в итоговом отчете.
Работа с Экселом (строки 208-365) заключается во взятии файла Excel c шапкой отчета RBU10_blank.xls, затем он копируется во временную директорию Excel_Temp c уникальным именем и открывается там в строках 219-221. Затем он заполняется в строках 225-297 для отладочного отчета, который просто перечислит выведетт все, что есть в сформированном нами датасете (с указанием типа данных), либо в строках 300-354 формируется чистовой отчет - по формату, требуемому РЖД. Затем в строке 356 Эксел закрывается с сохранением сформированных в нем данных и в строках 360-363 этот временный файл с уникальным именем из директории Excel_Temp - пишется в поток браузера.
Я сделал это перечисление полей в датасете (строки 226-296) - для своего удобства - для проверки какие в итоге поля у меня получились в сформированном датасете и их тип данных.
Теперь рассмотрим, пожалуй, даже более интересную тему (чем собственно программирование этой формы) - вынос этого сайта на хостинг.
Прежде всего, этот сайт непросто вынести на хостинг, даже из-за наличия в нем кристалловских сборок. Дело в том, что часть кристалловских сборок подключается из ГАКА. Чтобы вычислить все сборки, задействованные при компиляции - необходимо открыть студией PRECOMPILED-сайт. И увидеть в окне OUTPUT перечень сборок, необходимых для компиляции. Ибо они могут быть версии 2.7, 2.8 или вообще какого-то иного релиза, если кристал был установлен отдельно или обновлен.
Но даже копирование на хостинг всех этих сборок - не гарантирует корректную работу кристала. Несмотря на то, что сборка CrystalDecisions.ReportAppServer.CommLayer отсутсвует в списке линкуемых с сайтом сборок - обычно получается чудесное исключение:
System.TypeInitializationException: Инициализатор типа "CrystalDecisions.CrystalReports.Engine.ReportDocument" выдал исключение. ---> System.IO.FileNotFoundException:
Невозможно загрузить файл или сборку "CrystalDecisions.ReportAppServer.CommLayer, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304"
или один из зависимых от них компонентов. Не удается найти указанный файл.
Имя файла: "CrystalDecisions.ReportAppServer.CommLayer, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304"
в CrystalDecisions.CrystalReports.Engine.ReportDocument..cctor()
Предупреждение: регистрация привязки сборок выключена.
Чтобы включить регистрацию ошибок привязки сборок, установите значение параметра реестра [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) в 1.
Примечание. Регистрация ошибок привязки сборок может привести к некоторому снижению производительности.
Чтобы отключить эту функцию, удалите параметр реестра [HKLM\Software\Microsoft\Fusion!EnableLog].
Несмотря на полезность журналирования привязки сборок - это очень похоже на обычный баг (тк эту сборку отдельно я вообще не нашел). А вообще Crystal - ацкая по обьему продуктов компания - например только для Crystal Reports (а еще есть Crystal Reports Server и Xcelsius) - основные версии их продуктов - .NET,10,2,2008,3,4,4.5,5,6,6.5,7,8,8.5,9,XI,XI Release 1,XI Release 2. Так вот в версии Crystal Reports .NET - существует 87 продуктов! Всевозможные патчи, дополнения, сервис паки и прочее. Мне стало лень разбиратся в этом баге - поскольку требуемого форматирования я так и не смог добится от кристала (из-за чего и переделал отчет на Excel) и в этом проекте корректную установку ран-тайм среды для кристала я оставил админам РЖД .
Теперь давайте посмотрим, что надо сделать, чтобы на хостинге заработал Excel. Как я говорил - в рунете самое распространенное мнение, что вообще невозможно вызывать Excel и Word из Web-сервисов на сайтах. По крайней мере я не нашел никаких рекомнедаций и настроил все сам (хотя после этой моей заметки они наверняка уже появятся).
Так, вот - сначала довольно тривиальная вещь. Как это можно убедится, у меня на хостинге этот сайт запущен под учеткой Network Services (хотя ничто мне не мешает сделать это иначе). Значит, первое действие - это права на запись на эту учетку в директорию Excel_Temp, куда сайт копирует временные файлы строкой 217 и куда Excel сохраняет свою страничку после записи в нужные клетки странички нужных данных их базы (строка 356). Иначе - обычный отказ в доступе.
Следующая тривиальная вешь - Excel в принципе должен присутствовать на хостинге, причем именно той версии, под которой компилировался сайт. Если он отсутсвует на хостинге - то в этом сообщении будут просто нули вместо GUID'а Excel'а.
Но даже если все это сделано (те доступ ко всем диреториям есть и сам Excel есть) - cайт все равно упадет.
Сбой при получении фабрики класса COM для компонента с CLSID {00024500-0000-0000-C000-000000000046} в результате следующей ошибки: 80070005.
Описание: Необработанное исключение при выполнении текущего веб-запроса. Изучите трассировку стека для получения дополнительных сведений о данной ошибке и о вызвавшем ее фрагменте кода.
Сведения об исключении: System.UnauthorizedAccessException: Сбой при получении фабрики класса COM для компонента с CLSID {00024500-0000-0000-C000-000000000046} в результате следующей ошибки: 80070005.
ASP.NET не имеет права обращаться к запрошенному ресурсу. Рекомендуется предоставить идентификатору запроса ASP.NET права доступа к этому ресурсу. ASP.NET имеет базовый идентификатор процесса
(обычно {MACHINE}\ASPNET для IIS 5 или Network Service на IIS 6), который используется, если приложение не олицетворяется. Если приложение олицетворяется через задание <identity impersonate="true"/>,
идентификатором будет служить идентификатор анонимного пользователя (обычно IUSR_MACHINENAME) или идентификатор пользователя запроса с проверенной подлинностью.
Для предоставления ASP.NET прав на запись в файл, щелкните на файле правой кнопкой мыши в окне "Проводник", выберите "Свойства", затем вкладку "Безопасность".
Выберите "Добавить" для добавления соответствующего пользователя или группы. Выделите учетную запись ASP.NET и установите флажки для требуемых прав доступа.
И на этом этапе широкая публика обычно скисает. А между тем требуется всего два простых шага для разруливания этой ситуации.
В первую очередь, на потребуется самое распространенное действие - разрешение на ключ реестра. Это широко известный шаг в среде профессиональных программистов-асперов. У меня много раз это действие упоминается на сайте. Например, на этой страничке разрешение на ключ HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog необходимо, чтобы сайт мог писать в журнал. Поэтому, наш первый шаг - разрешение на ключ реестра COM-класса Excel'а HKEY_CLASSES_ROOT\CLSID\{00024500-0000-0000-C000-000000000046}. Естественно, на ту учетку, под которой работает домен приложения - в моем случае это Network Services.
И второй шаг, необходимость которого тоже туговато доходит до широкой публики. Набираем от командной строки DCOMCNFG. Находим в оснастке служба компонентов Excel. Конфигурировать его там на пяти вкладках и десятках дочерних вкладок - можно ой как по-разному. Но самый примитивный способ для начинающих - отключить вообще проверку подлинности.
И если вы все это сделали правильно, вы будете вознаграждены - подобные отчеты ваш сайт будет формировать так же легко, как и мой:
Удачи!
|