Monday, December 27, 2010

SharePoint service uses all available CPU (owstimer.exe)

I noticed my test web server was running slowly, and when I ran perfmon, I found that CPU utilization was pegged at 100%. I ran taskmgr and looked for processes using a lot of CPU. At first I didn’t see any, but after selecting the Show processes from all users checkbox, I discovered that owstimer.exe was using 95 to 99% of CPU time. I learned that this process is used to perform scheduled tasks in SharePoint – interesting since this server doesn't host anything that uses SharePoint. I confirmed that SharePoint is installed on the test web server

To eliminate the CPU utilization problem, in services.msc, I stopped and disabled the Windows SharePoint Services Timer service.

To determine when SharePoint was installed, I launched SQL Server Management Studio on the test server and connected to the SQL Server instance on the local machine. There I found some SharePoint databases -- WSS_AdminContent, WSS_AdminConfig and WSS_Content. By right-clicking each of these and viewing the properties, I was able to see that they were created on Dec 30 2009, almost a year ago. SharePoint has apparently been present all along, and was likely installed as part of Team Foundation Services.

This doesn’t explain why a SharePoint service suddenly started performing some action that consumed all available CPU time. That remains a mystery. According to several other online posts, other people have experienced the same thing.

Friday, December 17, 2010

Missing DLL causes error "file has not been pre-compiled, and cannot be requested"

My .NET 2.0 website used an old version of Telerik's RadWindow control. I replaced this with a recent version of Telerik RadControls for ASP.NET AJAX. When I tested the website on my local machine, it worked fine. When I uploaded it to the web server, though, attempting to view any page resulted in an error of the form

The file '/step1.aspx' has not been pre-compiled, and cannot be requested.

The filename step1.aspx would be replaced with the name of whatever page I was trying to view.

I eventually determined that the error message didn't mean at all what it said. What it really meant was that Telerik.Web.UI.dll was missing from the website's bin folder. The Telerik control was referenced in a master page that was used by most pages of the site, and therefore the error occurred on almost every page.

Simply adding the missing DLL to the bin folder resolved the problem. I then got to wondering why the DLL was missing in the first place. Here's the answer.

I installed Telerik RadControls for ASP.NET AJAX using the MSI installer provided by Telerik. Among other things, this installed Telerik.Web.UI.dll to C:\Program Files (x86)\Telerik\RadControls for ASP.NET AJAX Q1 2010\Bin20.

I copied Telerik.Web.UI.dll from there to a folder named Shared Assemblies in my .NET solution. I use this folder to store some third-party DLLs to which one or more of my projects make references.

In my project, I added a reference by browsing to the copy of Telerik.Web.UI.dll in my Shared Assemblies folder. Nonetheless, Visual Studio added the reference from the GAC, and did not copy the DLL to my project's bin folder. Because my project was a website, not a web application, I had no option to select "copy local" when adding the reference.

So my only recourse was to manually copy Telerik.Web.UI.dll into the bin folder and add it to source control. Now when I publish my project, the DLL is where it needs to be, and the error no longer occurs.

By the way, this is not specific to Telerik. I've noticed other people have had similar problems with other DLLs, especially ReportViewer.

Thursday, November 11, 2010

LogParser tricks

Microsoft's free command-line LogParser utility has many uses. I find it particularly helpful when I want to comb through IIS logs or Windows event logs for troubleshooting purposes. The SQL-based syntax isn't always easy to master. Whenever I have a helpful LogParser example to share, I'll add it to this post.

Today's example shows how to search the IIS logs. Here's the command line, followed by an explanation and some tips:

G:\Log Parser 2.2>logparser "select date, time, cs-uri-stem, sc-status from g:\logs\www.2l23l.com\W3SVC954531014\ex101110.log where cs-uri-stem like '/hungryboiler%' and time >= '02:00:00' and time < '03:00:00' and cs(User-Agent) like '%Gecko%' and sc-status >= 400"

The fields I wanted to output were date, time, URL (cs-uri-stem) and status (sc-status). Tip: to get a list of all available fields in a log file, use select top 1 * from...

I figured out which log file to search by looking at my website's properties in IIS Manager. In IIS6, on the Web Site tab, under the Logging section, click the Properties button. Then you just need to pick the desired log file according to date. Don't forget: by default, IIS logs use GMT, so something that occurred in the evening Eastern Time will be in the following day's log.

I knew the error occurred on a page whose name started with "hungryboiler", so I searched for cs-uri-stem like '/hungryboiler%'.

I knew a user experienced a "page not found" error, so I searched for sc-status >= 400.

I knew the error occurred between 2:00 and 3:00 AM GMT (10:00 to 11:00 PM the previous day EST), so I search for time >= '02:00:00' and time <= '03:00:00'.

One final tip for LogParser newbies like myself: The query itself goes inside double quotes. Any strings within the query go inside single quotes.

Monday, November 1, 2010

SQL unions and the text data type

Transact-SQL supports a data type named text. It's deprecated and may not be supported forever. It's similar to nvarchar(max).

I ran into an odd problem related to the text data type. I tried to do a query with a union, something like this:

CREATE TABLE [dbo].[t1](
[c1] [int] NULL,
[c2] [text] NULL
)

CREATE TABLE [dbo].[t2](
[c1] [int] NULL,
[c2] [text] NULL
)

SELECT * FROM t1 UNION SELECT * FROM t2


The SELECT statement fails with this error message: The text data type cannot be selected as DISTINCT because it is not comparable.

A bit odd, because there's no reference to DISTINCT in my query, but SQL Server 2005 must use DISTINCT behind the scenese to perform the UNION.

A workaround is to cast text to nvarchar(max), as in SELECT c1, CAST(c2 AS NVARCHAR(MAX)) AS c2 FROM t1 UNION SELECT c1, CAST(c2 AS NVARCHAR(MAX)) AS c2 FROM t2.

Two drawbacks to this workaround: First, you can't use SELECT *, since you must explicitly name any columns that need to be cast to nvarchar(max). Second, if your text columns happen to hold more data than the capacity of nvarchar(max), I'm not sure what would happen. Either an error message or data loss, I assume.

The real fix would be to change any text columns in your database to nvarchar(max) or some other data type that isn't deprecated and works with unions. That could take a fair amount of work if you have many stored procedures, etc. that refer to the existing text columns.

ReportViewer and ToolTips

Some interesting facts about ReportViewer and ToolTips:
  • Yes, you can have ToolTips in a report (RDL file) created in SQL Server 2005 Business Intelligence Development Studio (BIDS) and viewed in the .NET ReportViewer control. Just set the ToolTip property for any textbox.
  • You can include line breaks in the text of a tooltip. One way to do so is to set the ToolTip property to an expression that uses vbCrLf. Example: ="This is the first line" & vbCrLf & "This is the second line".
  • The ToolTips don't show up when you preview the report in BIDS. But they do show up when you run the report in SQL Server Report Services or inside in a ReportViewer control in your .NET program.

Friday, October 15, 2010

Create a PDF and attach it to an email message without saving it to disk

Okay, I must admit it -- I'm pretty proud of this one. This is cool!

The goal: Use WebSupergoo's ABCpdf .NET 7.0 to create a PDF file on the fly. Without saving the file to disk, attach it to an email message, and send the email.

Prerequisites:
  • Create a .NET 3.5 C# web site.

  • Configure the mailSettings section in web.config to point to your SMTP server. Don't have an SMTP server? See this handy tip.

  • Add a button to a form on your website. Place the code below in the button's Click event handler.

  • That's it! But if you're on 64-bit Windows and ABCpdf.NET is throwing an exception, try this.

Here's the code:

// Create a mail message, setting the "from" address, "to" address, subject and body
SmtpClient smptClient = new SmtpClient();
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress("ssaporta@localupsolutions.com");
mailMessage.To.Add(new MailAddress("ssaporta@localupsolutions.com"));
mailMessage.Subject = "Test";
mailMessage.Body = "This is a test.";

// Use WebSupergoo's ABCpdf to create a PDF document
Doc theDoc = new Doc();
theDoc.AddHtml("<html><body>The time is now <b>" + DateTime.Now.ToString() + "</b> text.</body></html>");

// Save the document to a memory stream (rather than to a file on disk)
MemoryStream ms = new MemoryStream();
theDoc.Save(ms);

// Important: return to the beginning of the stream
ms.Position = 0;

// Set the MIME type and filename for the attachment
ContentType ct = new ContentType();
ct.MediaType = "application/pdf";
ct.Name = "mydoc" + DateTime.Now.ToString() + ".pdf";

// Create the attachment from the stream
Attachment attachment = new Attachment(ms, ct);
mailMessage.Attachments.Add(attachment);

// Send the email
smptClient.Send(mailMessage);

// Clean up
ms.Close();
theDoc.Clear();

Label1.Text = "Done";

Thursday, October 14, 2010

Cause of exception "Index and length must refer to a location within the string"

Every once in a while, my .NET 2.0 website would throw this exception: "Index and length must refer to a location within the string." I knew this because I could see the exception in the Windows application event log, but the log didn't give a line number or any other information that allowed me to narrow it down to less than a few hundred lines of code.

By adding a bunch of logging -- trapping exceptions with lots of try/catch blocks and writing them to disk -- I found the problem. This exception can occur when you call the .NET String.Substring() method and the string's length is not long enough.

Something like this would cause it:
string s = "abc";
string s2 = s.Substring(1,5);

In my case, I was parsing a credit card expiration date on the assumption that it would always be formatted as mm/yyyy, but it turned out sometimes the month was a single digit, as in 6/2013 rather than 06/2013.

How to test sending email on Windows 7

When you install IIS on Windows 7, it doesn't include an SMTP mail server. That's different than Windows XP, and is inconvenient when you're developing -- and would like to test -- a .NET application that sends email.

Of course you could install and configure a third-party SMTP server, and there are some free ones available. But I found a simpler alternative here.

In brief, put this in your web.config file:

<smtp deliverymethod="SpecifiedPickupDirectory">
<specifiedpickupdirectory pickupdirectorylocation="c:\Temp\">
</smtp>

In your .NET application, instantiate SmtpClient using the constructor that takes no arguments; this tells SmtpClient to use the SMTP settings in web.config. Then, when your application calls SmtpClient.Send(), an .EML file will be created in c:\temp. I was able to double-click the .EML file, and it opened in Windows Live Email.

Wednesday, October 13, 2010

Visual Studio debugger may run 32-bit on 64-bit Windows

I discovered something while testing a component called ABCpdf from WebSupergoo. I have 64-bit Windows 7 Home Premium, so I donwloaded the 64-bit version of ACBpdf. I created a .NET 3.5 web site and attempted to instantiate the component like this:

WebSupergoo.ABCpdf7.Doc doc = new WebSupergoo.ABCpdf7.Doc();

When I pressed F5 in Visual Studio 2008 to start debugging, a runtime error resulted:

The type initializer for 'WebSupergoo.ABCpdf7.Internal.ManagedInvoke' threw an exception.

I found the explanation on WebSupergoo's support site (although they describe a different symptom: "I see the exception "Unable to load DLL 'ABCpdfCE7.dll': The specified module could not be found.")

It turns out that Visual Studio's debugger may run as a 32-bit application, even on 64-bit Windows.

By creating an IIS 7 website that points to my source code folder, and running it that way, it ran 64-bit, and the problem was solved.

Interestingly, when I created a Windows Forms application and ran that using the Visual Studio debugger, that worked without error as well.

WebSupergoo suggests a good trick for determining whether your .NET website or appllication is running 32-bit: Output the value of IntPtr.Size. If it is 4, rather than 8, your application is running in a 32-bit process.

Thursday, October 7, 2010

Calling a method in the parent page upon clicking a button in a .NET user control

Lots of people probably know this already, but it was new to me.

Suppose you have a button (or other control) in an ASP.NET user control, and when the button is clicked, you'd like to call a method in the containing page, so that the page can take some appropriate action.

One way to do this is with InvokeMethod. You can even pass a value from the user control to the parent page.

A tip of the hat to Rajshree Mittal for this post that pointed the way.

My example consists of four files: Default.aspx, MyUserControl.ascx, Default.aspx.cs, and MyUserControl.ascx.cs. The two .CS files are provided here.

Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = "before";
}

public void DisplayMessage(string message)
{
Label1.Text = message;
}
}


MyUserControl.aspx.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class MyUserControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{

}
protected void btnOK_Click(object sender, EventArgs e)
{
Page.GetType().InvokeMember("DisplayMessage", System.Reflection.BindingFlags.InvokeMethod, null, Page, new object[] { TextBox1.Text });
}
}

Wednesday, October 6, 2010

Parsing a comma-separated list of values into a table in SQL

You might want to select records that match any of several values. For example, you might want to get all restaurants where restaurantID is 50, 51 or 60. That's no problem in a T-SQL select statement: select * from restaurants where restaurantID in (50,51,60).

However, suppose you'd like to pass the list of restaurant IDs as a parameter to a stored procedure. Let's suppose the stored proc accepts an nvarchar parameter named @restaurantID. In our example, @restaurantID = '50,51,60'.

T-SQL does not allow something like this: select * from restaurants where restaurantID in @restaurantID.

You must get the list of restaurant IDs into a temporary table and do something like this:

declare @r table (
restaurantid int
)
-- parse comma-separated list into table here...
select * from restaurants where restaurantid in (select restaurantid from @r)


So, how do you parse the comma-separate list into a table? Like this:

declare @index int
declare @leftPart varchar(255)
while len(@restaurantidlist) > 0
begin
set @index = charindex(',', @restaurantidlist)
if @index = 0
begin
set @index = len(@restaurantidlist) + 1
end
set @leftPart = substring(@restaurantidlist, 0, @index)
set @restaurantidlist = substring(@restaurantidlist, @index + 1, len(@restaurantidlist))
insert into @r (restaurantid) values (@leftPart)
end

Tuesday, October 5, 2010

ReportViewer quirks with browsers other than IE

I'm using the .NET ReportViewer control to display report output. Today I found this control fully supports Internet Explorer, but has some quirks when used with other browsers.

Microsoft has documented this here. Also a shout-out to this post on stackoverflow that set me on the right track.

I've examined several aspects of ReportViewer's behavior in four browsers, all running on Windows:
- Internet Explorer 8
- Firefox 3.6.10
- Google Chrome 6.0.472.63
- Safari 5.0.2

Here's a rundown of the quirks I encountered...

1. You can set the width of ReportViewer's output by setting its ZoomMode and/or ZoomPercent properties.
- Works perfectly in IE.
- Totally ignored by FF. Output is always displayed at 100%.
- Works perfectly in Chrome.
- Mostly works in Safari, but width is greater than it should be.

2. The ReportViewer control is supposed to display a dropdown list so you can change the width of the output interactively.
- Works perfectly in IE.
- Not displayed in FF, Chrome or Safari.

3. The ReportViewer control displays several controls at the top of the report for pagination, exporting the report, etc. The way these controls are laid out varies by browser.
- In IE, they're left-justified on a single line.
- Ditto in FF.
- In Chrome, they're bunched up vertically in a narrow column.
- In Safari, they're similarly bunched up.

Sunday, October 3, 2010

Welcome to ProgBlog

I've been the CTO of LocalUp Solutions, developing web-based software for the restaurant industy, since February, 2010. It's a small company, so I have the privilege and challenge of solving most technical problems myself. When I come across a software engineering problem or solution that I find interesting, I'll post it here on ProgBlog. If you find something here to be intriguing or useful, please join the conversation.