Code Focused

Safety in Numbers: Encoding URL Data in ASP.NET Web Page Projects

Let's go old school and use base-64 to (somewhat) safely package and pass around text and binary data.

The move from desktop to mobile has brought with it an urgent need for data-passing mechanisms. The most popular apps communicate with that magical cloud thing, and developers have come up with some of the most intriguing ways to shuttle data between cloud-based servers and client devices. But sometimes, you just want simplicity, and in the world of the Internet, one of the easiest ways of passing data is to include it right in the destination URL:

/* ----- Passing data via query strings */
http://www.example.com/order?id=12345

/* ----- Passing data via semantic URLs */
http://www.example.com/order/12345

You can transmit all kinds of fun data, as long as it doesn’t get too long or too wild:

/* ----- This will fail: Too many dangerous characters */
http://www.example.com/calculate?formula=(-b+sqrt(b^2-4ac))/2a

Passing raw user data within the URL, even after it’s been cleaned up a bit, can still cause syntax problems. But there’s a workaround that’s not only useful, but fun! In this article, I’ll use a data format known as "base-64" to package text and binary data for convenient and safe transport within a URL path.

Base-64 is a decades-old format that’s still used regularly in e-mail systems and XML data management, among other things. It works by taking a source block of text or binary content, cutting it up into six-bit chunks, and assigning a standard printable character to each of the 64 unique values represented by those six bits. In its traditional form, the 64 target characters are sourced from the uppercase and lowercase English letters, all 10 digits, the plus sign (+), the equals sign (=) and the slash (/). Because the slash character is already meaningful in URL paths, we will need to make a few adjustments in our implementation.

The code in this article collects address data from a user through a Web form, uses JavaScript to encode the address in a base-64 format within the client browser, sends that content to a server-side C# routine that knows how to decode it, and finally returns the encoded and decoded versions back to another Web page to prove that it all worked. ASP.NET Web Pages with Razor syntax provides a compact way to include client and server code in a single project, so I’ll use that platform.

Start Visual Studio 2017, and use the File | New Web Site menu command to create a new Web application. Use the C# ASP.NET Empty Web Site template, and put it in the folder of your choice, naming it "EncodedUrl." When the project is finished loading, access the Tools | NuGet Package Manager | Manage NuGet Packages for Solution menu command, and use the Browse feature on the form that appears to locate and install the latest version of the Microsoft.AspNet.Razor package.

Now you’re ready to code. Add the initial client page by selecting the Website | Add New Item command, choosing the Content Page (Razor 3) template on the Add New Item form, and giving it the name Default.cshtml. Replace any code that appears with the content from Listing 1.

Listing 1: Default Client Page
<!DOCTYPE html>
<html>
<head>
  <title>URL Data Encoder</title>
  <link rel="home" id="ApplicationRoot"
    href="@(Request.Url.GetLeftPart(UriPartial.Authority))/" />
  <script>
  </script>
</head>
<body>
  <h1>Encode a New Address!</h1>
  <table>
    <tr><td>Address:</td>
      <td><input type="text" id="AddressText" /></td></tr>
    <tr><td>City:</td>
      <td><input type="text" id="CityText" /></td></tr>
    <tr><td>State:</td>
      <td><input type="text" id="StateText" /></td></tr>
    <tr><td>Zip Code:</td>
      <td><input type="text" id="ZipCodeText" /></td></tr>
    <tr><td colspan="2">
      <button onclick="encodeAddress();">Encode</button></td></tr>
  </table>
</body>
</html>

This page displays the basic user input fields, collecting four pieces of textual address data. What it lacks is the code that encodes that data. In between the <script> tags near the top, add the encodeAddress JavaScript function, which appears in Listing 2.

Listing 2: JavaScript To Encode Data and Submit to Server
function encodeAddress() {
  // ----- Store the address parts as tab-delimited content.
  var rawContent =
    document.getElementById("AddressText").value
    + '\t' + document.getElementById("CityText").value
    + '\t' + document.getElementById("StateText").value
    + '\t' + document.getElementById("ZipCodeText").value;

  // ----- Convert to base-64, with some adjustments:
  //       "+" to "-", "/" to "_", "=" to "$"
  var encodedContent = window.btoa(rawContent)
    .replace(/\+/g, "-").replace(/\x2f/g, "_").replace(/=/g, "$");

  // ----- Send it to the server via a URL string.
  window.location = document.getElementById("ApplicationRoot").href +
    "Result/" + encodedContent;
}

The good news for us is that JavaScript (at least in the context of a Web browser) already has a base-64 conversion tool built right in, via the window.btoa method. Pass it normal content, and out pops encoded content. As mentioned earlier, that output includes a few special-use characters, so the JavaScript replaces those with characters that mean nothing to URL parsers. Once the data is converted, it gets tacked on to the end of a URL path for the Result.cshtml page, which we’ll design soon. Here’s what that URL will look like when run from the Visual Studio test server:

http://localhost:12345/Result/abcdefg

The "abcdefg" part gets replaced with the actual encoded content, and contains only letters, digits and a few benign characters. The embedded tabs and any other special characters entered by the user are hidden away inside the embedded form.

Next, let’s develop a C# method that accepts the encoded string, and extracts the original data elements. Use the Website | Add New Item command once again, and this time add a plain C# class, naming it Processing.cs. You might receive a message asking if the file should be put in the App_Code folder, to which you should respond Yes. Then replace the file’s default class definition with a class named AddressParts, enclosed within the EncodedUrl namespace:

namespace EncodedUrl
{
  public class AddressParts
  {
    public string Street;
    public string City;
    public string State;
    public string ZipCode;
  }
}

The AddressParts class will hold the extracted elements sent by the initial client Web page. Next, add the DecodeAddress method that performs the actual reverse-encoding. This method appears within the new Processing static class, placed within the EncodedUrl namespace. Insert it just below the AddressParts class:

public static class Processing
{
  public static AddressParts DecodeAddress(string encodedContent)
  {
  }
}

The caller passes in the encoded content that arrived at the end of the URL string. The first task to do within the DecodeAddress method is to undo those special character adjustments that were made to the traditional base-64 content:

// ----- Undo the special character adjustments.
string decodedContent = (encodedContent ?? "")
  .Replace("$", "=").Replace("_", "/").Replace("-", "+");

Now you have true base-64 content. To convert it back to plain text, you can use the Convert.FromBase64String method, which implements the reverse logic of the JavaScript window.btoa method:

// ----- Convert from base-64 back to plain text.
decodedContent = System.Text.Encoding.UTF8.GetString(
  Convert.FromBase64String(decodedContent ?? ""));

The output from that conversion should be the original address data, with embedded tab characters. Separate the parts into a string array, after first checking for missing data:

// ----- Extract the original tab-delimited parts.
if (string.IsNullOrEmpty(decodedContent))
  return null;
else if (decodedContent.Replace("\t", "").Trim().Length == 0)
  return null;
string[] parts = decodedContent.Split('\t');

Finally, take the individual address pieces and wrap them up in an AddressParts instance:

// ----- Convert to a structured format and return.
AddressParts result = new AddressParts();
result.Street = parts.Length > 0 ? parts[0] : "";
result.City = parts.Length > 1 ? parts[1] : "";
result.State = parts.Length > 2 ? parts[2] : "";
result.ZipCode = parts.Length > 3 ? parts[3] : "";
return result;

The Processing class is complete. The only thing left to write is the output Web page that displays the encoded content and the decoded version built in the DecodeAddress method. Add another Content Page (Razor 3) item to the project through the File | Add New Item command, this time calling it Result.cshtml. Replace any code that appears with the content from Listing 3.

Listing 3: Razor Page that Displays the Output
@using EncodedUrl;

@{
  // ----- Extract content from the first URL component, if it exists.
  string encodedContent = UrlData[0] ?? "";
  AddressParts decodedParts = Processing.DecodeAddress(encodedContent);
  if ((encodedContent.Length == 0) | (decodedParts == null))
  {
    Response.Redirect("~/Default");
    Response.End();
  }
}

<!DOCTYPE html>
<html>
<head>
  <title>URL Data Encoder - Result</title>
</head>
<body>
  <h1>Here's Your Data!</h1>
  <h2>Encoded Content</h2>
  <ul>
    <li>@encodedContent</li>
  </ul>
  <h2>Decoded Content</h2>
  <ul>
    <li><strong>Address:</strong> @decodedParts.Street</li>
    <li><strong>City:</strong> @decodedParts.City</li>
    <li><strong>State:</strong> @decodedParts.State</li>
    <li><strong>Zip Code:</strong> @decodedParts.ZipCode</li>
  </ul>
  <p><a href="~/Default">Click here</a> to try it again.</p>
</body>
</html>

The Razor code at the top of the Result page manages the conversion of the encoded content back to the individual address parts. First, it isolates the last section of the URL, the encoded data portion, using the very convenient UrlData array, built by ASP.NET as the page loaded:

string encodedContent = UrlData[0] ?? "";

Then the page calls our new Processing.DecodeAddress method, sending it the encoded content and getting structured address data in return:

AddressParts decodedParts = Processing.DecodeAddress(encodedContent);

Once it confirms that the data is valid, it plugs everything into HTML for display in the client browser. Right-click on the Default.cshtml page and select Set As Start Page from the shortcut menu. Then, run the project. The Default page appears, prompting you for address information, as shown in Figure 1.

[Click on image for larger view.] Figure 1. Default.cshtml Page Prompts User for Address Information

Fill in the fields with a reasonable address, then click the Encode button. When the Result page loads, you’ll find the encoded content in the address bar, as part of the URL:

http://localhost:64342/Result/MTYwMCBQZW5uc3lsdmFuaWEgQXZlbnVlCVdhc2hpbmd0b24JREMJMjA1MDA$

Figure 2 shows the output generated by the server-side decoding logic.

[Click on image for larger view.] Figure 2. Resulting Output From Server-Side Decoding Logic

While base-64 encoded data is convenient for quick communications using relatively simple data values, it’s not always the right solution. For one thing, it’s not secure, because any programmer can use a predefined or custom base-64 decoder to restore the data. But if your needs are simple, and you just want a quick way to transmit text content with a bit of contextual variety, base-64 encoding provides a simple solution.

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.

Upcoming Events