Saturday, August 20, 2016

MVC Tips n Study

The parameter to RouteConfig.RegisterRoutes(RouteTable.Routes) in Application_Start() event, is the value of the static RouteTable.Routes property, which is an instance of the RouteCollection class.

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     
           });  
}
or the Route can be defined as below also.
 
Route myRoute = new Route("{controller}/{action}"new MvcRouteHandler());
            myRoute.Defaults.Add("controller", "Home");
            myRoute.Defaults.Add("action", "index");
            myRoute.Defaults.Add("id", UrlParameter.Optional);
            routes.Add("MyRoute", myRoute);

Above can also be defined simply like,

   routes.MapRoute("MyRoute", "{controller}/{action}");

The MapRoute method is solely for use with MVC applications. ASP.NET Web Forms applications can use the MapPageRoute method, also defined in the RouteCollection class.

A URL Pattern with a Mixed Segment in the RouteConfig.cs File. 
The pattern in this route matches any two-segment URL where the first segment starts with the letter X. The value for controller is taken from the first segment, excluding the X.
The action value is taken from the second segment.

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute("", "X{controller}/{action}");

            routes.MapRoute("MyRoute", "{controller}/{action}",
            new { controller = "Home", action = "Index" });

            routes.MapRoute("", "Public/{controller}/{action}",
            new { controller = "Home", action = "Index" });
        }


Routes are applied in the order in which they appear in the RouteCollection object. the MapRoute method adds a route to the end of the collection, which means that routes are generally applied in the order in which they are defined.

# The route’s URL pattern defines the standard controller and action variables, as well as a custom variablecalled id. This route will match any zero-to-three-segment URL. The contents of the third segment will be assigned to the id variable, and if there is no third segment, the default value will be used.
 public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
            new
            {
                controller = "Home",
                action = "Index",
                id = "DefaultId"
            });
        }


we can access any of the segment variables in an action method by using the RouteData.Values property.

# Accessing a Custom Segment Variable in an Action Method in the HomeController.cs File
 public ActionResult CustomVariable()
        {
            ViewBag.Controller = "Home";
            ViewBag.Action = "CustomVariable";
            ViewBag.CustomVariable = RouteData.Values["id"];
            return View();
        }

This method obtains the value of the custom variable in the route URL pattern and passes it to the view using the ViewBag. 
Using Custom Variables as Action Method Parameters

Custom Route Variables can be accessed in an Action method by using RouteData.Values["name"] property.
Some names are reserved and cannot be used as custom segment variable names eg. controller, action and aarea.

Like, in above code , we have accessed the custom route variable in action method like this.

             ViewBag.CustomVariable = RouteData.Values["id"];  // output: "DefaultId"

        In addition to RouteData.Values["name"] property, another way to access custom route variables is to define action method parameters with names that match the URL pattern variables.  
     Then the MVC framework will pass the values obtained from the URL as parameters to the action method i.e The MVC framework will compare the list of url segment variables with the list of action method parameters, and if the names match, then pass the values from the URL to the action method. for eg. using the above route definition, we can define action method as 

public ActionResult Index(string id)  // here this id value in defined in the route def.
        {
            ViewBag.CustomVariable = id; // output value = "DefaultId"
        }

Also, the MVC framework will try to convert the URL value to whatever parameter type we defined in route definition.

# In the previous route definition, We have given a default value to the "id" parameter in route. But suppose if we make "id" route variable as optional eg.

routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                   new { controller = "Home", action = "Index", id = UrlParameter.Optional });

then, we need to make a null value check in controller eg.

public ActionResult Index(string id)
        {
            ViewBag.CustomVariable = id ?? "<No Value>";
        }


# Default values for url segment variables should be defined in controller code and not in the routing definition,
It is not good practice to give default values for url segment variables into the route definition. 
Instead we can use C# optional parameters along with the optional segment variables in the route to define the default values for an action method parameters.
for eg.
public ActionResult Index(string id = "DefaultId")
    {
       // Now here the "id" varialbe is made optional in the route definition
       // and given a value as C# optional parameter.
       //Hence, if the URL doesn't have a value for 'id', then also it will have a value of    
           'DefaultId' 
        ViewBag.CustomVariable = id;
        return View();
    }
 



 Defining Variable-Length Routes

This allows you to route URLs of arbitrary lengths in a single route. You define support for variable segments by designating one of the segment variables as a catchall, done by prefixing it with an asterisk (*)

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional});


This route will now match any URL, irrespective of the number of segments it contains or the value of any of those segments. The first three segments are used to set values for the controller, action, and id variables, respectively.
If the URL contains additional segments, they are all assigned to the catchall variable.

   Notice that the segments captured by the catchall are presented in the form segment/segment/segment. I am responsible for processing the string to break out the individual segments.
Eg.
/Customer/List/All/Delete/Perm   ==>      controller = Customer,  action = List,  id = All,  catchall =Delete/Perm



Prioritizing Controllers by Namespaces 
when the value of the controller variable is Home, then the MVC Framework looks for a controller called HomeController. This is an unqualified class name, which means that the MVC Framework doesn’t know what to do if there are two or more classes called HomeController in different namespaces.

To address this problem, I can tell the MVC Framework to give preference to certain namespaces when attempting to resolve the name of a controller class.

 Specifying Namespace Resolution Order in the RouteConfig.cs File
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                new { controller = "Home", action = "Index",id = UrlParameter.Optional},
                new[] { "URLsAndRoutes.AdditionalControllers" });


we express the namespaces as an array of strings and in the listing I have told the MVC Framework to look in the URLsAndRoutes.AdditionalControllers namespace before looking anywhere else.  
 If a suitable controller cannot be found in that namespace, then the MVC Framework will default to its regular behavior and look in all of the available namespaces.
The namespaces added to a route are given equal priority. The MVC Framework does not check the first namespace before moving on to the second and so forth. 
For example, suppose that I added both of the project namespaces to the route, like this:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
          new { controller = "Home", action = "Index", id = UrlParameter.Optional },
          new[] { "URLsAndRoutes.AdditionalControllers", "UrlsAndRoutes.Controllers" });



I would see the same error ie.'Multiple Types were found that matched the Controller  named 'Home'.,    because the MVC Framework is trying to resolve the controller class name in all of the namespaces added to the route.

      If I want to give preference to a single controller in one namespace, but have all other controllers resolved in another namespace, I need to create multiple routes, as shown below

Using Multiple Routes to Control Namespace Resolution in the RouteConfig.cs File

   routes.MapRoute("AddContollerRoute", "Home/{action}/{id}/{*catchall}",
                    new { controller = "Home", action = "Index",id = UrlParameter.Optional },
                    new[] { "URLsAndRoutes.AdditionalControllers" });

    routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                    new { controller = "Home", action = "Index",id = UrlParameter.Optional },
                    new[] { "URLsAndRoutes.Controllers" });

       The first route applies when the user explicitly requests a URL whose first segment is Home and will target the Home controller in the AdditionalControllers folder. All other requests, including those where no first segment is specified, will be handled by controllers in the Controllers folder. 
  We can tell the MVC Framework to look only in the namespaces that we specify. If a matching controller cannot be found, then the framework will not search elsewhere.  for eg. see below listing:

Disabling Fallback Namespaces in the RouteConfig.cs File

   Route myRoute = routes.MapRoute("AddContollerRoute","Home/{action}/{id}/{*catchall}",
                    new {controller = "Home", action = "Index", id = UrlParameter.Optional },
                    new[] { "URLsAndRoutes.AdditionalControllers" });

    myRoute.DataTokens["UseNamespaceFallback"] = false;

The MapRoute method returns a Route object.To disable searching for controllers in other namespaces, take the Route object and set the UseNamespaceFallback key in the DataTokens collection property to false. 
This setting will be passed along to the component responsible for finding controllers, which is known as the controller factory. 
 The effect of this addition is that requests that cannot be satisfied by the Home controller in the AdditionalControllers folder will fail.


Constraining Routes
Constraining a Route Using a Regular Expression
The first technique is constraining a route using regular expressions

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                new { controller = "^H.*" },
                new[] { "URLsAndRoutes.Controllers" });
 


      Constraints are defined by passing them as a parameter to the MapRoute method. 
Like default values, constraints are expressed as an anonymous type, where the properties of the type correspond to the names of the segment variables they constrain.In this example, I have used a constraint with a regular expression that matches URLs only where the value of the controller variable begins with the letter H.

    Default values are applied before constraints are checked. So, for example, if i request the Url , the default value for controller, which is Home, is applied. the constraints are then checked, and since the controller value begins with H, the default UrL will match the route.   Constraining a Route to a Set of Specific Values Regular expressions can constrain a route so that only specific values for a URL segment will cause a match. I do this using the bar (|) character.
   Constraining a Route to a Specific Set of Segment Variable Values in the RouteConfig.cs File

This route will match URLs only when the controller variable begins with the letter H and the action variable is Index or About.
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                   new { controller = "Home", action = "Index", id = UrlParameter.Optional},
                   new { controller = "^H.*", action = "^Index$|^About$" },
                   new[] { "URLsAndRoutes.Controllers" });


Constraining a Route Using HTTP Methods
Routes can be constrained so that they match a URL only when it is requested by using a specific HTTP method.
 
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
 new{controller="Home",action="Index",id=UrlParameter.Optional },
 new{controller="^H.*",action="Index|About", httpMethod = new HttpMethodConstraint("GET")},
 new[] { "URLsAndRoutes.Controllers" });


The format for specifying an HTTP method constraint is slightly odd. It does not matter what name is given to the property, as long as it is assigned to an instance of the HttpMethodConstraint class.

Note the ability to constrain routes by http method is unrelated to the ability to restrict action methods using attributes such as HttpGet and HttpPost. the route constraints are processed much earlier in the request pipeline, and they determine the name of the controller and action required to process a request. the action method attributes are used to determine which specific action method will be used to service a request by the controller.

we pass the names of the HTTP methods I want to support as string parameters to the constructor of the
HttpMethodConstraint class
Also,  we could have easily added support for other methods, like this:

httpMethod = new HttpMethodConstraint("GET", "POST") },
 

Using Type and Value Constraints for segment variables.

Using a Built-in Type/Value Constraint in the RouteConfig.cs File
The MVC Framework contains a number of built-in constraints that can be used to restrict the URLs that a route matches based on the type and value of segment variables.

using System.Web.Mvc.Routing.Constraints;

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                            new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                            new
                                {
                                    controller = "^H.*",
                                    action = "Index|About",
                                    httpMethod = new HttpMethodConstraint("GET"),
                                    id = new RangeRouteConstraint(10, 20)
                                },
                            new[] { "URLsAndRoutes.Controllers" });

You can combine different constraints for a single segment variable by using the CompoundRouteConstraint class, which accepts an array of constraints as its constructor argument.

  routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                  new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                  new
                      {
                        controller = "^H.*",
                        action = "Index|About",
                        httpMethod = new HttpMethodConstraint("GET"),
                        id = new CompoundRouteConstraint(new IRouteConstraint[] {
                                                              new AlphaRouteConstraint(),
                                                              new MinLengthRouteConstraint(6)
                                                                })
                       },
                   new[] { "URLsAndRoutes.Controllers" });

Defining a Custom Constraint 
We can define your own custom constraints by implementing the IRouteConstraint interface. To demonstrate this feature, I added an Infrastructure folder to the example project and created a new class file called UserAgentConstraint.cs, the contents of which are shown


namespace UrlsAndRoutes.Infrastructure
{
    public class UserAgentConstraint : IRouteConstraint
    {
        private string requiredUserAgent;
        public UserAgentConstraint(string agentParam)
        {
            requiredUserAgent = agentParam;
        }
        public bool Match(HttpContextBase httpContext, Route route, string parameterName,
        RouteValueDictionary values, RouteDirection routeDirection)
        {
            return httpContext.Request.UserAgent != null &&
            httpContext.Request.UserAgent.Contains(requiredUserAgent);
        }
    }
}


The IRouteConstraint interface defines the Match method, which an implementation can use to indicate to the routing system if its constraint has been satisfied.
            The parameters for the Match method provide access to the request from the client, the route that is being evaluated, the parameter name of the constraint, the segment variables extracted from the URL, and details of whether the request is to check an incoming or outgoing URL. 
For the example, I check the value of the UserAgent property of the client request to see if it contains a value that was passed to the constructor.
Applying a Custom Constraint in a Route in the RouteConfig.cs File
 routes.MapRoute("ChromeRoute", "{*catchall}",
                new { controller = "Home", action = "Index" },
                new { customConstraint = new UserAgentConstraint("Chrome") },
                new[] { "UrlsAndRoutes.AdditionalControllers" });

    routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
                    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                    new {
                    controller = "^H.*", action = "Index|About",
                    httpMethod = new HttpMethodConstraint("GET"),
                    id = new CompoundRouteConstraint(new IRouteConstraint[] {
                                             new AlphaRouteConstraint(),
                                             new MinLengthRouteConstraint(6)})
                       },
                  new[] { "URLsAndRoutes.Controllers" });
}

In the above route definitions, we have constrained the first route so that it will match only requests made from browsers whose user-agent string contains Chrome. If the route matches, then the request will be sent to the Index action method in the Home controller defined in the AdditionalControllers folder, irrespective of the structure and content of the URL that has been requested. The URL pattern consists of just a catchall segment variable, which means that the values for the controller and action segment variables will always be taken from the defaults and not the URL itself.
The second route will match all other requests and target controllers in the Controllers folder, subject to the type and value constraints I applied in the previous section. The effect of these routes is that one kind of browser always ends up at the same place in the application.

Using Attribute Routing
All of the examples so far in this chapter have been defined using a technique known as convention-based routing. MVC 5 adds support for a new technique known as attribute routing, in which routes are defined by C# attributes that are applied directly to the controller classes. 

Enabling and Applying Attribute Routing
Attribute routing is disabled by default and is enabled by the MapMvcAttributeRoutes extension method, which is called on the RouteCollection object passed as the argument to the static RegisterRoutes method.  
Calling the MapMvcAttributeRoutes method causes the routing system to inspect the controller classes in the application and look for attributes that configure routes.
 
 public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapMvcAttributeRoutes();
    routes.MapRoute("Default", "{controller}/{action}/{id}",
    new
    {
        controller = "Home",
        action = "Index",
        id = UrlParameter.Optional
    },
    new[] { "UrlsAndRoutes.Controllers" });
}


Applying the Route Attribute in the CustomerController.cs File
[Route("Test")]
public ActionResult Index()
{
    ViewBag.Controller = "Customer";
    ViewBag.Action = "Index";
    return View("ActionName");
}

Above I used the Route attribute to specify that the Index action on the Customer controller can be accessed through the URL /Test.
This is the basic use of the Route attribute, which is to define a static route for an action method. 
The Route defines two properties,Name:        Assigns a name to the route, used for generating outgoing URLs from a specific routeTemplate:  Defines the pattern that will be used to match URLs that target the action method

If you supply just one value when applying the Route attribute—as I did in the listing—then the value is assumed to be the pattern that will be used to match routes. Patterns for the Route attribute follow the same structure as for convention-based routing, although there are some differences when it comes to constraining route matching 

When an action method is decorated with the Route attribute, it can no longer be accessed through the convention-based routes defined in the RouteConfig.cs file. For my example, this means that the Index action of Customer controller can no longer be reached through the /Customer/Index URL.

Caution the Route attribute stops convention-based routes from targeting an action method even if attribute routing is disabled. take care to call the MapMvcAttributeRoutes method in the RouteConfig.cs file or you will create unreachable action methods.
Tip You can apply the Route attribute to the same action method multiple times and each instance will create a new route.

 
Creating Routes with Segment Variables
Creating an Attribute Route with a Segment Variable in the CustomerController.cs File

[Route("Users/Add/{user}/{id}")]
public string Create(string user, int id)
{
    return string.Format("User: {0}, ID: {1}", user, id);
}
Above The route I defined with the Route attribute mixes a static prefix (Users/Add) with user and id segment variables that correspond to the method arguments. 
The MVC Framework uses the model binding feature to convert the segment variable values to the correct types in order to invoke the Create method       
Notice that each instance of the Route attribute operates independently, and that means that I am able to create entirely different routes to target each of the action methods in the controller.
Applying Route ConstraintsRoutes defined using attributes can be constrained just like those defined in the RouteConfig.cs file, although the
technique is more direct.
[Route("Users/Add/{user}/{id:int}")]
public string Create(string user, int id)
{
    return string.Format("Create Method - User: {0}, ID: {1}", user, id);
}


[Route("Users/Add/{user}/{password}")]
public string ChangePass(string user, string password)
{
    return string.Format("ChangePass Method - User: {0}, Pass: {1}",
    user, password);
}


The new action method, called ChangePass, takes two string arguments. But I have used the Route attributeto associate the action with the same URL pattern as for the Create action method: a static prefix of /Users/Add,followed by two segment variables. To differentiate between the actions, I applied a constraint to the Route attribute for the Create method
This tells the routing system that the Create action method should only be targeted by requests where the value provided for the id segment is a valid int value. The int constraint corresponds to the IntRouteConstraint constraint class.

Combining Constraints
You can apply multiple constraints to a segment variable to further restrict the range of values that the route will match.


[Route("Users/Add/{user}/{password:alpha:length(6)}")]
public string ChangePass(string user, string password)
{
    return string.Format("ChangePass Method - User: {0}, Pass: {1}",
    user, password);
}
Multiple constraints are chained together using the same format as for a single constraint: a colon followed by the name of the constraint and, if required, a value in parentheses. The route created by the attribute in this example will only match alphabetic strings that have exactly six characters.

Using a Route Prefix
You can use the RoutePrefix attribute to define a common prefix that will be applied to all of the routes defined in a controller, which can be useful when you have multiple action methods that should be targeted using the same URL root. 
 namespace UrlsAndRoutes.Controllers
{
    [RoutePrefix("Users")]
    public class CustomerController : Controller
    {
        [Route("~/Test")]
        public ActionResult Index()
        {
            ViewBag.Controller = "Customer";
            ViewBag.Action = "Index";
            return View("ActionName");
        }
        [Route("Add/{user}/{id:int}")]
        public string Create(string user, int id)
        {
            return string.Format("Create Method - User: {0}, ID: {1}", user, id);
        }
        [Route("Add/{user}/{password}")]
        public string ChangePass(string user, string password)
        {
            return string.Format("ChangePass Method - User: {0}, Pass: {1}",
            user, password);
        }
        public ActionResult List()
        {
            ViewBag.Controller = "Customer";
            ViewBag.Action = "List";
            return View("ActionName");
        }
    }
}
   I used the RoutePrefix attribute to specify that the routes for the action method should be prefixed with Users.With the prefix defined, I am able to update the Route attribute for the Create and ChangePass action methods to remove the prefix. The MVC Framework will combine the prefix with the URL pattern automatically when the routes are created.
Notice that I have also changed the URL pattern for the Route attribute applied to the Index action method as follows:

[Route("~/Test")]

Prefixing the URL with ~/ tells the MVC Framework that I don’t want the RoutePrefix attribute applied to the Index action method, which means that it will still be accessible through the URL /Test.


Advanced Routing Features

Instead of hard coding the links , A better alternative
is to use the routing system to generate outgoing URLs, which ensures that the URLs scheme is used to produce the URLs dynamically and in a way that is guaranteed to reflect the URL schema of the application.

Using the Routing System to Generate an Outgoing URL
 The simplest way to generate an outgoing URL in a view is to call the Html.ActionLink helper method
@Html.ActionLink("This is an outgoing URL", "CustomVariable")
The parameters to the ActionLink method are the text for the link and the name of the action method that the link should target.
The HTML that the ActionLink method generates is based on the current routing configuration.
<a href="/Home/CustomVariable">This is an outgoing URL</a>
 The benefit of this approach is that it automatically responds to changes in the routing configuration. I have changed the route defined and added a new route to the RouteConfig.cs file
routes.MapMvcAttributeRoutes();
routes.MapRoute("NewRoute", "App/Do{action}",new { controller = "Home" });
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                new { controller = "Home", action = "Index", id = UrlParameter.Optional });  


The new route changes the URL schema for requests that target the Home controller. If you start the app, you will see that this change is reflected in the HTML that is generated by the ActionLink HTML helper method, as follows:
<a href="/App/DoCustomVariable">This is an outgoing URL</a>
You can see how generating links in this way addresses the issue of maintenance. I am able to change the routing schema and have the outgoing links in the views reflect the change automatically. And, of course an outgoing URL becomes a regular request when you click on the link, and so the routing system is used again to target the action method correctly.


Targeting Other Controllers
The default version of the ActionLink method assumes that you want to target an action method in the same controller that has caused the view to be rendered. To create an outgoing URL that targets a different controller, you can use an overload that allows you to specify the controller name :
@Html.ActionLink("This targets another controller", "Index", "Admin")
The routing system includes routes that have been defined using the Route attribute when determining how to target a given action method.
 
Targeting an Action Decorated with the Route Attribute in the ActionName.cshtml File
@Html.ActionLink("This targets another controller", "Index", "Customer")
The link that is generated is as follows:
<a href="/Test">This targets another controller</a>
This corresponds to the Route attribute I applied to the Index action method in the Customer controller 

 Supplying Values for Segment Variables
@Html.ActionLink("This is an outgoing URL", "CustomVariable", new { id = "Hello" })

I have supplied a value for a segment variable called id. If the application uses the route shown below
 routes.MapRoute("NewRoute", "App/Do{action}", new { controller = "Home" });  
then the following HTML will be rendered in the view:
<a href="/App/DoCustomVariable?id=Hello">This is an outgoing URL</a>Notice that the value I supplied has been added as part of the query string to fit into the URL pattern described by the route. This is because there is no segment variable that corresponds to id in that route.I have edited the routes in the RouteConfig.cs file to leave only a route that does have an id segment.

routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new {
          controller = "Home", action = "Index",
         id = UrlParameter.Optional
        });
Start the application again and you will see that the call to the ActionLink helper method in the ActionName.cshtml
view produces the following HTML element:
<a href="/Home/CustomVariable/Hello">This is an outgoing URL</a>






This time, the value I assigned to the id property is included as a URL segment, in keeping with the active route in the application configuration.

    @Html.ActionLink("This is an outgoing URL", "Index", "Home",
                      "https", "myserver.mydomain.com", " myFragmentName",
                      new { id = "MyId" },
                      new { id = "myAnchorID", @class = "myCSSClass" })

When rendered in a view, the ActionLink helper generates the following HTML:
<a class="myCSSClass" href="
https://myserver.mydomain.com/Home/Index/MyId#myFragmentName"
id="myAnchorID">This is an outgoing URL</a>



 



0 comments:

Post a Comment

Twitter Delicious Facebook Digg Stumbleupon Favorites More