Click here to Skip to main content
15,885,546 members
Articles / Web Development / ASP.NET / ASP.NETvNext

Controllers And Routing In ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
3.80/5 (6 votes)
15 Jul 2018CPOL12 min read 31.5K   7   4
Here we’ll discuss how to work with controller actions and routing.

Introduction

Here we’ll discuss how to work with controller actions and routing. We’ll explore routing and its different techniques. Here we see how we pass the data from route and through query string to the action. So let’s start our journey.

Roadmap

Controller And Routing

Let’s try to understand Routing, Controllers and Actions. So let’s open Visual Studio and create the HelloWorld ‘Empty’ project.

  • Add an Empty Controller
  • And name it as ‘FirstController’

Image 1

Here the Controller Name should have suffix (Controller). Because there are lots of (.cs) CSharp files in the project. And this convention helps Visual Studio to identify which is the Controller file. So we can’t remove this suffix ‘Controller’ from any Controller name.

  • Now remove its built-in scaffolded Function and add this function.
C#
public class FirstController : Controller
{
    public string Welcome()
    {
        return "Hello, <b>World!</b>";
    }
}
  • Now come on to the RouteConfig.cs file
C#
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

This routemap tells us the way how to request from the browser to any action. And by default when we run the application, then the browser will execute ‘Home’ controller and its ‘Index’ action. But we’ve created an Empty template project. As we’ve not set a new route for our controller and action so we need to manually request our controller and actions. In the above route we can see in (url) what is the actual route pattern we need to follow.

As we’ve created an empty application so we don’t have any ‘HomeController’ so when we run our application then it will show the error in the browser because by default it is requesting the ‘HomeController’ as we can see in the above RouteMap.

Image 2

But now if we want to execute our ‘Welcome’ action then we need to manually request the url.

Image 3

We know that html renders on the browser. So we make the “World!” bold on welcome method returning string that’s why it is looking us bold.

  • Let’s change the route map. ‘id’ is actually optional so it is not necessary to provide id here.
C#
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "First", action = "Welcome", id = UrlParameter.Optional }
);

Now if just run the application, then our routemap knows that he needs to route the ‘First’ controller and ‘Welcome’ action by default.

Press Ctrl + F5

Image 4

And now it is working without requesting any url explicitly.

  • Let’s copy Welcome function and paste it.
public class FirstController : Controller
{
    public string Welcome()
    {
        return "Hello, <b>World!</b>";
    }

    public string Hello(string name)
    {
        return "Hello " + name;
    }
}

Asp.net mvc automatically mapes request data to parameter values for action methods. If an action method takes a parameter, mvc framework looks for a parameter of the same name in the request data. If the parameter with that name exists, the framework will automatically passed the value of that parameter to the target action. Let me explain there are 2 different ways to pass the data to actions as action parameters.

URL:                             /first/hello/Usama

Query String:                /first/hello?name=Usama

Let’s try to understand both of the scenarios,

Look as we’ve modified our routemap with ‘First’ controller and ‘Welcome’ action but we need to test our hello action. As we know that the ‘id’ is optional in routemap and ‘id’ is just the identifier to identify the action parameter. So, if we change our parameter name from ‘name’ to ‘id’ then it will work for 1st case.

namespace HelloWorld.Controllers
{
    public class FirstController : Controller
    {
        public string Welcome()
        {
            return "Hello, <b>World!</b>";
        }

        public string Hello(string id, string cast = null)
        {
            return "Hello " + id + " " + cast;
        }
    }
}

Now let’s run the application.

Image 5

As ‘Welcome’ action is set in our routemap so we need to mention the action name here in the route.

Routemap was telling us the parameter name ‘id’ and also telling us the path

url: "{controller}/{action}/{id}"

So we request the page with this url

http://localhost:65354/first/hello/usama

And it triggers our hello action.

Now let’s implement with Query String. So first of all change the action parameter from ‘id’ to ‘name’

C#
public string Hello(string id, string cast = null)
{
    return "Hello " + id + " " + cast;
}

Now run the application. And if you request the above url then our hello action will not be triggered. But if you use the query string,

http://localhost:65354/first/hello?name=usama

then our hello action triggers because in the routemap, we’ve set the ‘id’ as action parameter so we need to manually supply the name of the parameter of action with its value followed by question mark (?)

As we can see here we’ve 2 parameters in hello action so to make the 2nd parameter active, we need to supply the value to it as well.

http://localhost:65354/first/hello?name=usama&cast=khan

Image 6

So this is how query string works.

But to pass the data in query strings isn’t a good practice so we should avoid from it, we just use this approach whenever we really need it. Actually it shows the complete parameter name its value, action name, controller name so a malicious user can easily hack our application.

Here we feel the need of convention based routing.

Routing

The complete cycle of a request is

  • First of all the request goes to routes in (RouteConfig.cs)
  • After proper verification from the routes, the request goes to a specific controller action.
  • And then the response comes back in views (browser)

We oftenly use this route.

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

But there are situations where we need to request the url with multiple parameters e.g.

http://localhost:64354/students/search/name/dob

Here we can search the students by their names and their date of births.

So let’s see how to create the custom route. Let’s make a custom route before the default one in RouteConfig file.

Open the ‘RouteConfig’ file,

C#
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

You’ll see this code. Most of the developers even don’t know the purpose of IgnoreRoute

Let me explain, suppose you’re working on a project and a critical problem occur in your project and you’ve to revoke access for the specific controller or module in production environment then you’ll use the IgnoreRoute i.e.

C#
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("Customers/");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Now we can’t access the Customers controller. Now you’re still curious to know the first IgnorRoute purpose, actually .axd files doesn’t physically exists in the project but ASP.NET use the URLs with .axd extensions with Web Resource files (ScriptResource.axd and WebResource.axd) internally, so ASP.NET MVC prevents from trying to handle this kind of request. In simple words, this line is used to revoke the access from files, controller request etc.

Remember: Define the routes more specific to more generic in flow.

As we can see ‘Default’ route is most general route in our application, if it comes first before all the other routes where you’ve apply some kind of constraints (which are limited for some kind of special url request) then surely your this specific route will not handle that request because your general routes execute first and it will handle that request and you’ll not see the results according to your need.

So define the routes first where you have applied some kind of constraint, which are more specific and then slowly comes down and define the less specific to the before route and then in the end a general route to handle the request which isn’t handle by the above routes.

routes.RouteMap();

it has multiple overloads but we commonly use which has 3 parameters {name, url, defaults}

name:         it is for the identification purpose. It should be unique or different from other route names.

url:              it is for the pattern matching

defaults:     it is for some default fixings. Like if you’re requesting the domain in the browser without any controller name and action name then which controller and action will handle that request.

The very common example of this scenario is when we run the application then by default our Home Controller and its Index action executes because they’re fixed in our ‘default’ controller

Why ‘name’ should be unique?

Actually the thing is, RouteTable is a class which contains the application routes. And we know a table has a primary key so our routeMap is actually a record and ‘name’ parameter is the primary key of the record.

Open the Application_Start() in Global.asax.cs

C#
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Here as we can see, we’re passing the RouteTable routes into RouteConfig.RegisterRoutes method. And it is essential that we need to register the routes in Global.asax file as well, otherwise it will not work.

And our RegisterRoutes method has RouteCollection parameter

C#
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

RouteCollection actually provides the collection of route information to use them by the controller actions. In Simple Words, if we conclude this above discussion we know that Global.asax manages the states of the application and when our application starts, Application_Start() triggers and register the RouteTable and our RouteTable has the collection of routes as RouteCollection. And when the request comes in, it matches the pattern from these records of the RouteTable and if it founds the record of route then the request goes to the relevant controller action otherwise 404 error.

Image 7

Image Credit Goes To TutorialsTeacher

But don't take it so much so much serious, RouteTable doesn't physically exist but it works logically like a table. So the name attribute of each route map should be unique.

Now come back to the point here is our default route teaches us how to define the custom route. So we need same 3 parameters to define the custom route. But 'name' and 'url' are necessary and 'defaults' is optional, you can see here MapRoute is the property of RouteCollection and it is clear that we're defining the routes in the collection of routes which maps in the RouteTable.

C#
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "StudentsByNamesAndDOB",
        "students/search/{name}/{year}",
        new { controller = "Students", action = "Index" });

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Now let’s modify the index action in Students controller.

C#
public class StudentsController : Controller
{
    // GET: Students
    public ActionResult Index(string name, int year)
    {
        var student = new Student
        {
            Name = "Usama",
            DOB = new DateTime(19001025)
        };

        return View(student);
    }
}

Now run the application and request the url

http://localhost:64341/students/search

Then the results will be

Image 8

Here I’m not specifying any parameter, and we got 404 error because our url doesn’t match any of our route pattern. Now provide the parameters to make the pattern complete.

http://localhost:64341/students/search/usama/17

And if you’ve applied the breakpoint on the first line of the action then you can see the request comes in into the action.

Constraints

We can apply the constraints (limitations) to our route like year parameter should be in 4 digit and name should be in 3 letters.

// We use Anonymous objects for defaults and for constraints

C#
    routes.MapRoute(
        "StudentsByNamesAndDOB",
        "students/search/{name}/{year}",
        new { controller = "Students", action = "Index" },
        new { name = @"\d{3}", year = @"\d{4}" }
    );

The reason I put @ sign before “” because we’re using escape sequence character and if we’re working with escape sequence character then we need to follow 1 technique out of these 2 methods.

ASP.NET
new { year = @"\d{4}", month = "\\d{2}" }

But this 2nd technique is really ugly.

Now if we try to access the controller action without following these contraints then you’ll see resource not found error. So we need to follow these rules stricktly,

http://localhost:64341/students/search/usa/017

Now it will work.

Now let’s say we want to hit the action when the year should be in range. And it should not work in any other case.

ASP.NET
    routes.MapRoute(
        "StudentsByNamesAndDOB",
        "students/search/{name}/{year}",
        new { controller = "Students", action = "Index" },
        new { name = @"\d{3}", year = @"2001 | 2002" }
    );

Then we’ll use this route map.

Attribute Routing

Now let’s discuss how to define the custom route in more cleaner way.

Look at this code, we’ve currently only 1 custom route if we’re working with larger application this file will grow with lots of custom routes and over the period of time it will become a huge problem for us to handle all the routes. As we can see, each route consists of 3-4 lines of code, it is so much time consuming and hard to keep in mind for logic building.

C#
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "StudentsByNamesAndDOB",
        "students/search/{name}/{year}",
        new { controller = "Students", action = "Index" });


    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Another issue with this approach you need to move back and forth in the files like controller and RouteConfig to verify the pattern you’re writing in your custom route will really works. You need to make sure the action name of the action.

And the 3rd issue if you go back to the studentscontroller and rename the action Index to Welcome then you need to keep in your mind that you’ll also update its relevant route as well otherwise request goes anywhere else.

C#
new { controller = "Students", action = "Welcome" }

So this code is fragile because of the magic strings.

In ASP.Net MVC 5, Microsoft introduces a cleaner and better way to create the custom route. Instead of creating the route here, we can apply it using the attribute to the corresponding action. You might be thinking then why we learn old and poor way of custom route because you may be working with existing code base with convention base custom routes so you need to understand how they work. But if you’re building a new application or improving an existing one, I would recommend you to use attribute routing.

Now let me show you how to define the custom route using an attribute.

First of all in order to use the attribute routing, you need to enable it here.

ASP.NET
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapMvcAttributeRoutes();                 // enabling

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Now back to the controller.

ASP.NET
[Route("students/search/{name}/{year}")]
public ActionResult Index(string name, int year)
{
    var student = new Student
    {
        Name = "Usama",
        DOB = new DateTime(1900, 10, 25)
    };

    return View(student);
}

Now if we want to apply the constraints here on month

ASP.NET
[Route("students/search/{name}/{year:regex(\\d{2})}")]
public ActionResult Index(string name, int year)
{
    var student = new Student
    {
        Name = "Usama",
        DOB = new DateTime(1900, 10, 25)
    };

    return View(student);
}

We can also apply more than 1 constraint on 1 item.

ASP.NET
[Route("students/search/{name}/{year:regex(\\d{2}):range(1, 20)}")]
public ActionResult Index(string name, int year)
{
    var student = new Student
    {
        Name = "Usama",
        DOB = new DateTime(1900, 10, 25)
    };

    return View(student);
}

So you see attribute routes are more powerful we also have bunch of other contraints as well like min, max, minlength, maxlength, int, range, float, guid etc.

You don’t have to memorize any of these constraints just be aware that they are supported by the framework and whenever you need to know about their usage, just Google the things and you’ll find the result.

We can define the attribute routes at controller level as well. Actually we define the RoutePrefix which comes before the action route in the url.

ASP.NET
[RoutePrefix("candidate")]
public class StudentsController : Controller
{
    // GET: Students
    [Route("search/{name}/{year:regex(\\d{2}):range(1, 20)}")]
    public ActionResult Index(string name, int year)
    {
        var student = new Student
        {
            Name = "Usama",
            DOB = new DateTime(1900, 10, 25)
        };

        return View(student);
    }
}

And now this ‘Index’ action will be accessible by this kind of route

http://localhost:64341/candidate/search/usama/15

Anyhow you can explore attribute routing in details here

Conclusion

In this article, we’ve learnt how controller action works with routing. If you know routing then you can easily identify your problems very easily. If you’ve the knowledge of routing then I would say you’ve covered half ASP.NET MVC learning. Here we see the difference of different routing techniques like conventional routing approach and attribute routing approach. Attribute routing is so much easy to implement and easy to learn. We’ve not any kind of difficulty to move back and forth in different files for the pattern. We don’t any need to take care of the rule like we do in RouteConfig.cs (during working with conventional routing approach) that route should be define from most specific to more generic. Here we just need to use the attributes on the top of action and controller. Attribute routing provides us flexibility. And it is my kind suggestion that you must read this article.

So if you’re working on a new project then it would be a better approach to define the attribute routes because conventional routing needs more time, effort and code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralMy vote of 5 Pin
David A. Gray12-Jan-24 20:00
David A. Gray12-Jan-24 20:00 
QuestionThanks for the clear explanation Pin
Gary Schumacher22-Mar-20 15:49
Gary Schumacher22-Mar-20 15:49 
GeneralMy vote of 5 Pin
Hyland Computer Systems16-Jul-18 8:26
Hyland Computer Systems16-Jul-18 8:26 
GeneralRe: My vote of 5 Pin
Muhammad Usama Khan16-Jul-18 21:52
professionalMuhammad Usama Khan16-Jul-18 21:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.