Blogs

The One With

Implementing a URL Shrinker

     

It seems that the length of URLs gets bigger and bigger every hour.

Here is a Google Maps URL:

http://www.google.com/maps?f=q&hl=en&geocode=&q=805+N.Brand+Glendale,+CA+91210&sll=37.0625,-95.677068&sspn=25.704151,53.789062&ie=UTF8&ll=34.154609,-118.245227&spn=0.017419,0.033045&z=16&g=805+N.Brand+Glendale,+CA+91210&iwloc=addr&layer=c&cbll=34.158046,-118.255165&panoid=O08NNpO077m501EOlXSgUw&cbp=12,237.8462640496058,,0,-1.0291286092171708

Ugly isn't?

Here is another one I grabbed from a SharePoint site:

http://fiji/Shared%20Documents/Forms/AllItems.aspx?RootFolder=/Shared%20Documents/Specifications/Silverlight/AgDataGrid&FolderCTID=&View={8405395A-43B7-4666-BE45-CEBCE564CE88}

Not as bad as the Google Maps' one but unreadable nonetheless. And I am sure a lot of you have seen some that are even longer then that.

There are services out there that can help with that. http://TinyURL.com is probably the most popular one. Unfortunately, public services like that cannot be used by most companies if the URL in question is an internal one. A URL shrinking service must reside on the corp net. So let's quickly write one.

Getting Started

I am going to start with a new Silverlight application and have an ASP.NET Web Project generated for me by the Visual Studio wizard. We'll concentrate on the server side of things first. We will need:

  • A database to store our URL to ID mappings
  • A page that will handle the redirection
  • And some UI that can take in a long URL and shrink it.

To store the URLs, I am going to use XPO. (MS Access as a back-end during development and something more mature like SQL Server for production)

The shrinker requires only one table:

   1: namespace Shrinker.Data {
   2:     [OptimisticLocking(false)]
   3:     public class Link : PersistentBase {    
   4:         public Link(Session session)
   5:             : base(session) {
   6:         }
   7:         
   8:         int _id;
   9:         [Key(AutoGenerate = true)]
  10:         public int ID {
  11:             get { return _id; }
  12:             set { SetPropertyValue("ID", ref _id, value); }
  13:         }
  14:  
  15:         string _url;
  16:         [Size(1024)]
  17:         public string Url {
  18:             get { return _url; }
  19:             set { SetPropertyValue("Url", ref _url, value); }
  20:         }
  21:     }
  22: }
and two routines, Get and Put.
   1: namespace Shrinker {
   2:     public static class Utils {
   3:         static DevExpress.Xpo.DB.IDataStore _store;    
   4:  
   5:         static Utils() {
   6:             string connectionString = DevExpress.Xpo.DB.AccessConnectionProvider.GetConnectionString(
   7:                 HttpContext.Current.ApplicationInstance.Server.MapPath("~\\App_Data\\Shrinker.mdb"));
   8:  
   9:             _store = DevExpress.Xpo.XpoDefault.GetConnectionProvider(connectionString,
  10:                 DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
  11:         }
  12:  
  13:         public static int Put(Uri uri) {
  14:             using (UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(_store))) {
  15:                 var link = unitOfWork.FindObject<Data.Link>(new BinaryOperator("Url", uri.AbsoluteUri));
  16:                 if (link != null && link.ID > 0) {
  17:                     return link.ID;
  18:                 }
  19:  
  20:                 link = new Data.Link(unitOfWork);
  21:                 link.Url = uri.AbsoluteUri;
  22:  
  23:                 unitOfWork.Save(link);
  24:                 unitOfWork.CommitChanges();
  25:  
  26:                 return link.ID;
  27:             };
  28:         }
  29:  
  30:         public static string Get(int id) {
  31:             using (UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(_store))) {
  32:                 var link = unitOfWork.FindObject<Data.Link>(new BinaryOperator("ID", id));                
  33:                 if (link != null && link.ID > 0) {
  34:                     return link.Url;
  35:                 }
  36:             };
  37:             return null;
  38:         }
  39:  
  40:     }
  41: }
we cab use this now in our Default.aspx. Simply check if there are any params passed, if so try to grab the actual URL, otherwise navigate to a UI page.  (Input validation and error checking omitted for clarity)
   1: protected void Page_Load(object sender, EventArgs e) {
   2:     if (Request.QueryString.Count == 0) {
   3:         Response.Redirect("Shrinker.aspx", true);
   4:         return;
   5:     }
   6:     Response.Redirect(Utils.Get(int.Parse(Request.QueryString[0])));
   7: }

User Interface

For the UI, let's create a simple ASP.NET page with ASPxRoundPanel, ASPxTextBox and an ASPxButton on it.

(I customized it using the BlackGlass Theme)

and in the Page_Load simply use Utils.Put to store the URL from the text box.

   1: namespace Shrinker.Web {
   2:     public partial class Shrinker : System.Web.UI.Page {
   3:         protected void Page_Load(object sender, EventArgs e) {
   4:             if (IsPostBack || IsCallback) {
   5:                 try {
   6:                     Uri uri = new Uri(urlBox.Text);
   7:                     errorLabel.Visible = false;
   8:                     linkLabel.NavigateUrl = string.Format(ConfigurationSettings.AppSettings["RedirectUrl"], Utils.Put(uri).ToString());
   9:                     linkLabel.Text = linkLabel.NavigateUrl;
  10:                     linkLabel.Visible = true;
  11:                 }
  12:                 catch (Exception error) {
  13:                     linkLabel.Visible = false;
  14:                     errorLabel.Text = error.ToString();
  15:                     errorLabel.Visible = true;
  16:                 }
  17:             }
  18:         }
  19:     }
  20: }
we'll add the RedirectUrl to Web.config so that we can change on the production server.
   1: <appSettings>
   2:     <add key="RedirectUrl" value="http://localhost:8081?{0}" />
   3: </appSettings>

and that's it.
 

Silverlight Client

For the Silverlight Client, we will expose the Utils.Put as a WCF web service.

   1: namespace Shrinker.Web {
   2:     [ServiceContract(Namespace = "")]
   3:     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   4:     public class WebService {
   5:         [OperationContract]
   6:         public string Put(string uriString) {
   7:             return string.Format(
   8:                 ConfigurationSettings.AppSettings["RedirectUrl"],
   9:                 Utils.Put(new Uri(uriString)).ToString());
  10:         }
  11:     }
  12: }
import it into our Silverlight Project and call it like so:
   1: namespace Shrinker {
   2:     public partial class Page : UserControl {
   3:         private void OnClick(object sender, RoutedEventArgs e) {
   4:             WebService.WebServiceClient client = new Shrinker.WebService.WebServiceClient();
   5:             client.PutCompleted += new EventHandler<Shrinker.WebService.PutCompletedEventArgs>(PutCompleted);
   6:             client.PutAsync(urlBox.Text);
   7:         }
   8:  
   9:         void PutCompleted(object sender, Shrinker.WebService.PutCompletedEventArgs e) {
  10:             if (e.Error == null) {
  11:                 linkBox.Text = e.Result;
  12:             }
  13:             else {
  14:                 linkBox.Text = e.Error.ToString();
  15:             }
  16:         }
  17:     }
  18: }
The layout for the Page.xaml is done using the AgLayoutControl
   1: <layout:LayoutControl Background="#C9D7DC" VerticalAlignment="Top" Margin="0,32,0,0"  Orientation="Vertical">
   2:             <layout:LayoutGroup VerticalAlignment="Top" HorizontalAlignment="Stretch" Orientation="Horizontal">
   3:                 <TextBlock Text="URL:" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBlock>
   4:                 <TextBox x:Name="urlBox" HorizontalAlignment="Stretch" Text="http://"></TextBox>
   5:                 <Button Content="Shrink" HorizontalAlignment="Right" Style="{StaticResource ButtonStyle}" Width="24" Height="24" 
   6:                         Click="OnClick"></Button>
   7:             </layout:LayoutGroup>
   9:             <TextBox x:Name="linkBox" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Text="http://"></TextBox>
  10: </layout:LayoutControl>

 

Bonus

After you deploy the Shrinker to your corp net. (Assuming you added all the error handling and switched to a SQL Server) you can have it always available for you in the System Tray by running the Shrinker app outside of the browser.

Download the http://community.devexpress.com/blogs/theonewith/fiji.zip and run it with the URL of your .xap file as a command line parameter.

For example: "fiji.exe http://localhost:8081/Silverlight/Shrinker.xap"

Download Source

Cheers,

Azret

 
Published Dec 11 2008, 11:37 PM by Azret Botash (DevExpress)
Technorati tags: Silverlight, XPO, AgLayoutControl
Bookmark and Share

Comments

 

Linton said:

Thanks for taking time to create this interesting and practical post. I'm very interested with your work on fiji.exe

Linton

December 12, 2008 10:53 AM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.