Bart's Smarts

Smart .NET, Silverlight, and SharePoint Development.

Archive for the ‘.NET’ Category

Calculating relative (offset) dates for DOS commands and scheduled tasks

Posted by sapientcoder on March 5, 2009

I’ve had several cases come up over the years where I’ve needed to pass a date to a program that’s being run from the Windows command line (DOS prompt) or as a Windows Scheduled Task. Since different programs require dates for different meanings and in different formats, I’ve always thought it would be nice if there were a “standardized” way to calculate whatever date needs to be passed to the program and then format it in a way the program expects.

Up until now, the most common way I’ve seen documented on the web to do this is to parse the built-in %Date% variable (or the result of the ‘date’ command) into multiple variables that can then be strung together to generate a date in the desired format.

In my opinion, this approach has a couple of shortcomings:

  1. When a variable is set using the SET command in a DOS session, the variable retains its value until the DOS session is terminated. In other words, you must close and re-open the command prompt before its value can be reset. This is inconvenient if you want to re-run a program several times in the same session and pass a different date each time.
  2. Parsing the date returned by the ‘date’ command or %Date% variable is machine-specific and can break if the date settings on the machine are changed.

To avoid these issues, I used a different approach when I recently had the need to do this.

I wrote a small batch file that does two things. First, it calls a small utility program I wrote called getrelativedate to get a date relative to the current date and format it. Second, it calls the application that needs the date and passes it the output from getrelativedate.

Here’s a sample batch file that uses this approach to pass a date 5 days in the past to an application:

@echo off

FOR /F %%a IN ('path\to\getrelativedate -5 yyyyMMdd') DO (
    if errorlevel 0 (
        path\to\prog\expecting\date -date %%a -other params
    )
)

And here’s the syntax for the getrelativedate utility:

getrelativedate <interval> <format>

The first parameter, interval, designates the date offset and is formatted in a way that’s accepted by .NET’s TimeSpan.Parse() method. See that method’s documentation to see how interval should be formatted. It’s pretty easy. Also, intervals can be positive (in the future), negative (in the past), or 0 (if you just want to format the current date and don’t need an offset).

The second parameter, format, is a standard .NET date format string (like what you would pass to the DateTime.ToString() method).

Pretty simple and easy but also quite flexible and powerful, in my opinion.

If you’re interested, download getrelativedate.zip.

The zip file includes the utility program, a sample batch file, and a readme file with documentation.

Posted in .NET | Tagged: | Leave a Comment »

Programmatically Converting/Printing a Web Page to PDF

Posted by sapientcoder on August 8, 2008

Something that came up recently at work was the need to programmatically convert a web page (rendered, not raw HTML) to PDF. Since I was less than impressed with the documentation I found on the web explaining how to do this in a Windows enviroment, I thought I’d post how I did it to make it easier for the next person.

Let’s start with the overall architecture. The first thing I did was install PDFCreator on a server on our network. PDFCreator is a free “virtual printer” that allows you to print documents to it and writes them out as PDF documents. While I use CutePDF on my own computer for this purpose, PDFCreator has the advantage of having a “batch print” capability, meaning it’s possible to print to it without a print dialog, and the files are automatically saved to a specific location. To do this, I installed PDFCreator as a service (instructions here). I then created a console application (using VS.NET 2005) that grabs the web page I’m interested in, renders it, and sends it to the PDF printer. I set this application up as a Scheduled Task in windows so it can run weekly.

Now let’s take a look at the code. (All code snippets include the relevant “using” statements.) Here’s my Main method (leaving out the exception handling for brevity’s sake):

using System.Windows.Forms;

[STAThread]
static void Main(string[] args)
{
    Application.Run(new CustomApplicationContext());
}

Now, I know that two aspects of that code seem unusual for a console application. The first is the [STAThread] attribute, which I’ll explain later, and the second is the call to Application.Run(). That call is there because I need the console application to handle events and to continue executing until I explicitly tell it to quit, which means it needs a message loop (or message “pump”). In this regard, it acts more like a GUI application but without the overhead of creating forms I don’t need.

NOTE: Although I used a console application, the same code will work in a Windows (GUI) application by changing the call to Application.Run() to run a form rather than an ApplicationContext.

Next, here’s the majority of my CustomApplicationContext class definition:

using System;
using System.Windows.Forms;

class CustomApplicationContext : ApplicationContext
{
    WebBrowser _browser = new WebBrowser();
    Int16 PRINT_ARGS = (
        Constants.PRINT_DONTBOTHERUSER |
        Constants.PRINT_WAITFORCOMPLETION );

    public CustomApplicationContext()
    {
        _browser.DocumentCompleted += _browser_DocumentCompleted;
        _browser.Navigate(Constants.TARGET_URL);
    }

    void _browser_DocumentCompleted(object sender,
        WebBrowserDocumentCompletedEventArgs e)
    {
        if (_browser.ReadyState == WebBrowserReadyState.Complete)
        {
            try
            {
                string defaultPrinter = WrappedNativeMethods.GetDefaultPrinter();

                if (NativeMethods.SetDefaultPrinter(Constants.PDF_PRINTER_NAME))
                {
                    object pvaIn = PRINT_ARGS;
                    object pvaOut = Type.Missing;
                    ((SHDocVw.IWebBrowser2)_browser.ActiveXInstance).ExecWB(
                        SHDocVw.OLECMDID.OLECMDID_PRINT,
                        SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
                        ref pvaIn, ref pvaOut);
                        NativeMethods.SetDefaultPrinter(defaultPrinter);
                }
            }
            finally
            {
                Application.ExitThread();
            }
        }
    }
}

Here’s the relevant code in my Constants class:

using System;

class Constants
{
    public const string PDF_PRINTER_NAME = "PDFCreator";
    public const Int16 PRINT_WAITFORCOMPLETION = 0x02;
    public const Int16 PRINT_DONTBOTHERUSER = 0x01;
    public const string TARGET_URL = @"http://url_of_web_page_to_print";
}

Lastly, here’s the relevant code in my NativeMethods class:

using System;
using System.Runtime.InteropServices;
using System.Text;

class NativeMethods
{
    [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool GetDefaultPrinter(StringBuilder pszBuffer, ref int pcchBuffer);

    [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool SetDefaultPrinter(string name);
}

The above Win32 function declarations were obtained from pinvoke.net.

If you’re wondering why I didn’t post the code for the WrappedNativeMethods class, it’s because there’s only one method in it: a method that wraps the above GetDefaultPrinter method. To get that code, click here and copy the “Sample Code” on the page.

Now I’ll explain the [STAThread] attribute above the Main method. The attribute is there because in order to instantiate an instance of the WebBrowser control, the current thread must be running in a “single-threaded apartment” state. Click here for a short explanation of what that means.

Now to explain a few quirks of printing with the WebBrowser control (i.e. Internet Explorer) and how I worked around them. First, the WebBrowser control has a single Print() method that takes no arguments. As a result, you can’t easily do the following: (a) send the output to any printer other than the system’s default printer, (b) suppress Internet Explorer’s “print” dialog (it’s displayed as though a user clicked “File => Print”), and (c) execute the print call synchronously (i.e. cause it to block until completion rather than firing off a thread and then returning).

To solve the default printer issue, I had to make Win32 API calls to get the current default printer, make my PDF printer the default right before printing, and reset the default printer right after printing.

Next, to work around the print dialog issue, I bypass the web browser’s Print() method and call ExecWB directly (which Print() calls under the hood) with my own arguments. Note that to call ExecWB, the web browser must be cast to a COM interface of type IWebBrowser2. To include that interface (and a few constants) in my code, I added a reference to the “SHDocVw.dll” file (found in the “C:\WINDOWS\system32″ folder). The OLECMDEXEOPT_DONTPROMPTUSER value passed to ExecWB tells the browser to suppress the print dialog. Likewise, the pvaIn parameter contains a reference to PRINT_ARGS, which includes a “don’t bother the user” flag.

By the way, both flags in PRINT_ARGS are documented here on MSDN. Likewise, the IDM_PRINT command (which is what calling ExecWB with the OLECMDID_PRINT argument maps to) is documented here. If you read the docs you’ll see that the IDM_PRINT command takes a VARIANT of type VT_I2 for the pvaIn argument. In .NET, that maps to an object holding a reference to an Int16 structure (see this page on MSDN).

Lastly, you’ll see that PRINT_ARGS includes a “wait for completion” flag. This is what tells the ExecWB call to block until printing is completed. Why is this important? Because we don’t want to reset the default printer and exit the application until we’ve actually printed something. In fact, exiting too soon can cause the print job never to happen.

I hope this post was helpful. Even if you’re not printing to PDF, I hope knowing a little more about how the WebBrowser controls prints in general will help make it easier to use.

Posted in WebBrowser | Tagged: , , , , | 7 Comments »

Troubleshooting the “Failed to enable constraints…” DataSet Error

Posted by sapientcoder on July 30, 2008

At one time or another, many of us who have worked with DataSets in .NET have received this foreboding message:

Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.

I’ll go out on a limb here and say we probably all agree it’s not an incredibly useful message. So how do you find out what the heck actually happened to cause the error?

Error messages (real error messages, not the exceptionally “helpful” one above) in DataSets are stored at the column and row levels within the tables in the DataSet. Therefore, you can use diagnostic code such as the following to get these messages:

using System.Collections.Specialized; // for NameValueCollection class

...

try
{
    // code that throws DataSet exception
}
catch
{
    // print out a list of the "real" error messages in the DataSet
    NameValueCollection c = BuildDataSetErrorInfo(myDataSet);
    foreach (string name in c)
    {
        System.Diagnostics.Debug.WriteLine(name + c[name]);
    }
}

...

private static NameValueCollection BuildDataSetErrorInfo(DataSet dataSet)
{
    NameValueCollection errorInfo = new NameValueCollection();
    errorInfo.Add("DataSetName: ", dataSet.DataSetName);

    foreach (DataTable table in dataSet.Tables)
    {
        DataRow[] rows = table.GetErrors();
        if ((rows != null) && (rows.Length > 0))
        {
            errorInfo.Add(" - Table: ", table.TableName);
            foreach (DataRow row in rows)
            {
                errorInfo.Add("   - Row Error: ", row.RowError);
                DataColumn[] cols = row.GetColumnsInError();
                if ((cols != null) && (cols.Length > 0))
                {
                    foreach (DataColumn col in cols)
                    {
                        errorInfo.Add("     - Column: ", col.ColumnName);
                        errorInfo.Add("       Column Error: ", row.GetColumnError(col));
                    }
                }
            }
        }
    }

    return errorInfo;
}

Posted in DataSets | Tagged: , | Leave a Comment »