Calculate Shipping Cost Using UPS OnLine Tools

Before using this code, all you need to do is register for an UPS OnLine Tools account and then apply for a XML Access key using your developer key which you will receive after creating an account. After you get your access key, just plug your username, password and the key into the highlighted line below and you should be good to go!

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;

/// <summary>
/// Summary description for ShippingCalculator
/// </summary>
namespace MyCo.BusinessLogic
{
    public static class ShippingCalculator
    {
        public static decimal GetShippingCost(string shipFromZipCode, string shipToZipCode, string serviceRateCode, string packageWeight)
        {
            decimal shippingCost = -1;
            string URL = "https://www.ups.com/ups.app/xml/Rate";
            WebRequest objRequest = WebRequest.Create(URL);
            objRequest.Method = "POST";
            objRequest.ContentType = "application/x-www-form-urlencoded";

            using (StreamWriter writer = new StreamWriter(objRequest.GetRequestStream()))
            {
                writer.Write(GetAuthXML("accesskey", "username", "password"));
                writer.Write(GetRequestXML(shipFromZipCode, shipToZipCode, serviceRateCode, packageWeight));
                writer.Flush();
                writer.Close();
            }

            objRequest.Timeout = 5000;

            try
            {
                using (WebResponse objResponse = objRequest.GetResponse())
                {
                    XmlDocument xmlResponse = new XmlDocument();
                    xmlResponse.Load(objResponse.GetResponseStream());
                    int responseStatusCode = int.Parse(xmlResponse.SelectSingleNode("//RatingServiceSelectionResponse/Response/ResponseStatusCode").InnerText);

                    if (responseStatusCode == 1)
                    {
                        XmlNodeList xmlNodeList = xmlResponse.SelectNodes(string.Format("/RatingServiceSelectionResponse/RatedShipment/TotalCharges[../Service/Code={0}]/MonetaryValue", serviceRateCode));

                        foreach (XmlElement xmlElement in xmlNodeList)
                        {
                            shippingCost = decimal.Parse(xmlElement.InnerText);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //Log exception
            }

            return shippingCost;
        }
        private static string GetAuthXML(string accessLicenseNumber, string userID, string password)
        {
            StringBuilder xmlAuthTemplate = new StringBuilder();

            xmlAuthTemplate.Append("<?xml version="1.0"?>");
            xmlAuthTemplate.Append("<AccessRequest xml:lang="en-US">");
            xmlAuthTemplate.Append(string.Format("<AccessLicenseNumber>{0}</AccessLicenseNumber>", accessLicenseNumber));
            xmlAuthTemplate.Append(string.Format("<UserId>{0}</UserId>", userID));
            xmlAuthTemplate.Append(string.Format("<Password>{0}</Password>", password));
            xmlAuthTemplate.Append("</AccessRequest>");
            xmlAuthTemplate.Append("<?xml version="1.0"?>");

            return xmlAuthTemplate.ToString();
        }
        private static string GetRequestXML(string shipFromZipCode, string shipToZipCode, string serviceRateCode, string packageWeight)
        {
            StringBuilder xmlRequestTemplate = new StringBuilder();

            xmlRequestTemplate.Append("<RatingServiceSelectionRequest xml:lang="en-US">");
            xmlRequestTemplate.Append("<Request>");
            xmlRequestTemplate.Append("<TransactionReference>");
            xmlRequestTemplate.Append("<CustomerContext>Rating and Service</CustomerContext>");
            xmlRequestTemplate.Append("<XpciVersion>1.0001</XpciVersion>");
            xmlRequestTemplate.Append("</TransactionReference>");
            xmlRequestTemplate.Append("<RequestAction>Rate</RequestAction>");
            xmlRequestTemplate.Append("<RequestOption>shop</RequestOption>");
            xmlRequestTemplate.Append("</Request>");
            xmlRequestTemplate.Append("<PickupType>");
            xmlRequestTemplate.Append("<Code>01</Code>");
            xmlRequestTemplate.Append("</PickupType>");
            xmlRequestTemplate.Append("<Shipment>");
            xmlRequestTemplate.Append("<Shipper>");
            xmlRequestTemplate.Append("<Address>");
            xmlRequestTemplate.Append(string.Format("<PostalCode>{0}</PostalCode>", shipFromZipCode));
            xmlRequestTemplate.Append("</Address>");
            xmlRequestTemplate.Append("</Shipper>");
            xmlRequestTemplate.Append("<ShipTo>");
            xmlRequestTemplate.Append("<Address>");
            xmlRequestTemplate.Append(string.Format("<PostalCode>{0}</PostalCode>", shipToZipCode));
            xmlRequestTemplate.Append("</Address>");
            xmlRequestTemplate.Append("</ShipTo>");
            xmlRequestTemplate.Append("<Service>");
            xmlRequestTemplate.Append(string.Format("<Code>{0}</Code>", serviceRateCode));
            xmlRequestTemplate.Append("</Service>");
            xmlRequestTemplate.Append("<Package>");
            xmlRequestTemplate.Append("<PackagingType>");
            xmlRequestTemplate.Append("<Code>02</Code>");
            xmlRequestTemplate.Append("<Description>Package</Description>");
            xmlRequestTemplate.Append("</PackagingType>");
            xmlRequestTemplate.Append("<Description>Rate Shopping</Description>");
            xmlRequestTemplate.Append("<PackageWeight>");
            xmlRequestTemplate.Append(string.Format("<Weight>{0}</Weight>", packageWeight));
            xmlRequestTemplate.Append("</PackageWeight>");
            xmlRequestTemplate.Append("</Package>");
            xmlRequestTemplate.Append("<ShipmentServiceOptions/>");
            xmlRequestTemplate.Append("</Shipment>");
            xmlRequestTemplate.Append("</RatingServiceSelectionRequest>");
            xmlRequestTemplate.Append("</RatingServiceSelectionRequest>");

            return xmlRequestTemplate.ToString();
        }
    }
}

Send Templated Emails Using MailDefinition Object

Recently I discovered a better way to format my email messages in ASP.NET using the MailDefinition object. It lets you to use an email template and define tokens which you want to replace in it. This helps keep the presentation and business layers clean & seperate and lets the designers go in and edit the email templates without having to navigate the StringBuilder jungle.

Here’s how its done.

private void SendEmail(long customerID)
{
	Customer customer = CustomerData.GetCustomer(customerID);

	MailDefinition mailDefinition = new MailDefinition();
	mailDefinition.BodyFileName = "~/Email-Templates/Order-Confirmation.html";
	mailDefinition.From = "[email protected]";

	//Create a key-value collection of all the tokens you want to replace in your template...
	ListDictionary ldReplacements = new ListDictionary();
	ldReplacements.Add("<%FirstName%>", customer.FirstName);
	ldReplacements.Add("<%LastName%>", customer.LastName);
	ldReplacements.Add("<%Address1%>", customer.Address1);
	ldReplacements.Add("<%Address2%>", customer.Address2);
	ldReplacements.Add("<%City%>", customer.City);
	ldReplacements.Add("<%State%>", customer.State);
	ldReplacements.Add("<%Zip%>", customer.Zip);

	string mailTo = string.Format("{0} {1} <{2}>", customer.FirstName, customer.LastName, customer.EmailAddress);
	MailMessage mailMessage = mailDefinition.CreateMailMessage(mailTo, ldReplacements, this);
	mailMessage.From = new MailAddress("[email protected]", "My Site");
	mailMessage.IsBodyHtml = true;
	mailMessage.Subject = "Order Confirmation";

	SmtpClient smtpClient = new SmtpClient(ConfigurationManager.AppSettings["SMTPServer"].ToString(), 25);
	smtpClient.Send(mailMessage);
}

Your email template could be of any extension (txt, html, etc) as long as its in a text format. I personally like to keep it in HTML format so that we can preview the email template in a browser. Basically it’ll looks something like this -

Hello <%FirstName%> <%LastName%>,

Thank you for creating an account with us. Here are your details:

<%Address1%>,
<%Address2%>
<%City%>, <%State%> <%Zip%>

Thank You,
My Site

Create PDF Forms using iTextSharp

Generating PDF forms in .NET has always been somewhat messy and complicated. I’ve seen people convert raw HTML to PDF or adding and formatting form elements to a PDF doc in code behind, etc. All those methods are highly time consuming and tricky. Easiest way to create PDF Forms is by using iTextSharp (open source) and Adobe LiveCycle Designer.

You can just create your custom form template using LiveCycle and then data bind the form fields using iTextSharp like this:

User user = UserData.GetUserByID(userID);
string randomFileName = Helpers.GetRandomFileName();
string formTemplate = Server.MapPath("~/FormTemplate.pdf");
string formOutput = Server.MapPath(string.Format("~/downloads/Forms/Form-{0}.pdf", randomFileName));

PdfReader reader = new PdfReader(formTemplate);
PdfStamper stamper = new PdfStamper(reader, new System.IO.FileStream(formOutput, System.IO.FileMode.Create));
AcroFields fields = stamper.AcroFields;

// set form fields
fields.SetField("Date", DateTime.Now.ToShortDateString());
fields.SetField("FirstName", user.FirstName);
fields.SetField("LastName", user.LastName);
fields.SetField("Address1", user.Address1);
fields.SetField("Address2", user.Address2);
fields.SetField("City", user.City);
fields.SetField("State", user.State);
fields.SetField("Zip", user.Zip);
fields.SetField("Email", user.Email);
fields.SetField("Phone", user.Phone);

// set document info
System.Collections.Hashtable info = new System.Collections.Hashtable();
info["Title"] = "User Information Form";
info["Author"] = "My Client";
info["Creator"] = "My Company";
stamper.MoreInfo = info;

// flatten form fields and close document
stamper.FormFlattening = true;
stamper.Close();

ADO.NET Data Access Template

Here’s a basic ADO.NET data access template with a SqlCacheDependency. Just wanted to post this for my future reference.

public static DataSet GetData(long itemID)
{
    DataSet ds = new DataSet();
    string cacheKey = Helpers.GetCacheKey(itemID); //Get a cache key unique to this method.

    if (HttpContext.Current.Cache[cacheKey] != null)
    {
        ds = (DataSet)HttpContext.Current.Cache[cacheKey];
    }
    else
    {
        using (SqlConnection sqlConnection = new SqlConnection(WebConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
        {
            using (SqlCommand sqlCommand = new SqlCommand())
            {
                sqlCommand.Connection = sqlConnection;
                sqlCommand.CommandType = CommandType.StoredProcedure;
                sqlCommand.CommandText = "sp_Items_GetItemByID";
                sqlCommand.Parameters.Add("@itemID", SqlDbType.BigInt).Value = itemID;
                sqlConnection.Open();

                using (SqlDataAdapter da = new SqlDataAdapter(sqlCommand))
                {
                    da.Fill(ds);
                }
            }
        }

        SqlCacheDependency sqlCacheDependency = new System.Web.Caching.SqlCacheDependency(WebConfigurationManager.AppSettings["DatabaseName"], "Items");
        HttpContext.Current.Cache.Insert(cacheKey, ds, sqlCacheDependency, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration);
    }

    return ds;
}

Note that I’ve employed most of the recommended best practices like caching, “using” keywords, stored procedures and connection string inside web.config.

Code Blocks Inside Master Pages

Some time ago I kept getting this error in one of the projects I was working on:

The Controls collection cannot be modified because the control contains code blocks (i.e. <% … %>).

I spent quite some time trying to figure it out. Finally after hours of searching I found out that Code Blocks Inside Master Pages Cause Trouble.

I was using code blocks inside the head tag of all the MasterPages to resolve paths at runtime:

<link rel="stylesheet" href="<%= ResolveUrl("~/myStyleSheet.css") %>" type="text/css" />
<script type="text/javascript" src="<%= ResolveUrl("~/myJavaScript.js") %>"></script>

Fortunately Milan Negovan proposed a quick workaround which saved a lot of time.

Basically, the workaround is to change the code blocks to data binding expressions (<%# … %>).

<link rel="stylesheet" href="<%# ResolveUrl("~/myStyleSheet.css") %>" type="text/css" />
<script type="text/javascript" src="<%# ResolveUrl("~/myJavaScript.js") %>"></script>

and adding this to the master page code behind:

protected override void OnLoad (EventArgs e)
{
  base.OnLoad (e);
  Page.Header.DataBind ();
}

Since HtmlHead ultimately derives from Control and everything that derives from Control has a DataBind() method, adding that one line above would force the header to resolve data binding expressions.

Thats it, that does the trick!