Tips for debugging likely Crystal Reports hang in ASP.NET - asp.net

Attempting to keep this short: our shipping personnel use a Windows Mobile-enabled barcode scanner to scan the serial numbers of items shipped on customer orders. That data is submitted via asmx web service and a report is automatically printed to a networked printer showing customer info and the details of items shipped. All of this happens in an intranet environment.
Until recently, I was using the Microsoft Report engine built into Visual Studio (.rdlc files) to generate the reports and the code outlined here to print without user intervention. It worked fine that way for several years.
Recently, I ran up against some report formatting limitations in MS Reports and implemented the reports in Crystal Reports (10.5.3700) instead. The code works fine, but after running in production for a few hours to a day or more, the asp.net worker process is hanging (not sure if this is the appropriate term). The report generating/printing process runs forever without throwing an exception. Recycling the AppPool makes everything work again for a while.
The reports are using "push" mode where the report is given a typed dataset rather than accessing the database itself. To take the CR ReportDocument.PrintToPrinter() method out of the equation, I have considered exporting the generated reports to PDF or similar and then printing the resulting file independently. But I haven't found a great way to do that yet.
Researching the problem, I've read all kinds of complaints about the buggy-ness of Crystal, but I'm hoping that there's is a bug in my code related to cleaning up after the report is printed.
Imports CrystalDecisions.CrystalReports.Engine
Imports CrystalDecisions.ReportAppServer
Imports CrystalDecisions.Shared
Public Class CrystalReport
Private _report As ReportDocument
Public Sub New(ByVal Path As String)
_report = New ReportDocument()
_report.Load(Path)
End Sub
Public Sub SetDatasource(ByVal DataSet As DataSet)
_report.SetDataSource(DataSet)
End Sub
Public Sub AddParameter(ByVal Name As String, ByVal Value As Object)
Dim crParameterFieldDefinitions As ParameterFieldDefinitions = _report.DataDefinition.ParameterFields
Dim crParameter1 As ParameterFieldDefinition = crParameterFieldDefinitions.Item(Name)
Dim parameterValue As CrystalDecisions.Shared.ParameterDiscreteValue = New CrystalDecisions.Shared.ParameterDiscreteValue()
parameterValue.Value = Value
crParameter1.CurrentValues.Add(parameterValue)
crParameter1.ApplyCurrentValues(crParameter1.CurrentValues)
End Sub
Public Sub Print(ByVal PrinterPath As String)
_report.PrintOptions.PrinterName = PrinterPath
_report.PrintToPrinter(1, True, 0, 0)
Close()
End Sub
Private Sub Close()
_report.Close()
_report.Dispose()
_report = Nothing
End Sub
End Class
Any ideas for debugging this further? I've never had to resort to real Windows debugging (WinDbg, Process Explorer, etc.), so any recommendation on which debugging tool to try first would be great.
Thank you.

It sounds like the report actually isn't being released, although by looking at your code you would think you are doing exactly that.
You might try Close()ing and Dispose()ing your report object on the Page_Unload event of your ASP.NET page. This forum post on the Microsoft ASP.NET forums talks about an issue where the maximum amount of report processing jobs is reached, but I imagine the issue could be related.

Related

converting MSIDXS search to Windows Search Service for website

Here's my situation: I'm in the process of a website overhaul to the public website for our company. The old site was running on server 2003 on .net framework 3.5? it may have been originally built on the 2.0 framework for that matter. ANYway, the old site had a search feature which worked really nicely for users to find pages related to topics they were interested in. It used the old MSIDXS oledb connection type... simple code as follows...
Dim odbSearch As New System.Data.OleDb.OleDbConnection("Provider=""MSIDXS"";Data Source=""Proto"";")
Dim cmdSearch As New System.Data.OleDb.OleDbCommand
cmdSearch.CommandText = String.Format("SELECT doctitle, filename, vpath, rank, characterization, size FROM scope() WHERE FREETEXT(Contents, '{0}') ORDER BY rank DESC", searchText) WHERE CONTAINS(*,'\""" & searchText & "*\""') AND scope='file:C:\...\Web_App' ORDER BY System.ItemPathDisplay DESC
This worked great. But now we're moving this to a 2008 r2 server, which doesn't have the MSIDXS indexing anymore... or it does, but doesn't work for sites? I was able to turn it on, but it never found anything, and the catalog remained empty, and everything I've read said this isn't how to do searches on sites anymore. The 'new way' that I read about was using windows search service. I've adjusted the service on the box to 'index' the website's directory, and it seems to have stuff in the catalog... however, what code I've converted always returns 0. so the new code looks like...
Dim odbSearch As New System.Data.OleDb.OleDbConnection("Provider=Search.CollatorDSO.1;Extended Properties='Application=Windows';")
cmdSearch.CommandText = String.Format("SELECT system.title, system.filename, System.ItemPathDisplay FROM SystemIndex WHERE scope='file:C:\...\Web_App'")
Dim rdrSearch As OleDbDataReader = cmdSearch.ExecuteReader()
While rdsSearch.read()
I can't get this to actually return any results. regardless of what I put in as the search criteria, it jumps right to the end while.
Can someone tell me what piece of the puzzle I'm missing?
Everything was actually correct; I wasn't getting any rows back because I had already narrowed the systemIndex to be looking at the subdirectory that I wanted, so including a scope in the where clause was causing zero results. As soon as I took that out, the search works.
It looks that the scope should be specified as 'file:C:\\...\\Web_App', or add a '#' sign at the beginning of your query string.

Running a series of VBScripts within an ASP.net (vb.net) page?

I have a requirement when a user clicks a specific arrangement of radio buttons to run a series of vbscripts (and soon Perl scripts).
I have all of the vbscripts stored server side, they do not need to be on the remote system to run. Oh yes, the scripts are gathering information on remote system in our intranet.
What would be the best way. Currently I have this to run just one script, not multiple...should I keep this or dispose of this idea.
Protected Sub windowsScript(ByVal COMPUTERNAME As String)
' Create an array to store VBScript results
Dim winVariables(1) As String
Dim filePath As String = COMPUTERNAME & "\C$\Windows\somefile.txt"
'Execute PsExec on script
runPsExec(COMPUTERNAME, "systemInfo.vbs", 1)
'Import data from text file into variables
textRead(filePath, winVariables)
System.Threading.Thread.Sleep(1000)
'Delete the file on server - we don't need it anymore
runPsExec(COMPUTERNAME, "systemInfo.vbs", 2)
MsgBox("Windows OS: " & winVariables(0).ToString())
MsgBox("Service Pack: " & winVariables(1).ToString())
End Sub
Also, it is hard to see here because I do have another function "textRead" but what is going on is this particular script is stored client side and the vbscript it outputting to a text file. textRead will read the variable and send a text file back to the server to read it.
This is definitely not what I want to do.
I want to be a little more dynamic, plus with my new scripts...they don't need to be on the client at all.
Any help would be appreciated :)
I'm thinking of making some type of While loop, not sure if that would work.
It's kind of strange to do this through the browser. In my company we collect systeminfo at logontime with a vbscript logonscript and add the result to a logfile which we can access through a webapp to do research. Occasionally when the need rises we run a specific script to gather more data or change some system setting through windows SCCM.
If the goal is to provide the user with info about his system there are some good utilities around which can be run locally (but from a location on a server share).
EDIT
a simple way to start multiple processes
dim scripts_to_run, script
const COMPUTERNAME = 0, SCRIPTNAME = 1, EXTRA_PARAMS = 2
scripts_to_run = Array(_
Array("computer1","script1.vbs",1),_
Array("computer2","script1.vbs",0),_
Array("computer3","script3.vbs",3)_
)
for each script in scripts_to_run
runPsExec script(COMPUTERNAME), script(SCRIPTNAME), script(EXTRA_PARAMS)
runPsExec join(script,",")
next
sub runPsExec(p1, p2, p3)
'here coms your code shat runs the script
wscript.echo p1 & p2 & p3
end sub
or a shorter version
dim scripts_to_run, aArgs
scripts_to_run = Array(_
Array("computer1","script1.vbs",1),_
Array("computer2","script1.vbs",0),_
Array("computer3","script3.vbs",3)_
)
for each aArgs in scripts_to_run
runPsExec aArgs
next
sub runPsExec(aArgs)
'here coms your code shat runs the script
wscript.echo aArgs(0) & aArgs(1) & aArgs(2)
end sub

VBScript Out Of Memory Error

I have a classic ASP CRM that was built by a third party company. Currently, I have access to the source code and am able to make any changes required.
Randomly throughout the day, usually after some prolonged usage by users, most of my pages start getting an Out of Memory error.
The way that the application is built, is all the pages and scripts pull core functions from a Global.asp file. In that file are embeds to other global files as well, but the error presented shows
Out Of Memory
WhateverScriptYouTriedToRun.asp Line 0
Line 0 is the include for the global.asp file. Once the error occurs, after an unspecified amount of time the error occurence subsides for some time but then begins to reoccur again. With how the application is written, and the functions it uses, and the "diagnostics" I've already done - it seems to be a common used function that is withholding data such as recordset or something of that nature and then not releasing it properly. Other users then try to use the same function and eventually it just fills up causing the error. The only way for me to effectively clear the error is to actually restart IIS, Recycle the App Pool, and Restart the SQL Server Services.
Needless to say, myself and my users are getting annoyed....
I can't pinpoint the error due to the actual error message presented being Line 0 - but from there I have no idea where in the 20K lines of code it could be hanging up. Any thoughts or ideas on how to isolate or at least point me in the right direction to begin clearing this up? Is there a way for me to increase "memory" size for VBScript? I know there is a limitation but is it set at say...512K and you can increase it to 1GB?
Here are things I have tried:
Removing SQL Inline statements into Views
Going through several hundred scripts and ensuring that every OpenConnection & OpenRecordSet is followed by an appropriate Close.
Going through the Global File and commenting out any large SQL statements such as ApplicationLog (A function that writes the executed query into a table).
Some smaller script edits.
Common Memory Leak
You say you are closing all recordsets and connections which is good.
But are you deleting objects?
For example:
Set adoCon = new
Set rsCommon = new
'Do query stuff
'You do this:
rsCommon.close
adocon.close
'But do you do this?
Set adoCon = nothing
Set rsCommon = nothing
No garbage collection in classic ASP, so any objects not destroyed will remain in memory.
Also, ensure your closes/nothings are run in every branch. For example:
adocon.open
rscommon.open etc
'Sql query
myData = rscommon("condition")
if(myData) then
response.write("ok")
else
response.redirect("error.asp")
end if
'close
rsCommon.close
adocon.close
Set adoCon = nothing
Set rsCommon = nothing
Nothing is closed/destroyed before the redirect so it will only empty memory some of the time as not all branches of logic lead to the proper memory clearance.
Better Design
Also unfortunately it sounds like the website wasn't designed well. I always structure my classic ASP as:
<%
Option Explicit
'Declare all vars
Dim this
Dim that
'Open connections
Set adoCon...
adocon.open()
'Fetch required data
rscommon.open strSQL, adoCon
this = rsCommon.getRows()
rsCommon.close
'Fetch something else
rscommon.open strSQL, adoCon
that = rsCommon.getRows()
rsCommon.close
'Close connections and drop objects
adoCon.close
set adoCon = nothing
set rscommon = nothing
'Process redirects
if(condition) then
response.redirect(url)
end if
%>
<html>
<body>
<%
'Use data
for(i = 0 to ubound(this,2)
response.write(this(0, i) & " " & this(1, i) & "<br />")
next
%>
</body>
</html>
Hope some of this helped.
Have you looked at using a memory monitoring tool to see how much memory fragmentation is happening? My guess at a possible cause is that some object of a size is trying to be created but there isn't enough room in the memory to store it as one contiguous chunk. Imagine needing room to store an object that would take 100 MB and while there may be several hundred megabytes free, the largest contiguous chunk is 90MB then this doesn't fit.
Debug Diagnostic Tool v1.1 would be a tool where Bernard's articles may help in understanding how to use the tool.
Another thought is the question of how much string concatenation is there in the code? I remember where I used to work had problems with doing a lot of string concatenation operations that sucked up memory that may be another idea to consider.
Yeah, I could see some shock at that kind of number the first few times you see it but then if you understand what the code is doing it may make sense for why so much space gets reserved right off the bat at times.
I haven't used that debug tool specifically but I did have a tool that took a snapshot of memory when pages were hung so I couldn't tell if there was a performance impact of the tool or not. Course in my case I used a similar tool in 2004 so it has been a few years since I've had to research this kind of issue.
Just going to throw this in here, but this problem has taken a long time to solve. Here's a breakdown of what we did:
We took all the inline SQL and made SQL Views, every SELECT statement is now handled with a VIEW first.
I took every single SQL INSERT and UPDATE (as much as I could without breaking the system) and put them into Stored Procedures.
#2 was the one item that really made the biggest difference
Went through several thousand scripts, and ensured that variables were properly disposed of, and all the DB Open Connections were followed correctly with a Close Connection and same with Open/Close RecordSet.
One of the slow killers was doing something like:
ID = Request.QueryString("ID)
at the top of the page. Before redirecting, or closing a page, there is always a:
Set ID = Nothing
or the complete removal of the inference.

RDLC Report Viewer Drill Through Report

I had posted this question on MSDN Forum
http://social.msdn.microsoft.com/Forums/en/vsreportcontrols/thread/f00e3406-354d-4f54-acce-7b7f0ad4c90f
But I am not getting any response. Can you please help me. I am really stuck with this rather simple task.
My code seems to be correct but still I get the
A data source instance has not been supplied for the data source 'DataSet1_Order_Details'.
Sorry for the cross post...
I had a same experience and it was because I try tosee the report from a wrong place, let me explain
There is a dataSet in RDLC reports
There is a method in DAL which get data from db
There is a Method in BAL or UI which call the DAL method and fill dataset. Most of time this is a unique page which get some parameter from user and fill dataset and finally redirect user to the report viewer page.
your eror appears in report viewer page
So the following may happened
you enter directly to reportviewer page without filling dataset
you went to correct page and fill the dataset but inreport viewer pageyoudidn't bind your dataset to your report in code behind.
in some cases it can be because of session expiration.
This worked for me, too, though the real key was right here:
((LocalReport)e.report)
In my case, I am using:
((LocalReport)e.report).LoadReportDefinition(report_stream);
foreach (ReportParameter rp in ((LocalReport)e.report).OriginalParametersToDrillthrough)
{
_paramCollection[rp.Name].parameterValue.AddRange(rp.Values.OfType<string>());
}
DataTable newData = GetData();
ReportDataSource rds = new ReportDataSource();
rds.Name = _datasetName;
rds.Value = newData;
((LocalReport)e.report).DataSources.Add(rds);
The reportviewer handles the rest. #Knows Not Much, thanks for the lead.
I solved the error.
I was adding the data source like
this.ReportViewer.Localreport.DataSource.Add
(new RemoteDataSource("DataSet1_Order_Details", ObjectDataSource2.ID));
instead the data source should be added in the following manner
DataSet1TableAdapter.OrderDetails od = new DataSet1TableAdapter.OrderDetails();
((LocalReport)e.report).Datasources.Add(new RemoteDataSource("DataSet1_Order_Details", od.get(orderid))
this resolved the issue and now I am able to have drill down in the ReortViewer report.
Regards,
Abhishek

ASP.NET Unexpected and Different Behavior in Different Environments

I have an ASP.NET site (VB.NET) that I'm trying to clean up. When it was originally created it was written with no error handling, and I'm trying to add it in to improve the User Experience.
Try
If Not String.IsNullOrEmpty(strMfgName) And Not String.IsNullOrEmpty(strSortType) Then
If Integer.TryParse(Request.QueryString("CategoryID"), i) And String.IsNullOrEmpty(Request.QueryString("CategoryID"))
MyDataGrid.DataSource = ProductCategoryDB.GetMfgItems(strMfgName, strSortType, i)
Else
MyDataGrid.DataSource = ProductCategoryDB.GetMfgItems(strMfgName, strSortType)
End If
MyDataGrid.DataBind()
If CType(MyDataGrid.DataSource, DataSet).Tables("Data").Rows.Count > 0 Then
lblCatName.Text = CType(MyDataGrid.DataSource, DataSet).Tables("Data").Rows(0).Item("mfgName")
End If
If MyDataGrid.Items.Count < 2 Then
cboSortTypes.Visible = False
table_search.Visible = False
End If
If MyDataGrid.PageCount < 2 Then
MyDataGrid.PagerStyle.Visible = False
End If
Else
lblCatName.Text &= "<br /><span style=""fontf-size: 12px;"">There are no items for this manufacturer</span>"
MyDataGrid.Visible = False
table_search.Visible = False
End If
Catch
lblCatName.Text &= "<br /><span style=""font-size: 12px;"">There are no items for this manufacturer</span>"
MyDataGrid.Visible = False
table_search.Visible = False
End Try
Now, this is trying to avoid generating a 500 error by catching exceptions. There can be three items on the query string, but only two matter here. In my test environment and in Visual Studio when I run this site, it doesn't matter if that item is on the query string. In production, it does matter. If that third item isn't present (SubCategoryID) on the query string, then the "There are no items for this manufacturer" displays instead of the data from the database.
In the two different environments I am seeing two different code execution paths, despite the same URLs and the same code base.
The site is running on Server 2003 with IIS 6.
Thoughts?
EDIT:
In response to the answer below, I doubt it's a connection error (though I see what you're getting to), as when I add the SubCategoryID to the query string, the site works correctly (displaying data from the database).
Also, if please let me know if you have any suggestions for how to test this scenario, without deploying the code back to production (it's been rolled back).
I think you should try to print out the exception details in your catch block to see what the problem is. It could anything for example a connection error to your database.
The error could be anything, and you should definitely consider printing this out or logging it somewhere, rather than making the assumption that there's no data. You're also outputting the same error message to the UI for two different code paths, which makes things harder to debug, especially without knowing if an exception occurred, and if so, what it was.
Generally, it's also better not to have a catch for all exceptions in cases like this, especially without logging the error. Instead, you should catch specific exceptions and handle these appropriately, and any real exceptions can get passed up the stack, ideally to a global error handler which can log it and/or send out some kind of error notification.
I discovered the reason yesterday. In short it was because when I copied my files from my computer into my dev-test environment, I missed a file, which ironically caused it to work, rather than not. So in the end it would have functioned the same in both environments.

Resources