A simple redirection mechanism using MVC 5 [and IIS8], including “Legacy” URLs

For some hours, I have been looking for good ideas on how to manage redirections in MVC without necessarily configuring IIS (which is not always available to the programmer, unfortunately).

However, very few of the walkthroughs found on the web take into account dealing with “legacy” URLs; primarily, those who have the “.html” extension.

How do I, in MVC, redirect a URL like “oldfolder/oldfilename.html” to a new MVC URL as “/newfolder/beautifulpeople” ?

The answer is not the simplest, because MVC doesn’t manage all that is asked to it by the browser:

A client request is handled, summarily, like this:

– MVC manages the “MVC” routes (as “/area/controller/action”)

– IIS8 manages the static files, which *presumably* don’t need MVC’s intervention.

If we want to manage all the redirections via MVC, we have to tell MVC that it has to take care of BOTH MVC routes AND static files (unless, of course, the latter don’t exist as such. If the static file exists, let IIS transfer it to the user).

The following is a very basic mechanism which uses MVC and a DB table to store redirections. It can be extended to use caching and OR XML files to be faster. It is also true that redirections should be an exception rather than the norm.

The idea is:

– We create a DB table holding old URLs and corresponding new URLs, along with the code we want to use for the redirection (301 is permanent) and an “active” flag.

– We create a set of MVC “catchall” routes (in the RouteConfig file) which will prevent 404 (“not found”) and will call a new controller that does the “redirection” job

– This new controller checks if the DB has an entry equal to the originating URL. If there is, it redirects  the flow to the new URL (permanenty or temporarily, depending on what we wrote in the DB entry); otherwise, it generates a custom 404 page.

– We tell MVC (in web.config) that it has to also take care of the routes that look like files, unless (of course) they are actual static files.

RouteConfig

In our MVC application’s RouteConfig, we create, AFTER the standard routes, a set of “catchall” routes which will direct to a new controller and action (“Error”/”NotFound”) all the requests that could not be routed to an existing controller.

Like this:

//Default, Real route

routes.MapRoute(

name: “Default”,
url: “{controller}/{action}/{id}”,
defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional },
constraints: new { controller=”Home|Search|Account|Show|Other_Controllers|Error”} // CONSTRAINTS are VERY IMPORTANT
);

//catchAll routes to grab the URLs we want to redirect

routes.MapRoute(
name: “Redirect_OrError”,
url: “{*CatchAll1*}”,
defaults: new { controller = “Error”, action = “NotFound” }
);

routes.MapRoute(
name: “Redirect_OrError_two_levels”,
url: “{CatchAll1*}/{CatchAll2*}”,
defaults: new { controller = “Error”, action = “NotFound” }
);

routes.MapRoute(
name: “Redirect_OrError_three_levels”,
url: “{CatchAll1*}/{CatchAll2*}/{CatchAll3*}”,
defaults: new { controller = “Error”, action = “NotFound” }
);
routes.MapRoute(
name: “Redirect_OrError_four_levels”,
url: “{CatchAll1*}/{CatchAll2*}/{CatchAll3*}/{CatchAll4*}”,
defaults: new { controller = “Error”, action = “NotFound” }
);

Attention: The “default, real” MapRoute method must be called with Constraints (Controller=”Home|…. all the controllers you have). If you don’t do so, MVC will not fall through to our “catchall” controllers. It will stop at the default and will give a “404”.

The “Error” Controller

The “Error” Controller does three things:

1. It checks if the request is actually found in the Redirections table as a URL we consider as “old”

2. If it is there, we redirect to the new URL (if 301, “RedirectPermanent”; if others, “Redirect”)

3. If it doesn’t, it generates a custom error page with a 404 status code

public class ErrorController:Controller
{

[HttpGet]
public ActionResult NotFound()
{

string UrlToTest = Request.Url.AbsolutePath.Trim(‘/’);
Redirections redirect = RedirectManager.GetRedirectFromDB(UrlToTest);
if (redirect != null)
{
if (redirect.RedirectType==”301″)
{
return RedirectPermanent(redirect.NewURL);
}
else
{
return Redirect(redirect.NewURL);
}
}
Response.StatusCode = 404;
ViewBag.UrlToTest = UrlToTest;
return View();
}

public ErrorController()
{

}

}

Now what is the RedirectManager we just found in the code?

Very simply, it is a class which contains some helper classes and methods that allow us to examine the DB and look for the URL we want to redirect from.

public class RedirectManager

{

public RedirectManager()

{

}

public class RedirectionsDTO
{

public int Id { get; set; }
public string OldURL { get; set; }
public string NewURL { get; set; }
public string RedirectType { get; set; }
public Nullable<bool> Active { get; set; }

}

public static Redirections GetRedirectFromDB(string OldUrl)
{
RedirectionsDTO redirects;
using (var db = new OurEntitiesDB()) // this is the name of your Entity Framework
{
string URLWithoutSlash = OldUrl;
if (!URLWithoutSlash.StartsWith(“/”))
{
URLWithoutSlash = “/” + URLWithoutSlash;
}

redirects = (from e in db.Redirections
where (e.Active == true && (e.OldURL == URLWithoutSlash))
select new RedirectionsDTO
{
OldURL = e.OldURL,
NewURL = e.NewURL,
RedirectType=e.RedirectType,
Active=e.Active
}).FirstOrDefault();
if (redirects != null)
{
return new Redirections { OldURL = redirects.OldURL, NewURL = redirects.NewURL, Active=redirects.Active, RedirectType=redirects.RedirectType };
}
else
{
return null;
}

}

}

}

}

How is the Redirect table created? It is created by Entity Framework Database first, based on a table maybe similar to this…

MVC redirection storage table

Table which holds redirection data

There should be an index on the OldURL column because we will include it in our queries.

The generated code could be something like this:

namespace YourPreferredNameSpace

{
using System;
using System.Collections.Generic;

public partial class Redirections
{
public int Id { get; set; }
public string OldURL { get; set; }
public string NewURL { get; set; }
public string RedirectType { get; set; }
public Nullable<bool> Active { get; set; }
}
}

Of course, you have to fill this table with the needed redirections! I suggest you create a controller and allow scaffolding templates to automatically generate the CRUD actions and views for these “Redirections” objects.

Web.Config

What we did so far does not help us if we want to redirect *.html files (or *.pdf files, for that matter). This is beacuse IIS will think that the *.html file should not be a part of an MVC route and it will look for it as a static file. To allow MVC to see the *.html URL as a possible MVC route (and redirect it, if needed), we have to allow MVC to examine all the file requests. To do so, we have three suggested paths. There are: an easy+smart way, a smart way and a semi-silly way. The easy+smart way didn’t work for me. The “smart way” did. It happens.

The easy+smart way is to patch the IIS as this article suggests: http://support.microsoft.com/kb/980368

It didn’t work for me because I had no access to IIS.

The “smart one” is suggested by Colin Farr in this old but powerful article:

http://www.britishdeveloper.co.uk/2010/06/dont-use-modules-runallmanagedmodulesfo.html

he suggests that in System.webServers you add

<system.webServer>
<modules >
<remove name=”UrlRoutingModule-4.0″ />
<add name=”UrlRoutingModule-4.0″ type=”System.Web.Routing.UrlRoutingModule” preCondition=”” />

… other modules…

</module>

Now the silly solution instead:

add the runAllManagedModulesForAllRequests=”true” attribute to the “module” section. Colin Farr already explains why this is silly, so I will not.

<modules runAllManagedModulesForAllRequests=”true”>

Using Google and LinkedIn Accounts for Your .net MVC 5 Site’s Authentication: Tips and Tricks and the error externalLoginCallback?error=access_denied

Some months ago, Rick Anderson and Erik Reitan wrote a very good article in the asp.net website (http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on)  to show how the ASP.NET Identity authentication / authorization framework can leverage external authentication providers as Google, Facebook, Twitter and LinkedIn.

These providers do frequent changes to their interfaces. I am writing this post to underline a couple of tricky errors that the Google / LinkedIn authentications can present to you even if you seemingly do everything by the books.

Authentication with Google: tricks

Google can give you: “externalLoginCallback?error=access_denied” if you don’t allow your application to use certain – not so intuituive – APIs. In particular, the API that must be checked so that Google gives the green light for authentication is the Google+ API, which is not pre-authorized.

This is documented in MSDN: http://blogs.msdn.com/b/webdev/archive/2014/07/02/changes-to-google-oauth-2-0-and-updates-in-google-middleware-for-3-0-0-rc-release.aspx, however, i thought it was a good idea to replicate the same concept on this site in order to make it easier to find it with a search on the redirect URI error.

Another error one can get for the Google authentication is that the Google “project” has to have a PRODUCT_NAME (however, this is easy to spot because it is Google’s page itself to tell you where the problem lies).

All in all, these are the steps to enable the Google + authentication in MVC 5 (highly suggested to use Visual Studio 2013 Release 2 or superior)

Steps in Visual Studio

Create a new MVC project with “Individual user” authentication

From Nuget, download the Microsoft.Owin.Security.Google package

Google authentication package in NuGet

Google authentication package in NuGet

Enable https for your site (project – properties – set SSL enabled to true)

Copy the location of the SSL site (https://localhost:port_number/, for instance: https://localhost:44302/)

Steps in Google Developer Console

Go to https://console.developers.google.com/project. Click on “Create Project”.

Click on the project name, click on APIs in the left menu and select “Google+ API”. Set it to ON (default is OFF)

Screenshot of the Google+ API we have to check to authenticate users in an MVC project

The Google+ API to enable if you want external authentication to work

Click on “Credentials” – oAuth.

Set the “Authorized Javascript origins” to the localhost SSL root (https://localhost:your_port_number). Set the “authorized redirect URLs” to localhost SSL root + /signin-google: for instance, https://localhost:43202/signin-google. No slash at the end of the URL!

Once you do this step, you have a ClientID and a Secret. You will need those for the Visual Studio application.

Configuring the ClientID in Google

Configuring the ClientID in Google

In the Consent screen, choose a contact email and give the PRODUCT a name!

Google Authentication: Consent screen

Google Authentication: Consent screen

Back to Visual Studio

In the folder App_Start, there is a file Startup.Auth.cs. 

Un-comment the lines releted to the Google authentication and fill the ClientId and ClientSecret fields with those given out by Google (these below are examples, they don’t work.

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = “xyzsdusidui2uo2380r787285780457284057804.apps.googleusercontent.com”,
ClientSecret = “Uhjhkjhk189r_iGAsMZdHz”
});

Now you should be good to use Google to register and authenticate your users.

 

Authentication with LinkedIn: tricks

Rick and Erik mention this good tutorial for authentication with Yahoo and LinkedIn (+ a set of other OAuth 2.0 providers)

http://www.beabigrockstar.com/blog/introducing-the-yahoo-linkedin-oauth-security-providers-for-owin/

The LinkedIn tutorial is a bit simplified: it does not take into consideration the Redirection URI for localhost.

How to work with Localhost?

As with Google, you need to write down the httpS address of your localhost, for instance: https://localhost:44302/

Beware: for the authentication to work, the OAuth 2.0 redirection URLs must be set to https://localhost:44302/signin-linkedin (or your port of course). 

You have to set this address here:

Linkedin-no-signin-error

In case you don’t append /signin-linkedin, you’ll end up with an “invalid redirect_uri” error:

invalid-redirect-uri-linkedin

URL routing in Microsoft MVC 5 when Areas are present: potential pitfalls

The “convention over configuration” philosophy of Microsoft’s MVC engine is very useful, as it automates a lot of “wiring” among the different components of a web application.

However, when the “going gets tough”, you have be careful how to circumvent certain convention-based automations that may bring undesired results.

One very important aspect to consider is routing, which technically is not part of MVC (it is a separate component of .net, as you can also use it with web forms) but plays an incredibly important role in the functioning of MVC. Routing is the mechanism that calls a certain object (in MVC, it is usually a controller) based on the URL the user typed or clicked on.

In MVC’s views based on Razor 3, we have a very user HTML helper which creates a URL based on:

– the controller we need

– the action we need.

Its syntax is the following:

@Html.ActionLink(“link text”, “action name”, “controller name”)

When you divide the application in Areas, you have to be extra-careful, as the overload that allows you to specify the Area you need has five arguments rather than four as you might wonder (since the Area is the fourth argument.)

So, in case you need a link that should call a controller inside an Area, it should read:

@Html.ActionLink(“link text”, “action name”, “controller name”, new {Area=”Area Name”}, new {})

The last new{} parameter is in lieu of html attributes. Without that specification, the compiler will think we’re calling the four-parameter overload, which has as fourth parameter… exactly the html attributes, not the Routing object and we would be wrong.

Is that all? No. There is another consideration related to the priority that the routing mechanism uses when you register more than one route in an Area route registration class (or any registration class, for that matter.)

When you create an area, in fact, an additional route AreaRegistration subclass is created for you.

For instance: if you create an area called “Particularities”, Visual Studio will create a class with this code (could not reproduce the indentation correctly here):
using System.Web.Mvc;
namespace RMTestSite.Areas.Particularities
{
public class ParticularitiesAreaRegistration : AreaRegistration
{
public override string AreaName
{
  get
{
  return "Particularities";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
  context.MapRoute(
  "Particularities_default",
  "Particularities/{controller}/{action}/{id}",
   new { action = "Index", id = UrlParameter.Optional }
 );
}
}
}

So, if you want to point to the new Area controller from a page called by a controller that is in another Area, you would have to code:

@Html.ActionLink("Particularities","Index", "Home", new{Area="Particularities"}, new {})

The result of this link is an reference to:
http://localhost:53869/Particularities/Home, which is OK.

Now let us imagine we add another route to the Particularities Area.

For instance, let us add this route before our default route (as a rule, specialized routes should precede general routing rules):

context.MapRoute(
"Particularities_AdditionalParticularitiesdefault",
"Particularities/AdditionalParticularities/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);

What happens now if I use again my Html.ActionLink? I mean: if I use exactly again,
@Html.ActionLink("Particularities","Index", "Home", new{Area="Particularities"}, new {})

The result is different. The address becomes
http://localhost:53869/Particularities/AdditionalParticularities/Home

Why is this? Because the routing mechanism found the first (in terms of: how the code is ordered) pattern in the route series which is matched by our controller/action ActionLink request. It does not choose the “simplest” one.

How can we continue to point to the previous controller/page?

We can specify the route itself when we build our link.

We can do this very easily by using RouteLink rather than the ActionLink. Some say this is in general a preferred routing habit: you can have thusly a list of what routes should do regardless where you want to implement the controllers and you are safer (well, in this case we would have been safe.)

Following our example, our code should have been
@Html.RouteLink("Particularities!", "Particularities_default", new { controller = "home", action = "index" }, new { })

We added the route name to the link, rather than the Area name, and we know we won’t get the wrong controller.

All in all, MVC’s routing mechanism (I specify, again, that is not a part of MVC per se) is a great thing, but we never have to give for granted that it will work in the way we guess a convention should work: it is the .net framework developer’s convention, not necessarily the one we think it is.

Xamarin.Forms: what about performance agains Xamarin.iOS “Classic”? Is it faster or slower?

As of the 1st of August, 2014, Xamarin issued the version 1.2.2 of the platform (version 1.2.1 proved really buggy. Luckily, the Xamarin guys patched it well).

I can now test how Xamarin Forms performs (in terms of speed) compared to a “classic” Xamarin iOS development.

I did a very similar test to the one we did on Android.

It is, in “Release” mode:

1. I create a scrollview for 50 times.

2. To every scrollview I add: 20 labels, 20 textfields with a placeholder, 20 buttons with an event handler.

3. all the labels, textfields and placeholders are removed from the scrollview and destroyed (except for the 100th iteration)

4. the scroll is removed from the root view (except for the 50th iteration)

Xamarin.iOS “Classic” (with iOS designer on visual studio, “release” configuration):

average of 3710 milliseconds on the iPhone 5 64 bit “simulator”

average of 14050 millseconds on the iPhone 5 “classic” device

Xamarin.Forms “Classic” (with iOS designer on visual studio): 

average of 141 milliseconds on the iPhone 5 64 bit “simulator”

average of 609 millseconds on the iPhone 5 “classic” device

 

So, in terms of how fast it is, on iOS Xamarin Forms beats Xamarin “classic” as it happens with Android, at least when it comes to standard UI buttons, labels and textfields.

This is the code:

“iOS Classic”

Code to test classic iOS development with Xamarin, in particular creation and destruction of subviews

Xamarin.iOS speed test code

Xamarin.Forms

Code to test performance of Xamarin Forms on iPhone and iPad

Xamarin.Forms test code

Xamarin Forms performance compared to “Classic” Xamarin development

How does the Xamarin Forms technology compare to the “classic” Xamarin technology on Android?

The “forms” technology has been out since May 28th, 2014 (http://blog.xamarin.com/announcing-xamarin-3/)

On July, 13th, 2o14, the 1.2.1 version of Xamarin.Forms was issued as a hotfix to 1.2.0.

The pros and cons of the philosophy of cross-platform “Forms” will not be discussed now. Let us evaluate the performance of the “forms” paradigm to check if it is as fast as the “old” one.

Of course, I focused on the UI, because the rest of the platform does not change (much).

I tested Android and iOS UI performance (i.e.: the speed) on:

– Genymotion emulator for Android (based on VirtualBox)

– Android “official” emulator (4.4.2 on Intel fast HAXM)

– Android machine (4.1)

In a later post, you will see the same with:

– iOS simulator

– iPhone 5

What did we precisely test?
We tested what, in our experience, is the hardest UI task: the dynamic creation and destruction of elements.

The task is the following:

  • Create (or individuate) a “main view”
  • Add a scrollview to the mainview
  • Add a vertical “stack” layout view to the scrollview
  • Add dynamicaly 100 labels, 100 textfields, 100 buttons with click event handlers
  • delete all of the labels and textfields except for two
  • add another label
  • destroy the “main view”, re-do another four times

At least part of these tasks is asynchronous, so these tests cannot be considered as a reliable proof that Xamarin.forms is faster than “classic” Android development, but… I am very confident now.

How did Xamarin.Forms perform on Android?
Very well, and I have to say surprisingly because it’s supposed to be an abstraction on an abstraction (it probably isn’t!).

The new technology seems to be at least as fast as the old one. Xamarin.Forms seems to have speed on its side.

I do have to say that of course we could not do the exact same things on the two platforms (for instance: in one platform you can plainly replace the child of a view; in the other, you first have to remove the children).

I tried to hone the code so that it gave the best results for both platforms.

GenyMotion: Xamarin Forms (KitKat on windows, i5, 8Gb) is better than Xamarin “classic” (and Genymotion is better than the Intel emulator)

Xamarin.Forms: average of ten tests: 195  milliseconds (three times faster than before, apparently)

Xamarin Android “classic”: average of ten tests: 773  milliseconds

Intel Android Emulator (KitKat on windows, i5, 8Gb): Xamarin Forms is still better than Xamarin “classic” 

Xamarin.Forms: average of ten tests: 566 milliseconds (still four times faster than before, apparently)

Xamarin Android “classic”: average of ten tests: 2100 milliseconds

A real Android device: Xamarin Forms is still better than Xamarin “classic” 
We also tested the performance of Xamarin.forms on an Android device (a 7 inch Samsung Tab2)

Xamarin.Forms: average of ten tests: 302 milliseconds (still four times faster than before, apparently)

Xamarin Android “classic”: average of ten tests: 909 milliseconds

The Code for XAMARIN FORMS:

the code for Xamarin forms performance test

Xamarin forms speed test

 

The Code for XAMARIN “Classic”:

the code to test the speed of Xamarin Android

Xamarin classic Android speed test