Developer Blog
Articles about Using Microsoft Developer Tools

Redirecting to WWW on ASP.NET 4.0 and IIS7

Monday, June 14, 2010 8:23 AM by jonwood

These days, most domains work both with and without the "www" prefix. However, this can actually hurt your ranking on search engines like Google.

The problem is that search engines like Google consider domain.com to be a different domain than www.domain.com. So as you try and build your search-engine ranking by getting more and more links to your domain, it waters down those links if some use the "www" prefix and others do not.

It is better to have every link use exactly the same form of your domain. To this end, it is common to redirect requests to domain.com to www.domain.com. If someone leaves off the prefix, the redirect will cause their browser to add it. And any links saved and published will use the form of the domain with the prefix.

I recently needed to implement this redirect for a couple of ASP.NET 4.0/IIS 7 sites but ran into a little difficulty. At first, it was recommended that I edit the .htaccess file, but that simply did not work. Apparently, it only works on older versions.

Next, I was told to download IIS 7 Remote Manager and make the changes there. Well, I assume that would work, but downloading, installing, and learning how to use a very large program just to perform a simple redirect seemed like overkill, to put it mildly.

If your site uses ASP.NET 4.0 and IIS 7, or later, there is a fairly painless way to redirect requests to the "www" version of your site by simply editing the web.config file.

Listing 1 shows the text that needs to be added somewhere within your <configuration> section.

<system.webServer>
  <rewrite>
    <rules>
    <clear />
    <rule name="WWW Rewrite" enabled="true">
      <match url="(.*)" />
        <conditions>
          <add input="{HTTP_HOST}" negate="true"
            pattern="^www\.([.a-zA-Z0-9]+)$" />
        </conditions>
        <action type="Redirect" url="http://www.{HTTP_HOST}/{R:0}"
          appendQueryString="true" redirectType="Permanent" />
      </rule>
    </rules>
  </rewrite> 
<system.webServer>

Listing 1: Web.config setting to redirect domain.com to www.domain.com

Note that you will most likely see squiggly lines under the <rewrite> tag with a message that the tag is invalid. I got this message but, in fact, it worked just fine.

I found some information on the web about why I got the error but just haven't yet been able to spend time with something that appears to work just fine.

I'm a developer and this is a little out of my area of expertise so some other folks may be able to shed more light on the details of this approach. Still, if anyone comes at it from the point of view that I had, this is a great solution to look into to perform an important SEO function without digging into the depths of IIS.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Validation Controls Lost Their Red Color

Friday, June 04, 2010 2:27 AM by jonwood

I was recently finishing up a new website (http://www.trailcalendar.com) and I noticed that all the validation and validation summary controls in my project no longer appeared in red.

Normally, these controls will display with "color:Red" as part of their style attribute unless you override the ForeColor property. Even if you have CSS styles in effect that change the text color, the inline attribute will override it and the control will appear red. But, all of a sudden, this was no longer being included in my output.

I could not figure this one out. I created a new test project from scratch and I got the same thing (no red color). Yet, when I looked at my older projects, all validation controls were including the color style attribute. What had changed?

It turns out that what had changed is that I recently upgraded to Visual Studio 2010 and ASP.NET 4.0. It appears ASP.NET 4.0 has changed the way that it renders some of the output. Most of these changes are great as they result in more concise HTML. (Verbose HTML has always been an issue with ASP.NET.)

Some of these changes include menus, which are now output as lists instead of tables, properties like border="0" are no longer included, and error text of validation controls is no longer set to red! Note that I think these are great changes and highly recommend the upgrade. In an effort to make the HTML more concise, VS 2010 also gives you the option of preventing your tag IDs growing from something like "LinkButton1" to "ContentPlaceHolder1_ContentPlaceHolder1_Repeater1_LinkButton1_0".

So, now that I know what is causing this, what is the solution? Well, it turns out there is a compatibility setting for this. The controlRenderingCompatibilityVersion can be set to "3.5" to produce the same rendering I'm familiar with. This setting goes in your web.config file and is demonstrated in Listing 1.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.0" />
    <pages controlRenderingCompatibilityVersion="3.5" />
  </system.web>
</configuration>

Listing 1: The controlRenderingCompatibilityVersion Setting.

It turns out that the Visual Studio Conversion Wizard sets this setting automatically when upgrading a project. This makes sense because you might have a big project that has many places that no longer display correctly. To be honest, I'm just not sure what happened in my case and don't recall if I ran the Conversion Wizard or not.

I'm still not entirely clear on what the expectation is here. I love the option of producing more compact HTML, but I still want my validation text to appear red and I would rather use the newer ASP.NET 4.0 rendering. On my project, I will probably go in and change the ForeColor property on every validation control in my project to "Red".

Either way, I thought I would post about this in case anyone else was bit by this issue. It certainly took me by surprise.

Currently rated 2.5 by 2 people

  • Currently 2.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Abbreviating URLs

Thursday, April 29, 2010 7:35 AM by jonwood

Recently, I had a case where an ASP.NET page displayed the user's URL in a side column. This worked fine except that I found some users had very long URLs, which didn't look right.

It occurred to me that I could simple truncate the visible URL while still keeping the underlying link the same. However, when I truncated the URL by trimming excess characters, I realized it could be done more intelligently.

For example, consider the URL http://www.domain.com/here/is/one/long/url/page.apsx. If I wanted to keep it within 40 characters, I could trim it to http://www.domain.com/here/is/one/long/u. The problem is that this abbreviation could be more informative. For example, is it a directory or a page? And, if it's a page, what kind? And what exactly does the "u" at the end stand for?

Wouldn't it be a little better if I instead abbreviated this URL to http://www.domain.com/.../url/page.apsx? We've lost a few characters due to the three dots that show information is missing. But we can still see the domain, and the page name and type.

The code is Listing 1 abbreviates a URL is this way. The UrlHelper class contains just a single, static method, LimitLength(). This method takes a URL string and a maximum length arguments, and attempts to abbreviate the URL so that it will fit within the specified number of characters as described above.

public class UrlHelper
{
  public static char[] Delimiters = { '/', '\\' };
  /// <summary>
  /// Attempts to intelligently short the length of a URL. No attempt is
  /// made to shorten less than 5 characters.
  /// </summary>
  /// <param name="url">The URL to be tested</param>
  /// <param name="maxLength">The maximum length of the result string</param>
  /// <returns></returns>
  public static string LimitLength(string url, int maxLength)
  {
    if (maxLength < 5)
      maxLength = 5;
    if (url.Length > maxLength)
    {
      // Remove protocol
      int i = url.IndexOfAny(new char[] { ':', '.' });
      if (i >= 0 && url[i] == ':')
        url = url.Remove(0, i + 1);
      // Remove leading delimiters
      i = 0;
      while (url.Length > 0 && (url[i] == Delimiters[0]
        || url[0] == Delimiters[1]))
        i++;
      if (i > 0)
        url = url.Remove(0, i);
      // Remove trailing delimiter
      if (url.Length > maxLength && (url.EndsWith("/") || url.EndsWith("\\")))
        url = url.Remove(url.Length - 1);
      // Remove path segments until url is short enough or no more segments:
      //
      // domain.com/abc/def/ghi/jkl.htm
      // domain.com/.../def/ghi/jkl.htm
      // domain.com/.../ghi/jkl.htm
      // domain.com/.../jkl.htm
      if (url.Length > maxLength)
      {
        i = url.IndexOfAny(Delimiters);
        if (i >= 0)
        {
          string first = url.Substring(0, i + 1);
          string last = url.Substring(i);
          bool trimmed = false;
          do
          {
            i = last.IndexOfAny(Delimiters, 1);
            if (i < 0 || i >= (last.Length - 1))
              break;
            last = last.Substring(i);
            trimmed = true;
          } while ((first.Length + 3 + last.Length) > maxLength);
          if (trimmed)
            url = String.Format("{0}...{1}", first, last);
        }
      }
    }
    return url;
  }
}

Listing 1: UrlHelper class.

If the specified maximum length is less than five, LimitLength() simply changes it to five as there is no point in attempting to shorten a URL to less than the length of the protocol (http://).

That's all there is to it. I hope some of you find this code helpful.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   C# .NET | ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Encoding Query Arguments

Thursday, April 29, 2010 7:06 AM by jonwood

When passing variables between pages in ASP.NET, you have a few techniques you can choose from. One of the simplest is to use query arguments (e.g. http://www.domain.com/page.aspx?arg1=val1&arg2=val2). In ASP.NET, query arguments are easy to implement and use.

If you spend time browsing sites like Amazon.com, you'll see these query arguments causing the URLs to grow quite long. Long URLs don't generally cause a problem; however, there are some potential problems with query arguments.

For one thing, they are completely visible to the user. If you need to pass sensitive variables, then this could cause problems. For another thing, users can easily modify these values. For example, let's say you have a page that displays the current user's information. If a user ID is passed as a query argument, the user could easily edit that ID, possibly causing information for another user to be displayed. The potential security concerns here are pretty obvious.

Still, query arguments can be so convenient I decided to throw together a class that allows me to use them without the potential issues described above. In order to prevent the arguments from being seen by the user, the arguments are encrypted into a single argument. And in order to prevent the user from tampering with the values, the encrypted value includes a checksum that can detect if the data has been tampered with or corrupted.

Listing 1 shows my EncryptedQueryString class. By inheriting from Dictionary<string, string>, my class is a dictionary class. You can add any number of key/value items to the dictionary and then call ToString() to produce an encrypted string that contains all the values and a simple checksum. The string returned can then be passed to a page as a single query argument.

To restore the values, you can call the constructor that accepts an encrypted string. This constructor extracts the data from the encrypted string and adds it to the dictionary. Note that if this constructor finds an invalid or missing checksum, nothing is added to the dictionary. This prevents the calling code from working with questionable data.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
public class EncryptedQueryString : Dictionary<string, string>
{
  // Change the following keys to ensure uniqueness
  // Must be 8 bytes
  protected byte[] _keyBytes =
    { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
  // Must be at least 8 characters
  protected string _keyString = "ABC12345";
  // Name for checksum value (unlikely to be used in user arguments)
  protected string _checksumKey = "__$$";
  /// <summary>
  /// Creates an empty dictionary
  /// </summary>
  public EncryptedQueryString()
  {
  }
  /// <summary>
  /// Creates a dictionary from the given, encrypted string
  /// </summary>
  /// <param name="encryptedData"></param>
  public EncryptedQueryString(string encryptedData)
  {
    // Descrypt string
    string data = Decrypt(encryptedData);
    // Parse out key/value pairs and add to dictionary
    string checksum = null;
    string[] args = data.Split('&');
    foreach (string arg in args)
    {
      int i = arg.IndexOf('=');
      if (i != -1)
      {
        string key = arg.Substring(0, i);
        string value = arg.Substring(i + 1);
        if (key == _checksumKey)
          checksum = value;
        else
          base.Add(HttpUtility.UrlDecode(key), HttpUtility.UrlDecode(value));
      }
    }
    // Clear contents if valid checksum not found
    if (checksum == null || checksum != ComputeChecksum())
      base.Clear();
  }
  /// <summary>
  /// Returns an encrypted string that contains the current dictionary
  /// </summary>
  /// <returns></returns>
  public override string ToString()
  {
    // Build query string from current contents
    StringBuilder content = new StringBuilder();
    foreach (string key in base.Keys)
    {
      if (content.Length > 0)
        content.Append('&');
      content.AppendFormat("{0}={1}",  HttpUtility.UrlEncode(key),
        HttpUtility.UrlEncode(base[key]));
    }
    // Add checksum
    if (content.Length > 0)
      content.Append('&');
    content.AppendFormat("{0}={1}", _checksumKey, ComputeChecksum());
    return Encrypt(content.ToString());
  }
  /// <summary>
  /// Returns a simple checksum for all keys and values in the collection
  /// </summary>
  /// <returns></returns>
  protected string ComputeChecksum()
  {
    int checksum = 0;
    foreach (KeyValuePair<string, string> pair in this)
    {
      checksum += pair.Key.Sum(c => c - '0');
      checksum += pair.Value.Sum(c => c - '0');
    }
    return checksum.ToString("X");
  }
  /// <summary>
  /// Encrypts the given text
  /// </summary>
  /// <param name="text">Text to be encrypted</param>
  /// <returns></returns>
  protected string Encrypt(string text)
  {
    try
    {
      byte[] keyData = Encoding.UTF8.GetBytes(_keyString.Substring(0, 8));
      DESCryptoServiceProvider des = new DESCryptoServiceProvider();
      byte[] textData = Encoding.UTF8.GetBytes(text);
      MemoryStream ms = new MemoryStream();
      CryptoStream cs = new CryptoStream(ms,
        des.CreateEncryptor(keyData, _keyBytes), CryptoStreamMode.Write);
      cs.Write(textData, 0, textData.Length);
      cs.FlushFinalBlock();
      return GetString(ms.ToArray());
    }
    catch (Exception)
    {
      return String.Empty;
    }
  }
  /// <summary>
  /// Decrypts the given encrypted text
  /// </summary>
  /// <param name="text">Text to be decrypted</param>
  /// <returns></returns>
  protected string Decrypt(string text)
  {
    try
    {
      byte[] keyData = Encoding.UTF8.GetBytes(_keyString.Substring(0, 8));
      DESCryptoServiceProvider des = new DESCryptoServiceProvider();
      byte[] textData = GetBytes(text);
      MemoryStream ms = new MemoryStream();
      CryptoStream cs = new CryptoStream(ms,
        des.CreateDecryptor(keyData, _keyBytes), CryptoStreamMode.Write);
      cs.Write(textData, 0, textData.Length);
      cs.FlushFinalBlock();
      return Encoding.UTF8.GetString(ms.ToArray());
    }
    catch (Exception)
    {
      return String.Empty;
    }
  }
  /// <summary>
  /// Converts a byte array to a string of hex characters
  /// </summary>
  /// <param name="data"></param>
  /// <returns></returns>
  protected string GetString(byte[] data)
  {
    StringBuilder results = new StringBuilder();
    foreach (byte b in data)
      results.Append(b.ToString("X2"));
    return results.ToString();
  }
  /// <summary>
  /// Converts a string of hex characters to a byte array
  /// </summary>
  /// <param name="data"></param>
  /// <returns></returns>
  protected byte[] GetBytes(string data)
  {
    // GetString() encodes the hex-numbers with two digits
    byte[] results = new byte[data.Length / 2];
    for (int i = 0; i < data.Length; i += 2)
      results[i / 2] = Convert.ToByte(data.Substring(i, 2), 16);
    return results;
  }
}

Listing 1: EncryptedQueryString class.

So, for example, a page that sends encrypted arguments to another page could contain code something like what is shown in Listing 2. This code constructs an empty EncryptedQueryString object, adds a couple of values to the dictionary, and then passes the resulting string as a single query argument to page.aspx.

protected void Button1_Click(object sender, EventArgs e)
{
  EncryptedQueryString args = new EncryptedQueryString();
  args["arg1"] = "val1";
  args["arg2"] = "val2";
  Response.Redirect(String.Format("page.aspx?args={0}", args.ToString()));
}

Listing 2: Code that passes encrypted query arguments.

Finally, Listing 3 shows code that could go in page.aspx to extract the encrypted values from the single argument.

protected void Page_Load(object sender, EventArgs e)
{
  EncryptedQueryString args =
    new EncryptedQueryString(Request.QueryString["args"]);
  Label1.Text = string.Format("arg1={0}, arg2={1}", args["arg1"], args["arg2"]);
}

Listing 3: Code to extract encrypted query arguments.

And that's all there is to it. Be sure to add error checking in case the dictionary objects are not there (either because they were not provided, or because an invalid checksum caused the EncryptedQueryString class to clear all items from the dictionary).

Also, be sure to customize the two keys near the top of Listing 1 so that people who read this article won't be able to decrypt your values.

Query arguments aren't always the best choice. As mentioned, you may choose to use Session variables or other techniques, depending on your requirements. But query arguments are straight forward and easy to implement. Using the class I've presented here, they can also be reasonably secure.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   C# .NET | ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Quick-and-Dirty, Buy-Now Buttons in ASP.NET

Sunday, February 14, 2010 6:34 AM by jonwood

I posted previously about an issue when incorporating PayPal Buy-Now buttons on an ASP.NET web form. Basically, after presenting a few hacks, I pointed out that you could simply place the form items directly within your ASP.NET form. (See that post for more info.)

However, for quick and dirty Buy Now buttons, there is a far simpler approach. You can simply use an anchor link and provide parameters as query arguments. Listing 1 demonstrates this technique.

<a href="https://www.paypal.com/cgi-bin/webscr
  ?cmd=_xclick&business=MyEmail
  &item_name=Widget
  &amount=29.00
  &undefined_quantity=1
  &currency_code=USD">
<img src="http://www.paypal.com/en_US/i/btn/x-click-but23.gif"
  border="0" alt="Buy Now Using PayPal" />
</a>

Listing 1: Simple Implementation of PayPal Buy Now Button

Note that the href value of the a tag should all go on a single line. I wrapped the text here only so it would fit within the page. MyEmail should be replaced with the email address associated with your PayPal account.

As you can see, we provided several bits of information. After our account email, we provide an item name, the price (amount), and I included the optional currency code.

The undefined_quantity parameter allows the user to enter the quantity, and PayPal will calculate the total based on the price you specified and the quantity entered by the user. Alternatively, you can instead say quantity=5 to fix the quantity so that the user cannot edit it.

Although that should be all you need for a simple Buy-Now button, Table 1 lists some additional arguments you can include.

Argument Description
business Email address associated with seller’s PayPal account
quantity Quantity of items being sold
undefined_quantity Allows user to edit quantity
item_name Name of item
item_number Optional item number
amount Price of each item (without currency symbol)
undefined_amount Allows user to edit the amount (good for donations)
shipping Price of shipping
currency_code Code for type of currency (Default appears to be USD)
first_name Customer’s first name
last_name Customer’s last name
address1 Customer’s first address line
address2 Customer’s second address line
city Customer’s city
state Customer’s state
zip Customer’s zip code
email Customer’s email address
night_phone_a Customers telephone area code
night_phone_b Customers telephone prefix
night_phone_c Remainder of customer’s telephone number

Table 1: Additional Query Arguments

The arguments listed in Table 1 are not exhaustive. Other arguments are available as well. For the simple task I’m describing, this list should be more than enough.

Of course, you also have the option of programmatically forming this link and then using code to redirect to it. This allows you, for example, to set the quantity based on a value entered by the user on your own site.

Note that there are some potential downsides to this technique. For starters, the link is fully visible for anyone to see. Of course, it won’t include your PayPal password so that type of information is not exposed. But your account email is visible.

Users can also save your web page to their computer, and then edit the link. So, for example, they could change the price, load the edited page, and click the link. So you need to verify the correct amount was paid when processing orders.

Nonetheless, for a simply Buy-Now button, this technique works great and couldn’t be simpler to implement.

Currently rated 3.0 by 3 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Create an RSS Feed in ASP.NET

Monday, January 18, 2010 4:52 PM by jonwood

Recently, it occurred to me that one of my websites would probably benefit from an RSS feed. However, I really didn’t understand what RSS feeds were. I understood the basic purpose but really had no clue as to how they worked. With words like “syndication” being tossed around when describing RSS feeds, I had imagined it involved some sort code that continually sent data to some mystical location.

Fortunately, understanding RSS feeds is very easy, and creating your own RSS feed in ASP.NET is a breeze. RSS stands for Really Simple Syndication. It provides a standard for you to make information available to anyone who wants to request your feed. One of my sites is a shareware site and I thought a feed would allow users to stay in contact with my site and make it more likely that they would return. Moreover, an RSS feed allows them to do this without signing up or even giving me their email address.

These days, it’s getting easier for users to use feeds because more and more software is starting to support them. For example, when you enter the URL of a feed into Microsoft Internet Explorer, the information is now formatted specifically for feeds. Microsoft Live Mail also has direct support for feeds. There are also a number of websites that can help you to subscribe to and view RSS feeds.

An RSS feed is simply an XML file on your site that conforms to the RSS specification. Of course, since feeds are meant to be constantly updated, you would normally want to generate this file on-the-fly when it is requested. And, of course, ASP.NET makes this very easy to do.

Listing 1 shows my feed file. This is a normal, every day ASPX file and what you see makes up the entire contents of the file. The first thing to notice is the OutputCache declaration on the second line. When you use OutputCache, requests for this file within the given duration will simply return a copy of the previous results. The duration is in seconds, so if two requests for this file occur within two minutes, the code will not run again for the second request. Instead, ASP.NET will simply return the same data that was returned for the first request. Since the page runs potentially lengthy code and makes a potentially substantial hit on the database, this ensures the site doesn’t get bogged down under heavy traffic.

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ OutputCache Duration="120" VaryByParam="none" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="SoftCircuits" %>
<script runat="server">
  
  /// <summary>
  /// Create RSS Feed of newest submissions
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  protected void Page_Load(object sender, EventArgs e)
  {
    // Clear any previous response
    Response.Clear();
    Response.ContentType = "text/xml";
    //
    XmlTextWriter writer = new XmlTextWriter(Response.OutputStream,
      Encoding.UTF8);
    writer.WriteStartDocument();
    // The mandatory rss tag
    writer.WriteStartElement("rss");
    writer.WriteAttributeString("version", "2.0");
    // The channel tag contains RSS feed details
    writer.WriteStartElement("channel");
    writer.WriteElementString("title", "File Parade's Newest Submissions");
    writer.WriteElementString("link", "http://www.fileparade.com");
    writer.WriteElementString("description",
      "The latest freeware and shareware downloads from File Parade.");
    writer.WriteElementString("copyright",
      String.Format("Copyright {0} SC Web Group. All rights reserved.", DateTime.Today.Year));
    // File Parade image    
    writer.WriteStartElement("image");
    writer.WriteElementString("url",
      "http://www.fileparade.com/Images/logo88x31.png");
    writer.WriteElementString("title",
      "File Parade Freeware and Trialware Downloads");
    writer.WriteElementString("link",
      "http://www.fileparade.com");
    writer.WriteEndElement();
    // Objects needed for connecting to the SQL database
    using (SqlDataReader reader = DataHelper.ExecProcDataReader("GetRssFeed"))
    {
      // Loop through each item and add them to the RSS feed
      while (reader.Read())
      {
        writer.WriteStartElement("item");
        writer.WriteElementString("title",
          EncodeString(String.Format("{0} {1} by {2}",
          reader["Title"], reader["Version"],
          reader["Company"])));
        writer.WriteElementString("description",
          EncodeString((string)reader["Description"]));
        writer.WriteElementString("link",
          String.Format("http://www.fileparade.com/Listing.aspx?id={0}",
          reader["ID"]));
        writer.WriteElementString("pubDate",
          ((DateTime)reader["ReleaseDate"]).ToShortDateString());
        writer.WriteEndElement();
      }
    }
    // Close all tags
    writer.WriteEndElement();
    writer.WriteEndElement();
    writer.WriteEndDocument();
    writer.Flush();
    writer.Close();
    // Terminate response
    Response.End();
  }
  protected string EncodeString(string s)
  {
    s = HttpUtility.HtmlEncode(s);
    return s.Replace("\r\n", "<br />\r\n");
  }
</script>

Listing 1: RSS Feed

Next are my declarations to import the needed namespaces. Nothing special here—just the declarations needed for database access. Note that this code won’t run for you as listed. It includes my SoftCircuits namespace, which contains some in-house routines for the database. You’ll need to replace this with your own database code. This makes sense since you’ll be returning your own data.

The core of the code is placed in the Page_Load event handler. As you know, this code is called when the page is first requested. The first step is to clear the response of any previously output content. Remember, we are creating an XML file and we don’t want any other content to be returned. Next, we set some headers so that the user agent can see what type of content we are returning.

From here, we go ahead and create an XmlTextWriter and attach it to our output stream, and we can start creating our output. We start with some mandatory RSS tags—these are need to identify our content as an RSS file. Next, we add some mandatory tags that describe our channel. This provides additional, descriptive information about our content. Next, I add some optional tags, which specify a small image and related data.

After that, we can finally start to output our actual data. My code uses an in-house method called DataHelper.ExecProcReader, which calls a stored procedure to obtain my data. You will need to replace this with your own code to return whatever data you are syndicating. My routine simply returns a SqlDataReader and I loop through each row in the data it returned.

Note that I perform some modifications to my text fields before writing them. In my case, this text is submitted from various authors and I don’t want them to include their own HTML markup. So I call HtmlEncode, which causes markup to appear as it was written instead of allowing it to modify the layout, formatting, or creating links. I then insert my own markup by placing <br /> wherever there is a newline. This ensures newlines will appear for the user. I should point out that WriteElementString() will HTML-encode the string being written. This prevents markup from disturbing the XML markup. Note that data will be HTML-decoded when it is read. So you only need to mess with this if you want to tweak the data you are returning.

We then flush the XML writer for good measure, and terminate our response. Again, we are creating an XML file and this last step prevents any other output from accidently being included in the response.

If you’re like me, you may be a little surprised how easy this really is. To allow someone to check your feed, you simply provide them with the URL to this page. Using software that supports feeds, they can have instant access to your data in a convenient format. And, of course, are more likely to return to your site when they need more information.

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Confirm Before Deleting Grid Item

Tuesday, May 12, 2009 4:17 AM by jonwood

When deleting an item in one of the ASP.NET grids, it would be nice to ask the user to confirm this is what they really meant to do. After all, it is very easy to mouse click somewhere by accident. And what would be even nicer is if this confirmation takes place on the client (browser) instead of requiring yet another round trip to the server.

Fortunately, this task is very easy to do. As you might expect, the answer is to use javascript. In some cases, setting up javascript on an ASP.NET page can get a little involved. However, a simple script can be added using the OnClientClick property, which is available with many ASP.NET controls.

Listing 1 shows part of the ASP.NET code for a GridView control. This code includes an ItemTemplate that defines a delete button and includes some confirmation javascript in the OnClientClick property.

<asp:GridView ID="GridView1" runat="server" />
  <Columns>
    <asp:TemplateField>
      <ItemTemplate>
        <asp:LinkButton ID="lnkDelete" runat="server"
          CausesValidation="false" 
          CommandName="DeleteItem"
          Text="Delete" CommandArgument='<%# Bind("ItemID") %>'
          OnClientClick="return confirm('Delete this item?');">
        </asp:LinkButton>
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>

Listing 1: Javascript to confirm deleting a GridView item.

This simple javascript calls confirm(), which returns true if the user selects Yes. The code associated with posting back the form and deleting the item only executes if this script returns true.

So, that’s a very simple technique that is easy to implement and works very well. And because it uses javascript, it doesn’t perform the postback to the server unless the user confirms they really do want to delete the grid item.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Validating Credit Card Numbers

Tuesday, May 12, 2009 2:17 AM by jonwood

When using ASP.NET to process online credit card orders, it is a good idea if you can perform some sort of validation on the credit card number before submitting it to your processor. I recently had to write some code to process credit card orders and thought I’d share a bit of my code.

Fortunately, credit card numbers are created in a way that allows for some basic verification. This verification does not tell you if funds are available on the account and it certainly doesn’t tell whether or not the person submitting the order is committing credit card fraud. In fact, It’s possible that the card number is mistyped in such a way that it just happens to pass verification. But it does catch most typing errors and reduces bandwidth by catching those errors before trying to actually process the credit card.

To validate a credit card number, you start by adding the value of every other digit, starting from the right-most digit and working left. Next, you do the same thing with the digits skipped in the first step, but this time you double the value of each digit and add the value of each digit in the result. Finally, you add both totals together and if the result is evenly divisible by 10, then the card has passed validation.

Of course, this would be clearer with a bit of code and Listing 1 shows my IsCardNumberValid method.

public static bool IsCardNumberValid(string cardNumber)
{
  int i, checkSum = 0;
  // Compute checksum of every other digit starting from right-most digit
  for (i = cardNumber.Length - 1; i >= 0; i -= 2)
    checkSum += (cardNumber[i] - '0');
  // Now take digits not included in first checksum, multiple by two,
  // and compute checksum of resulting digits
  for (i = cardNumber.Length - 2; i >= 0; i -= 2)
  {
    int val = ((cardNumber[i] - '0') * 2);
    while (val > 0)
    {
      checkSum += (val % 10);
      val /= 10;
    }
  }
  // Number is valid if sum of both checksums MOD 10 equals 0
  return ((checkSum % 10) == 0);
}

Listing 1: Validating a credit card.

The IsCardNumberValid method assumes that all spaces and other non-digit characters have been stripped from the card number string. This is a straight forward task but Listing 2 shows the method I use for this.

public static string NormalizeCardNumber(string cardNumber)
{
  if (cardNumber == null)
    cardNumber = String.Empty;
  StringBuilder sb = new StringBuilder();
  foreach (char c in cardNumber)
  {
    if (Char.IsDigit(c))
      sb.Append(c);
  }
  return sb.ToString();
}

Listing 2: Removing all non-digit characters from a credit card number.

You will also be able to reduce bandwidth if you can avoid trying to submit a card that is not supported by the business. So another task that can be useful is determining the credit card type.

public enum CardType
{
  Unknown = 0,
  MasterCard = 1,
  VISA = 2,
  Amex = 3,
  Discover = 4,
  DinersClub = 5,
  JCB = 6,
  enRoute = 7
}
// Class to hold credit card type information
private class CardTypeInfo
{
  public CardTypeInfo(string regEx, int length, CardType type)
  {
    RegEx = regEx;
    Length = length;
    Type = type;
  }
  public string RegEx { get; set; }
  public int Length { get; set; }
  public CardType Type { get; set; }
}
// Array of CardTypeInfo objects. Used by GetCardType() to identify credit card types.
private static CardTypeInfo[] _cardTypeInfo =
{
  new CardTypeInfo("^(51|52|53|54|55)", 16, CardType.MasterCard),
  new CardTypeInfo("^(4)", 16, CardType.VISA),
  new CardTypeInfo("^(4)", 13, CardType.VISA),
  new CardTypeInfo("^(34|37)", 15, CardType.Amex),
  new CardTypeInfo("^(6011)", 16, CardType.Discover),
  new CardTypeInfo("^(300|301|302|303|304|305|36|38)", 14, CardType.DinersClub),
  new CardTypeInfo("^(3)", 16, CardType.JCB),
  new CardTypeInfo("^(2131|1800)", 15, CardType.JCB),
  new CardTypeInfo("^(2014|2149)", 15, CardType.enRoute),
};
public static CardType GetCardType(string cardNumber)
{
  foreach (CardTypeInfo info in _cardTypeInfo)
  {
    if (cardNumber.Length == info.Length && Regex.IsMatch(cardNumber, info.RegEx))
      return info.Type;
  }
  return CardType.Unknown;
}

Listing 3: Determining a credit card’s type.

Listing 3 is my code to determine a credit card’s type. I’m a big fan of table-driven code, when it makes sense, and so I created an array of CardTypeInfo objects. The GetCardType() method simply loops through this array, looking for the first description that would match the credit card number being tested. As before, this routine assumes all non-digit characters have been removed from the credit card number string.

The main reason I like table-driven code is because it makes the code simpler. This results in code that is easier to read and modify. GetCardType() returns a value from the CardType enum. CardType.Unknown is returned if the card number doesn’t match any card descriptions in the table.

Writing code to process credit cards involves a number of issues that need to be addressed. Hopefully, this code will give you a leg up on addressing a couple of them.

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   C# .NET | ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Good Bye IE6—And Good Riddance!

Monday, January 26, 2009 10:18 AM by jonwood

Well, that’s it as far as I’m concerned. I have a laptop that runs Windows XP and, until today, Internet Explorer version 6. However, Windows Update is now automatically updating XP systems to Internet Explorer version 7, which I’ve just installed.

I can only assume Windows Update is doing this now due to recent security holes found in IE6. After being infected with a major virus recently, upgrading to IE7 seemed like a good idea. But as an ASP.NET developer, there are far larger considerations involved.

Internet Explorer 6 is notorious for not following the established Web standards that pretty much every other browser is following. In my view, this has made Website development, particularly layout and CSS, an absolutely nightmare! There have been many times I’ve spent hours getting layout exactly as I want it only to find it does not appear as expected in IE6. In fact, many books I have on CSS design have entire chapters devoted to IE6 and its unique set of layout quirks.

As with most of their products, Microsoft wanted to make their browser exceptional by doing their own thing. But this just doesn’t work in the browser world. There have to be standards in order for the largest number of browsers to work with the largest number of Websites. That’s not to say all other browsers are identical in their rendering of Web content—they’re not. But IE6 was the odd one out with a large majority of issues.

So, until now, I’ve continued to maintain a computer running IE6 in order to be able to test my sites with this browser. But now that Windows Update is urging users to upgrade, I’m going to give up on this browser. Of course, not everyone will upgrade. There are always users who will resist changes like this for as long as possible. But I expect this development to mean that the number of IE6 users will drop significantly. And I’m willing to take advantage of this and no longer work to ensure my sites appear correctly for the diminishing number of users who use this browser.

With IE6 out of the picture, the biggest offender of not following Web standards is now IE7. But IE7 is quite a bit better than IE6. And, perhaps more significant, IE8 is about to be released. IE8 presented Microsoft with some difficult decisions. On the one hand, Microsoft realized that the latest Web standards could no longer be ignored. But on the other hand, if they came out with a fully compliant browser, they would break compatibility with previous versions, causing some pages that looked fine with IE6 to no longer appear correctly.

There was considerable discussions about whether IE8 should require a flag in the HTML code to tell it to use the new compliant mode, otherwise, it would default to being compatible with prior versions. This would allow both new and old sites to appear correctly.

I fought strongly against this approach. This opens up a whole can of worms. And, from a Web developer’s viewpoint, it’s critical to get everyone in line using the current standards and end the chaos we have now, which was caused in large part by the noncompliance of IE6. Although I have not played with IE8 or heard the latest on this issue, I did hear at one point that the decision was to make IE8 standards-compliant by default.

If this becomes the final decision, I applaud Microsoft for making this hard choice. They will take a hit if they come out with a browser that, by default, produces different rendering of some Web pages than previous versions of IE. But I think this is in the best interest the industry. And, in my book, not having to worry about IE6 anymore is a good thing.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:  
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Implementing Non-ASP.NET Posts in ASP.NET

Thursday, January 22, 2009 6:14 AM by jonwood

When interfacing with some resource on the Web, you are sometimes provided with a little snippet of HTML code. For example, to insert a PayPal button on your Website, PayPal provides HTML code that may look something like this.

<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
  <input type="hidden" name="cmd" value="_xclick">
  <input type="hidden" name="business" value="bob@domain.com">
  <input type="hidden" name="lc" value="US">
  <input type="hidden" name="item_name" value="Widget">
  <input type="hidden" name="amount" value="100.00">
  <input type="hidden" name="currency_code" value="USD">
  <input type="hidden" name="bn"
    value="PP-BuyNowBF:btn_buynow_LG.gif:NonHostedGuest">
  <input type="image"
    src="https://www.paypal.com/en_US/i/btn/btn_buynow_LG.gif"
    border="0" name="submit" alt="">
  <img alt="" border="0"
    src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1"
    height="1">
</form>

HTML code to insert PayPal button

This snippet defines a <form> and several <input> tags, including a submit button. When the submit button is clicked, the items in the form are posted to the URL specified in the action attribute of the <form> tag. The target URL can then inspect the values of those <input> tags and perform the required operation.

However, you may find that such snippets don’t work correctly when inserted into an ASP.NET page. The main problem is that ASP.NET pages already define a form. In order for ASP.NET’s post-back mechanism to work, all the controls are placed within a <form> tag. Since HTML doesn’t allow nested <form> tags, inserting code like that shown above into an ASP.NET page causes problems.

There are a number of possible approaches to resolving this. Let me start with a couple that I think are less than ideal. The first approach is to simply move the inserted HTML code after the ending </form> tag of the ASP.NET form. Although you cannot nest <form> tags, it is okay to include multiple, non-nested <form> tags on the same page.

The result of this is that the page now has two separate <form> tags and you’ll find this generally works as expected. However, the problem here is that you lose some ASP.NET functionality. For example, the inserted HTML code comes after your ASP.NET form and so it is not really embedded within your ASP.NET content. And if you are using master pages, you cannot do this in content pages because everything in a content page is placed inside of the <form> tag defined in the master page.

The second approach is kind of an ugly hack and is shown below.

protected void btnPayPal_Click(object sender, ImageClickEventArgs e)
{
  System.Web.HttpContext.Current.Response.Clear();
  System.Web.HttpContext.Current.Response.Write("<html><head>");
  System.Web.HttpContext.Current.Response.Write(
    "</head><body onload='document.form1.submit()'>");
  System.Web.HttpContext.Current.Response.Write(
    "<form action='https://www.paypal.com/cgi-bin/webscr' " +
    "name='form1' method='post'>");
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='cmd' value='_xclick'>");
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='business' " +
    "value='bob@domain.com'>");
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='lc' value='US'>");
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='item_name' value='Widget'>");
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='amount' value='100.00'>");
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='currency_code' value='USD'>
  System.Web.HttpContext.Current.Response.Write(
    "<input type='hidden' name='bn' " +
    "value='PP-BuyNowBF:btn_buynow_LG.gif:NonHostedGuest'>
  System.Web.HttpContext.Current.Response.Write(
    "<input type='image' " +
    "src='https://www.paypal.com/en_US/i/btn/btn_buynow_LG.gif' " +
    "border='0' name='submit' alt=''>");
  System.Web.HttpContext.Current.Response.Write(
    "<img alt='' border='0' " +
    "src='https://www.paypal.com/en_US/i/scr/pixel.gif' " +
    "width='1' height='1'>");
  System.Web.HttpContext.Current.Response.Write("</form>");
  System.Web.HttpContext.Current.Response.Write("</body></html>");
  System.Web.HttpContext.Current.Response.End();
}

Ugly hack to perform non-ASP.NET postback

This code runs in response to the user clicking a button named btnPayPal. It dynamically creates an HTML page and then serves it to the user’s browser. The page created duplicates the form in our first listing but adds an onload attribute to the page’s <body> tag that causes the form to be submitted as soon as it loads and the user never sees this temporary page.

This is definitely an ugly hack. And, while it works, it has some problems. The main problem is if, after running the code, the user presses the Back button in their browser, the browser will reload this temporary page, which will cause the same action to happen again, which definitely has the potential to cause some nasty problems.

After exploring both of these options, I finally settled on a third approach that seems more straight forward and doesn’t seem to have any major problems. Beginning with ASP.NET 2.0, buttons have a property called PostBackUrl, which can be used to have that button post back to a page other than the page that contains the button.

At first glance, this didn’t seem helpful. ASP.NET buttons are designed to post back to ASP.NET pages. And I wanted to post back to an non-ASP.NET page. However, the post-back mechanism is the same in both cases. If we post back to a non-ASP.NET page, we’re going to send a lot of additional data—all of the form items on our ASP.NET form—to the target page. But, as long as their is no conflicting item names, which is unlikely, this really isn’t a problem.

Armed with this knowledge then, we can rewrite our original code block and insert it into an ASP.NET page.

<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="business" value="bob@domain.com">
<input type="hidden" name="lc" value="US">
<input type="hidden" name="item_name" value="Widget">
<input type="hidden" name="amount" value="100.00">
<input type="hidden" name="currency_code" value="USD">
<input type="hidden" name="bn"
  value="PP-BuyNowBF:btn_buynow_LG.gif:NonHostedGuest">
<asp:ImageButton ID="ImageButton1" runat="server"
  ImageUrl="https://www.paypal.com/en_US/i/btn/btn_buynow_LG.gif"
  PostBackUrl="https://www.paypal.com/cgi-bin/webscr" />

PayPal button inserted into ASP.NET page

I’ve removed the <form> tag from this code. We don’t need it since our ASP.NET form already defines a <form> tag. I’ve removed the <img> and submit <input> tags and I’ve replaced them with an ImageButton. I set the ImageButton’s ImageUrl property to the same PayPal button image used in the original code and, finally, I set the PostBackUrl property to the URL specified in the action attribute of the <form> tag in our original HTML code.

The result is that, when the ImageButton is clicked, the entire form is posted to the PayPal URL. As I mentioned before, this includes all our other ASP.NET values, including the page’s ViewState data. But the ViewState data is encrypted and the PayPal site will only look at those values it is interested in.

The result is that I’ve inserted a PayPal button at any location within my ASP.NET form, I’m still able to make use of all ASP.NET features, PayPal gets all the values it needs to do its job, and there is no funny business if the user happens to hit the browser’s Back button from the PayPal site.

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:   ,
Categories:   ASP.NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed