Tuesday, July 29, 2008

Managing Your Different Environments Using External Configuration Files

Download Example Solution: Click Here

One thing that I have found as I have worked for my current employer and worked on a large-scale web application is that configuration files can get hairy.  This has proven to be especially true as we have managed the different configurations for our different environments (development, integration, staging and production).

Our web.config files have grown to about 1000 lines, which becomes a bear when you have to make sure that 4 of those files are in sync.

 

External Config Files To The Rescue

Recently I stumbled upon the beauty of external configuration files, which has made me realize that I can isolate the configuration settings that change and only worry about managing those changes in each of the configuration files and not the entire set of configuration settings.

You may put all of your external configuration files in the root folder along with your web.config, but I'm warning you that this can get ugly in a hurry the more environments you have and as the sections of the configuration file you are breaking out.  I recommend making a sub-folder that will hold all of your external config files and reference them by prefixing the configSource with your sub-folder (e.g. configSource="Config\ConnectionStrings.config").  You may then use folders to organize the different types of configuration settings as well.

 

image

 

Keep in mind as you use external config files that you have to have a separate configuration file for each section of the configuration file that you break out.  You also can only specify an external config file at certain levels of the configuration file.  For example, you can't specify an external configuration file for the entire System.ServiceModel section of your config file.  However, you can specify an external config file at the client level under the System.ServiceModel section.

image

Download

Download the example project to see how you might us these external configuration files:

Click Here

Monday, July 28, 2008

When In Doubt, Regen The Proxies

As I've worked with web services, mainly WCF services, I've run into some really funky problems as I've consumed services that have made me want to rip my hair out.  Hopefully this little bit of advice will help many developers keep more of their hair intact.

Update your service reference before getting too far into trying to diagnose the problem:

image

This is often required when a namespace changes in the service code even if none of the method signatures have changed.  This has usually been the case when the service is mysteriously returning null when you know it shouldn't.

Cheers!

Tuesday, July 8, 2008

A Count Down Asp.NET User Control

Download Example Here:

Count Down Test Web Application

Recently at work, I did some work on our website that would alert our users that the website would be going down soon.  The customer wanted "count down" functionality that would give users an idea of how long they have until the site goes down as the time to take the website down approaches.

image

I figured there would be someone who had already done this sort of thing, so I went to Google.  I found an article by Robert Hashemian at http://www.hashemian.com/tools/javascript-countdown.htm that explains how you would create a count down timer with JavaScript.  His example was pretty slick, but I wanted to create a user control in .NET that I could just set the time of when the timer should expire and forget about all the JavaScript under the covers.  So I did just that.

At first I tried to use Mr. Hashemian's way of setting the ending time in the JavaScript and figuring out how much time was left there, but the problem I found was that the time was dependant on the browser's time, so I decided to change the way the initial number of seconds is determined.  Instead of setting the time in JavaScript, I decided to count how many seconds were left until the target time and just set the number of seconds in JavaScript, so the functionality wouldn't be disrupted by time zone differences.

Anyway, after some work, all I had to do was put the user control tag in my page and set the time in the code behind like so:


            this.countDown1.TimeToEnd = DateTime.Now.AddHours(1.02);

            this.countDown2.TimeToEnd = new DateTime(2050, 1, 1);

I've created a sample web app that uses this control shown in the examples above.

Download Here:

Count Down Test Web Application

Wednesday, June 25, 2008

Running Batch Jobs In An ASP.NET Web Application Using Application State

Download Example Files:

Sample Run Job Web Application

 

Recently while doing work for a client, I came across the need to run a job (batch) within the application while giving the user that ability to monitor the progress of the job and also being able to track how much time was remaining until the completion of the job.

Along with the aforementioned specs, the requirements were as follows:  they needed a web page that they could go to in order to start the job, they needed the website to be able to alert them if another job was being run, they needed to be able to cancel the job at any time.

I assume that this is a common need for businesses and would like to share a simple example that illustrates how one might go about solving such a problem.

image

How I Did It

Since I didn't want to create a database or something similar to handle such a task due to the overhead that it takes to create a database, I decided to store information about my 'job' in the Application State object (which is essentially a HashTable) due to the fact that Application State object could be accessed and modified across the entire application.  This way, anyone, regardless of who started a job, would be able to access the information regarding the job.

The meat of my solution lies in two things: The BatchRun class that I created (Figure 1), and the use of a special property that accesses the Applicaiton State object within my page (Figure 2).

 


 

    [Serializable]

    public class BatchRun

    {

        #region Constructors

 

        public BatchRun()

        {

        }

 

        public BatchRun(int totalNumberOfItems)

        {

            TotalNumberOfItems = totalNumberOfItems;

        }

 

        #endregion

 

        #region Properties

 

        public DateTime? LastUpdatedTime { get; set; }

        public DateTime? StartTime { get; set; }

        public int TotalNumberOfItems { get; set; }

        public int ItemsCompleted { get; private set; }

        public bool ShouldStop { get; set; }

 

        public bool HasNotBegun

        {

            get

            {

                return StartTime == null;

            }

        }

 

        public bool IsCompletedOrExpired

        {

            get

            {

                return PercentDone == 100 || ShouldStop || (LastUpdatedTime != null && LastUpdatedTime.Value < DateTime.Now.AddMinutes(-Settings.Default.StalledMinuteWait));

            }

        }

 

        public int PercentDone

        {

            get

            {

                if (TotalNumberOfItems == 0)

                    return 0;

                return (int)(100 * ((double)ItemsCompleted / (double)TotalNumberOfItems));

            }

        }

 

        private TimeSpan? TotalTime

        {

            get

            {

                if (StartTime == null)

                    return null;

                return DateTime.Now - StartTime.Value;

            }

        }

 

        public TimeSpan EstimatedTimeRemaining

        {

            get

            {

                if (ItemsCompleted == 0 || TotalTime == null)

                    return default(TimeSpan);

                return TimeSpan.FromSeconds(

                (int)((TotalTime.Value.TotalSeconds / ItemsCompleted) * (TotalNumberOfItems - ItemsCompleted)));

            }

        }

        #endregion

 

        #region Public Methods

        public void Start()

        {

            if (TotalNumberOfItems == 0)

            {

                throw new ArgumentException("Total Number of Items not set!", "TotalNumberOfItems");

            }

            StartTime = DateTime.Now;

            LastUpdatedTime = DateTime.Now;

        }

 

        public bool IncrementItemsCompleted()

        {

            if (ItemsCompleted < TotalNumberOfItems)

            {

                LastUpdatedTime = DateTime.Now;

                return (++ItemsCompleted == TotalNumberOfItems);

            }

            return true;

        }

        #endregion

    }

Figure 1

 

 


        protected BatchRun CurrentBatchRun

        {

            get { return (BatchRun)this.Application["CurrentBatchRun"]; }

            set { this.Application["CurrentBatchRun"] = value; }

        }

Figure 2

 

The BatchRun class keeps all of the information that you need to derive what percent of the job is complete as well as how much time is left.  As you can see, I've included some intelligent properties within the BatchRun class to make these calculations easier and uniform.

The special application state property allows you to just access and modify the information about the job without having to be thinking about where you are storing this data.

The rest of the solution is just hooking into this information to let the user know what the status of the job is.

Here's the entire code behind file for the batch run example web page (please note that not everything I've done here is optimal, but is merely to give an example of how one would accomplish such a task):

 


using System;

using System.Threading;

 

namespace RunJobWebsite

{

    public partial class _Default : System.Web.UI.Page

    {

        protected BatchRun CurrentBatchRun

        {

            get { return (BatchRun)this.Application["CurrentBatchRun"]; }

            set { this.Application["CurrentBatchRun"] = value; }

        }

 

        protected void Page_Load(object sender, EventArgs e)

        {

            if (!IsPostBack)

            {

                this.txtStartDate.Text = DateTime.Today.AddMonths(-1).ToShortDateString();

                this.txtEndDate.Text = DateTime.Today.ToShortDateString();

            }

        }

 

        protected void btnRunBatch_Click(object sender, EventArgs e)

        {

            SetBatchParameterTableVisibility(false);

 

            if (!CurrentBatchInProgress())

            {

                this.lblPercentage.Text = "0";

                this.lblTimeRemaining.Text = "Unknown";

                CurrentBatchRun = new BatchRun();

                var ts = new ThreadStart(RunBatch);

                var thread = new Thread(ts);

                thread.Start();

            }

            else

            {

                ShowAlert("Statement Batch Already In Progress");

            }

            timerBatchRun.Enabled = true;

        }

 

        private void RunBatch()

        {

            DateTime startDate = DateTime.Parse(this.txtStartDate.Text);

            DateTime endDate = DateTime.Parse(this.txtEndDate.Text);

 

            var tempDate = startDate;

            int i = 0;

            while (tempDate < endDate)

            {

                i++;

                tempDate = tempDate.AddHours(1);

            }

 

            CurrentBatchRun.TotalNumberOfItems = i;

            CurrentBatchRun.Start();

 

            tempDate = startDate;

            while (tempDate < endDate)

            {

                DoSomething(new Random().Next(1, 5));

                tempDate = tempDate.AddHours(1);

                if (CurrentBatchRun == null || CurrentBatchRun.ShouldStop)

                {

                    break;

                }

                CurrentBatchRun.IncrementItemsCompleted();

            }

        }

 

        private void DoSomething(int seconds)

        {

            Thread.Sleep(seconds * 1000);

        }

 

        private void SetBatchParameterTableVisibility(bool visible)

        {

            this.tblBatchParameters.Visible = visible;

            this.tblBatchProgress.Visible = !visible;

        }

 

        private bool CurrentBatchInProgress()

        {

            var batchRun = CurrentBatchRun;

            if (batchRun == null || batchRun.HasNotBegun)

            {

                return false;

            }

            return !batchRun.IsCompletedOrExpired;

        }

 

        protected void timerBatchRun_Tick(object sender, EventArgs e)

        {

            BatchRun currentBatch = CurrentBatchRun;

            if (currentBatch != null && currentBatch.HasNotBegun)

            {

                return;

            }

            if (currentBatch == null)

            {

                SetBatchParameterTableVisibility(true);

                return;

            }

            if (currentBatch.IsCompletedOrExpired)

            {

                if (currentBatch.ShouldStop)

                {

                    ShowAlert(String.Format("Statement Run Completed at {0}, but was cancelled.", currentBatch.LastUpdatedTime));

                }

                else

                {

                    ShowAlert(String.Format("Statement Run Completed at {0}.", currentBatch.LastUpdatedTime));

                }

                timerBatchRun.Enabled = false;

                SetBatchParameterTableVisibility(true);

            }

            else

            {

                this.lblPercentage.Text = currentBatch.PercentDone.ToString();

                this.lblTimeRemaining.Text = String.Format("{0:f2}", currentBatch.EstimatedTimeRemaining.TotalMinutes);

            }

        }

 

        private void ShowAlert(string alert)

        {

            Response.Write("<script>alert('" + alert + "')</script>");

        }

        protected void btnStop_Click(object sender, EventArgs e)

        {

            ShowAlert("Batch Run Stopped");

            if (CurrentBatchRun != null)

            {

                CurrentBatchRun.ShouldStop = true;

                this.timerBatchRun.Enabled = false;

                SetBatchParameterTableVisibility(true);

            }

        }

 

 

    }

}

 

Download Example Files:

Sample Run Job Web Application

Thursday, June 19, 2008

Hacking The Zune Podcast Feature To Give You Bookmarks For Your Audio Books

I got a Zune (www.zune.net) about a year and a half ago and have been quite happy with it. One of the major features that was lacking was the ability to save your place if you were listening to an audio book. I spend a lot of my time listening to self-help audio books and religious audio books. In order to keep track of my place in the audio book I realized I had two choices: just listen to one audio book and nothing else since the Zune remembers your last listening location, or have my audio books have many small tracks and try to remember the track that I was on.

As you can imagine, both of these methods have serious follies. I looked online to see if there was some sort of hack that could provide some sort of bookmark functionality, but didn't find anything of value. I did, however, stumble upon a forum post where someone suggested that you put your audio files into the Podcast section of your Zune. I realized that the Zune keeps track of your last listening location in each of your Podcast episodes. I immediately tried to drag files into the Podcast section of the Zune software, but it didn't take.

After a lot of investigation and trial and error, I found that marking the mp3 as an "ITUNESPODCAST" (iTunes podcast), if you drag it into your Zune software under music, it would immediately recognize it as a Podcast.

Here's how I did it:

First, download Mp3Tag (http://www.mp3tag.de/en/download.html), which is software you are going to want regardless. Mp3Tag makes it SOOO much easier to tag your mp3s with proper information about the album, artist, song, etc.


Second, you're going to want to change a couple settings to make Mp3Tag a little easier to use.

Set Mp3Tag so it doesn't annoy you every time you save mp3 tag information:

Under Tools-> Options-> Messages

image

Now make it so Mp3Tag will auto-save everything you change so you don't have to manually save every time you make a change:

Under Tools-> Options-> Tags

image


Third, open the mp3 you will want to add by either dragging it into Mp3Tag or using the open dialog.


Fourth, add the ITUNESPODCAST=1 extended tag to the mp3 by right-clicking on the mp3 in Mp3Tag and click on "Extended Tags..."

image

image

You can also set up your other tag info (you will mainly be concerned about the Title and Album information) for your Podcast file.


Fifth, just go to the 'Music' section of your Zune software and drag your quasi Podcast file into your Zune software, or put it where Zune will pick it up, and you now have a Podcast file that will save your place when you listen to it!


Merging / Joining / Combining Mp3s

I am a big audio book person and have downloaded and ripped a lot of audio books that were in many small audio files. I prefer having just one mp3 per audio book to keep my music library clean.

After trying out a few "try before you buy" programs that help you merge audio files, I stumbled upon MergeMp3, a free application that Aleksandr Gekht, a very nice person, wrote and wanted to let everyone use for free.

Check it out:

http://www.komkon.org/~shchuka/software/mergemp3/#download 

Tuesday, June 17, 2008

Using External Config Files For .NET Applications

A co-worker of mine came across a blog post about using external configuration files: http://blog.andreloker.de/post/2008/06/Keep-your-config-clean-with-external-config-files.aspx.

I realized that this can be incredibly useful, as we have had issues with config files at my current job.  Currently, we are managing four different configuration files: one for development, integration, staging and production.  Each of these config files have almost the exact same content, but differ in just a few ways.  I anticipate just having one main config file, but keeping a set of four external config files for those pieces of the configuration files that change.  We'll see how it goes.

I did a little experiment using these external config files on a "Cleanup" console app that I was working on.  I followed the example of the mentioned blog post, but ran into a couple blocks.

One error I got stated that 'The attribute 'configSource' cannot be specified because its name starts with the reserved prefix 'config'.' This happened because I was trying to specify a configSource for the System.ServiceModel section of my app.config.  I was trying to do this because of the fact that the URL's to WCF services in the client section might change.  It turns out that you can only specify a configSource for the child elements of the System.ServiceModel section.  So I changed the section that had a configSource to the 'client' element, and it fixed it.

Once I got that fixed, I ran into another issue stating 'Unable to open configSource file <Config File Name>.', which I had obviously created.  I couldn't figure out why this was happening, but then I realized that the application, due to the fact that it was an executable, was looking in the BIN/Debug folder, not the root of my application.  To remedy this, I made the custom .config files so they would 'Copy Always,' and then my app worked!  You can refer to the screen shot below:

image

Thursday, June 5, 2008

Piece By Piece Query Building With Linq

In numerous projects in the past, I've found myself needing to write code and build sql queries that filtered results based on one or more criteria and any combination of criteria. I've also needed to write code that returns extra parts of an entity (typically involving the joining of tables, but only under certain conditions.

In this post, I'm assuming that you know at least the very basics about Linq - how to create a DataContext class from a the Linq to SQL designer, select from it's tables and filter the results.

In the past, I have done a few things to solve this type of problem. I would create queries for each scenario, which resulted in a maintenance headache due to the numerous code changes every time a small change was introduced, and of course, more places to mess up. I also tried formulating my stored procedures to return different result sets depending on boolean parameters passed in. This, of course proved to be non-intuitive and error prone due to the variation in the type of data that was coming back. As a result, I felt I always had to choose between a lot of code, or non-intuitive code.

Linq to SQL - Building Queries Piece By Piece

Thanks to expression building capabilities of Linq, we can now build our queries one piece at a time without having to do tricky dynamic sql building while avoiding the caveats of the aforementioned approaches.

An example of a case when you would want to do this sort of thing would be (using an annoying Northwind database example) if you wanted to search for Orders based on any combination of criteria that you allow (e.g. searching by OrderDate, Freight, ShipCountry etc.) and possibly populating the order details of each order.

Traditionally, this would prove to be a bit of a challenge due to the number of queries or complexity of the query you would write to accomplish this, not to mention all of the CRUD code you would have to write. Thanks to Linq, we can now accomplish this in a matter of a handful of lines.

Writing Your Query Piece By Piece

So how is this done? As you create your queries, instead of returning result sets, you are returning an expression tree. An 'expression tree' can be thought of as a query builder in the sql sense. When you are writing your 'select statements' in your code, Linq is storing what it needs to know to query the database when you finally are ready to get the results back.

This means, using the Northwind orders example above, we can just keep defining our Order query piece by piece in a very natural way and we can know that when we are hitting the database, we are getting back exactly what we want.

Here's an example of how you might query Orders in a Northwind database using this 'piece by piece' query approach:





using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Data.Linq;

 

namespace NorthwindLinqExample

{

    public partial class NorthwindQuery : Form

    {

        public NorthwindQuery()

        {

            InitializeComponent();

        }

 

        private void btnSearch_Click(object sender, EventArgs e)

        {

 

            using (NorthwindDataContext database = new NorthwindDataContext())

            {

                // get the query for all orders

                var orderQuery = from o in database.Orders select o;

 

                // checks to see if you want to filter by order date

                if (this.chkUseOrderDate.Checked)

                {

                    orderQuery = filterOrderQueryByOrderDate(orderQuery);

                }

 

                // checks to see if you want to filter by freight

                if (this.txtFreight.Text != String.Empty)

                {

                    orderQuery = filterOrderQueryByFreight(orderQuery);

                }

 

                // checks to see if you want to filter by shipping country

                if (this.cmbShipCountry.Text != String.Empty)

                {

                    orderQuery = filterOrderQueryByShipCountry(orderQuery);

                }

 

                // checks to see if you want to load order details also

                if (this.chkLoadOrderDetails.Checked)

                {

                    setQueryToLoadOrderDetails(database);

                }

 

                // sets the results of the query as the data source on our data grid

                this.gvResults.DataSource = orderQuery.ToList();

            }

        }

 

 

        private void setQueryToLoadOrderDetails(NorthwindDataContext database)

        {

            var loadOptions = new DataLoadOptions();

            loadOptions.LoadWith<Order>(o => o.Order_Details);

            database.LoadOptions = loadOptions;

        }

 

        private IQueryable<Order> filterOrderQueryByOrderDate(IQueryable<Order> orderQuery)

        {

            var filterValue = this.dateTimeOrderDate.Value.Date;

 

            return from o in orderQuery

                   where o.OrderDate >= filterValue &&

                   o.OrderDate <= filterValue.AddDays(1)

                   select o;

        }

 

        private IQueryable<Order> filterOrderQueryByFreight(IQueryable<Order> orderQuery)

        {

            var filterValue = this.txtFreight.Text;

 

            return from o in orderQuery

                   where o.Freight == filterValue

                   select o;

        }

 

        private IQueryable<Order> filterOrderQueryByShipCountry(IQueryable<Order> orderQuery)

        {

            var filterValue = this.cmbShipCountry.Text;

 

            return from o in orderQuery

                   where o.ShipCountry == filterValue

                   select o;

        }

    }

}

Friday, May 30, 2008

Quick Code Generation With Excel

Example Download: Example Excel Code Generation Excel File

Often times, I have found myself in a place where I want to generate some code from a list of items that I was given or that I had created, but were only a one-time thing and creating an xslt transform, a mini code generation program, or a template in a code generation suite such as CodeSmith Studio or MyGeneration would be overkill.

In the past, I would just write all the code out by hand while copy and pasting from my list. There were a couple problems with this approach, which I'm sure many of you are familiar with: you often make copy-paste errors, and it is a lot more time consuming than it needs to be.

Recently, I have discovered the power of using Excel to help me with these kinds of tasks. All you need to know is a few functions and operators and how to copy and paste, and you can significantly cut down the time that it takes to finish such tasks.


Starting Out

Make sure you have Excel or another spreadsheet application with similar functionality (Many who do not want to pay the premium for the Microsoft Office suite are using Open Office, an open source office suite).


The Basics

The main thing to understand before you start is that anything that has carriage returns can be copied and pasted into excel as cells in a column. Once you have these values separated into cells, you can work with the values quite easily.

I feel that is easiest to learn through examples, so I will demonstrate how to generate code by using the example of making an insert script for a static company table. You get a list of companies to be inserted from your customer. Each record will have an integer id, and the company name. You also need to make an enumeration for each of the companies with no spaces, commas, or periods.

Here's your list (These are the first 40 Fortune 500 companies in alphabetical order):

3M Company
Abbott Laboratories
ADM
Advance Auto Parts, Inc.
Advanced Micro Devices, Inc.
Aetna Inc.
Affiliated Computer Services, Inc.
Aflac Incorporated
AGCO Corporation
Agilent Technologies, Inc.
Air Products and Chemicals, Inc.
AK Steel Holding Corporation
Albertson's, Inc.
Alcoa Inc.
Allied Waste Industries, Inc.
ALLTEL Corporation
Altria Group, Inc.
Amazon.com, Inc.
Ameren Corporation
American Electric Power Company, Inc.
American Express Company
American Family Insurance Group
American Financial Group, Inc.
American International Group, Inc.
American Standard Companies Inc.
AmerisourceBergen Corporation
Amgen Inc.
AMR Corporation
Anadarko Petroleum Corporation
Anheuser-Busch Companies, Inc.
Aon Corporation
Apache Corporation
Apple Computer, Inc.
Applied Materials, Inc.
ARAMARK Corporation
Arrow Electronics, Inc.
ArvinMeritor, Inc.
Asbury Automotive Group, Inc.
Ashland Inc.
Assurant, Inc.

You copy this list and single click the first cell in your Excel sheet.  It should appear like so:
image 

Now we're going to use some formulas along with concatenating strings to achieve our code generation.  If you are building anything dynamic, you must always start with an equals ('=') sign. Example: '= A1' would make the cell in which you currently are show up as the first cell in your worksheet like so:
image 

The functions and operators that I find most useful:
  • & - you use this to concatenate different values together
    • Example: ="first value" & "second value"
  • SUBSTITUTE - Will replace one value with another in a given text
    • Example: =SUBSTITUTE(A1," ","") will replace all empty spaces with nothing

    • LEFT, RIGHT, and MID - Will find the position of a string within a given text

    • Alt + Enter - will put a carriage return in your cell (Enter will take you to the next cell)

    • ROW - will give you the row number of a reference

    LET'S DO IT!

    According to the example description, you need to do a couple things:

    • You need to create the insert statement for company.

    • You need to remove the spaces, commas and periods from the name for the enumeration.

    Let's do the first one. The insert statement will most likely look similar to this:

    INSERT INTO Company (Id, Description) Values (<number>, '<description>')


    First we know that we're going to need an Id. We can use the 'ROW' function described above. Next, we know we can just use the company name as the description, we just need to add quotes.

    Our formula will look like this:

    ="INSERT INTO Company (Id, Description) Values (" & ROW(A1) & ", '" & A1 & "')"


    image


    Now we can copy and paste or just drag our formula down using the little square in the bottom right corner of the cell of our formula like so:

    image


    Now for the enumeration. We will need to remove all spaces, commas and periods in the company name and put a comma at the end. We will use the SUBSTITUTE function to replace the necessary characters and concatenate a comma to the end of each line with the following formula:

    = SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(A1, " ", ""),".",""),",","") & ","

    image


    This may look a little hairy, but all we're doing is removing the spaces, removing the periods from that text, and then removing the commas from that text then concatenating a comma to the end.

    As you can see, it is really easy to get some nice, quick code generation out of excel in a matter of minutes, sometimes seconds if you just know a few tricks. Download the excel file below to take a closer look:


    DOWNLOAD

    Example Excel Code Generation Excel File


    Tuesday, May 27, 2008

    Creating Pdf's with Adobe Acrobat, C# and ItextSharp

    Recently I have been creating a large number of dynamic pdf's for a client. I have learned a lot of ways to poorly create pdf files and have also found some pretty good ones. I'm hoping that this post will help others avoid some of the pitfalls and blocks that I've run into while generating dynamic pdf files.

    This post will most likely not cover a solution to all scenarios, but will likely be a solution or at least a starting point to most pdf generation problems.

    I have included with this post a sample solution from which I have drawn the example code snippets and screen shots in this post. I've included links to these example files at the end of the post also.

    DOWNLOAD

    Zipped Example Solution
    Sample Pdf Form
    Example Excel File

    Things you will want to do before you begin:

    ItextSharp - An Open Source PDF Creation/Manipulation Library

    ItextSharp is a port from another open source project called itext, which is a long running java, open-source pdf library (http://www.lowagie.com/iText/). Between the different projects, there is a pretty good following, and as a result, there are quite a few examples of people accomplishing different tasks between the two projects. Luckily, java code is a lot like C# and is pretty easy to convert over with just a few tweaks.

    Adobe Acrobat Professional- A PDF Creation/Manipulation Desktop Application

    Acrobat does about anything that you could ever want to do with a pdf file in a desktop application. For this post, It will primarily be used to create pdf forms that we will fill in with our code. To see an example of how to create a form, go to http://www.adobe.com/products/acrobat/solutions/detail/create_form.html.

    Starting Out

    According to my understanding, most people who have to generate some sort of Pdf will want to do one or more of the following actions:

    • Put text into certain areas of a pdf page (like a gift card)
    • Put dynamic table-style information on a pdf (like an invoice)
    • Put images in certain regions of a pdf page (like a graph)

    In this post I cover how to put text into certain areas of a pdf. I will try to cover the other two actions in subsequent posts.

    In almost all cases, in order to do any of these things, you will want a "template" pdf to start out with. There are ways to build the entire pdf in code, but having a base to start out with can save a lot of time. Most pdf desktop applications allow you to export documents into a pdf (Word, Excel, etc.). I usually have created my "template" pdf in excel and then have used the print to pdf option to create my pdf.

    Creating a Form from a PDF Page

    In order to put text in certain regions of a pdf file, I've found it best to create a pdf form (as in the forms you often find online that allow you to type the information directly onto the pdf) which allows you to choose the format and alignment of the text. I will go through the steps you would take if using Adobe Acrobat Professional as your pdf form creation software.

    First, open your "template" pdf file to which you want to add text. For my example, I will use a pdf that represents a template for an address change notice that a company might want to send out that I created from excel.

    Second, open up the Form toolbar

    image

    Third, add the form text fields to your pdf and give each of the fields unique, intuitive names according to what text will be put in the field.

    image

    Fourth, make the appropriate tweaks to the formatting and alignment with the 'Appearance' and 'Options' tabs of the 'Text Field Properties' window.

    Note: To have more direct control over the size of the text, use an absolute font size instead of the default 'Auto' size. Also, the options tab will give you the ability to set the alignment of your text and set the field to 'Multiline' (like a textbox in windows forms).

    image image

    Fifth, make sure that the size and formatting of the text is going to be appropriate for your pdf. Click 'Preview' on the Forms toolbar and type something in each of your form text fields to ensure that the characters are showing up as expected.

    Note: Make sure that you delete the text that you have typed into the fields before you save it for good.

    image

    Show Me Some Code Already!!!

    Now you should have a pdf with form text fields in it for each of the areas on the pdf in which you want text displayed. Now let's show you how to put any text from our code into the text fields that we created in our form. Here are the steps you would take to fill the form:

    First, you need to add a reference to the ITextSharp library in your project (you can download the library at http://sourceforge.net/project/platformdownload.php?group_id=72954). For my example, I have made a windows form application with fields that correspond with the fields on the pdf form I created.

    Second, you create a PdfReader that your code will use to read the pdf form that you created:

    PdfReader reader = new PdfReader(<unfilled pdf file path goes here>); 
     

    Third, you make a PdfStamper which allows you to put text into your form fields:

    PdfStamper formFiller = new PdfStamper(reader, new FileStream(<ouput file path goes here>, FileMode.<file mode>));

    Fourth, you get the form text fields (AcroFields) from the pdf file:

    AcroFields formFields= formFiller.AcroFields;
     

    Fifth, you use the SetField method of the AcroFields object to put your text into the field:

    formFields.SetField("<field name from pdf>", <value to insert>);
     

    Sixth, you 'flatten' your pdf by removing the fields while leaving the values of the fields in tact:

    formFiller.FormFlattening = true;

    Seventh, close your PdfStamper and PdfReader objects to make sure that all resources are disposed of:

    formFiller.Close();
    reader.Close();


    Your done! After following these steps, you should now have a pdf filled with any values you specified.


    Here is an example windows form code beside:










        1 using System;

        2 using System.Collections.Generic;

        3 using System.ComponentModel;

        4 using System.Data;

        5 using System.Drawing;

        6 using System.Linq;

        7 using System.Text;

        8 using System.Windows.Forms;

        9 using iTextSharp.text.pdf;

       10 using System.IO;

       11 using System.Diagnostics;

       12 

       13 namespace PdfStamperSample

       14 {

       15     public partial class PdfTestForm : Form

       16     {

       17         public PdfTestForm()

       18         {

       19             InitializeComponent();

       20         }

       21 

       22         private void btnCreatePdf_Click(object sender, EventArgs e)

       23         {

       24             // Grab each of the values from our windows form

       25             string accountNumber = txtAccountNumber.Text;

       26             DateTime pdfDate = datePdfDate.Value;

       27             string oldAddress = txtOldAddress.Text;

       28             string newAddress = txtNewAddress.Text;

       29 

       30             string outputFilePath = @"..\..\AddressChangeNotice_filled.pdf";

       31 

       32             // Get pdf from project directory

       33             PdfReader reader = null;

       34             try

       35             {

       36                 reader = new PdfReader(@"..\..\AddressChangeNotice.pdf");

       37 

       38                 // Create the form filler

       39                 using (FileStream pdfOutputFile = new FileStream(outputFilePath, FileMode.Create))

       40                 {

       41                     PdfStamper formFiller = null;

       42                     try

       43                     {

       44                         formFiller = new PdfStamper(reader, pdfOutputFile);

       45 

       46                         // Get the form fields

       47                         AcroFields addressChangeForm = formFiller.AcroFields;

       48 

       49                         // Fill the form

       50                         addressChangeForm.SetField("AccountNumber", accountNumber);

       51                         addressChangeForm.SetField("Date", pdfDate.ToShortDateString());

       52                         addressChangeForm.SetField("OldAddress", oldAddress);

       53                         addressChangeForm.SetField("NewAddress", newAddress);

       54 

       55                         // 'Flatten' (make the text go directly onto the pdf) and close the form

       56                         formFiller.FormFlattening = true;

       57                     }

       58                     finally

       59                     {

       60                         if (formFiller != null)

       61                         {

       62                             formFiller.Close();

       63                         }

       64                     }

       65                 }

       66             }

       67             finally

       68             {

       69                 reader.Close();

       70             }

       71 

       72             // Open the created/filled pdf

       73             if (MessageBox.Show(String.Format("Pdf filled - {0}.\nWould you like to open the filled pdf?",

       74                 Path.GetFullPath(outputFilePath)),

       75                 "PDF Created", MessageBoxButtons.YesNo) == DialogResult.Yes)

       76             {

       77                 Process.Start(outputFilePath);

       78             }

       79 

       80         }

       81     }

       82 }






    DOWNLOAD

    Zipped Example Solution
    Sample Pdf Form
    Example Excel File