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