Använd Route Constraints i ASP.NET MVC

För att kunna skicka värden till våra action-metoder tvid vanliga GET-anrop så kan vi använda route-värden. Som standard i ett ASP.NET MVC-projekt finns ett sådant värde i routen vid namn ”id”. I global.asax ser det ut så här:

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Det som anges där är att vi eventuellt kan ta in en parameter vid namn id. Den är dock satt som optional, så det finns inget krav på att något id ska skickas med.

För att se om något värde tas emot när vi surfar till /Home/Index/123 så ändrar vi på Index-metoden i HomeController till:

Public ActionResult Index(string id = "-1")
{
    ViewData["Message"] = "Welcome to ASP.NET MVC!";
    ViewData["id"] = id;
 
    return View();
}

Vi ändrar även i vyn till:

<%@PageLanguage="C#"MasterPageFile="~/Views/Shared/Site.Master"Inherits="System.Web.Mvc.ViewPage"%>
<asp:ContentID="Content1"ContentPlaceHolderID="TitleContent"runat="server">
    Home Page
</asp:Content>
<asp:ContentID="Content2"ContentPlaceHolderID="MainContent"runat="server">
<h2><%: ViewData["Message"] %></h2>
<p>
        ID: <%: ViewData["id"] %>
</p>
</asp:Content>

I Index-metoden så tar vi nu emot parametern id om den finns. Annars används standardvärdet -1. Surfar vi nu till /Home/Index/123 så får vi upp det här:

1 - LongRoute

Ett problem nu är att vi kan skicka in t.ex. ”123abc” som värde, vilket inte är vad vi har förväntat oss då vi vill ha ett heltal. Det vi kan göra för att försäkra oss om att det är just tal som skickas in så kan vi ändra direkt i action-metoden så att parametern för id är en int istället för string. Då ignoreras alla strängar och vi får istället standardvärdet.

Om vi nu istället vill ha mer kontroll, och enbart skicka användaren dit om det är just ett tal så kan vi använda Route Constraints. På så vis kan vi styra användaren till olika controllers helt baserat på vad som skickas in. För att tydligt visa detta så kan vi skapa en ny route-mappning som kollar om användaren anger ett heltal istället för controller-namnet (till exempel /12345 istället för /home).

För att lyckas med det så skapar vi en ny MapRoute i global.asax:

routes.MapRoute(
"Id",
"{id}",
new {controller = "Home", action = "Index" },
new { id = @"\d+" }
);

Det här är en väldigt enkel MapRoute som använder en route constraint i form av ett regular expression på sista raden. Det kollar om ett heltal har skickats in och körs i sådana fall. Om det inte är ett heltal så ignoreras routen och ASP.NET MVC väljer automatiskt den som passar bäst (vilket är standard-routen).

Surfar till /123 så ser vi på sidan att ID är 123, och surfar vi till /home så är ID satt till -1. Nu har vi angett samma controller och action, men vi kan självklart sätta till exempel ”Product” som controller och ”Details” som action om vi hade velat det. På så vis kan vi enkelt skapa nya routes med specifika regler. Alla länkar som vi har genererat med de medföljande html helpers som följer med kommer automatiskt att skickas till /{id} om den routen passar bäst.

Det här kan dock leda till ett problem. Vi får inte full kontroll, utan kan bara använda regular expressions för att ange regler som skall användas. Vad händer om vi vill ha logik bakom? Ett exempel på det skulle vara om vi skickar användaren till ”/billig-bil-till-salu” och då vill göra ett uppslag mot databasen för att se vilket id det skall mappas till (så kallad ”slug”).

För att göra det möjligt så kan vi skapa en egen route constraint genom att implementera IrouteConstraint och metoden ”bool Match(...)”. Här returnerar vi true eller false beroende på om requesten bör hanteras av vår route constraint.

I vårt fall ska vi ha en IdRouteConstraint som ska se om värdet dels är ett tal, men även om värdet är över 3.

En väldigt enkel implementation av det skulle kunna se ut så här:

using System;
using System.Web;
using System.Web.Routing;
 
namespace MvcRouteConstraints
{
public class IdRouteConstraint : IRouteConstraint
    {
        public bool Match(HttpContextBase httpContext,
            Route route, string parameterName,
            RouteValueDictionary values,
            RouteDirection routeDirection)
        {
            int id;
 
            if (!Int32.TryParse(values[parameterName] asstring, out id))
                return false;
 
            if (id > 3)
                return true;
 
            returnfalse;
        }
    }
}

För att använda vår route constraint så får vi ändra lite i global.asa:

routes.MapRoute(
  "Id",
  "{id}",
  new { controller = "Home", action = "Index" },
  new { id = newIdRouteConstraint() }
);

Surfar vi nu till /1 så får vi ett 404-meddelande då vi dels inte accepterar så låga tal i vår Route Constraint, samt då det inte finns någon Controller vid namn ”1”. Surfar vi däremot till /4 så får vi fram 4 som id. Vi kan även surfa till /Home, vilket ger oss -1 som id då vi inte skickade in något.

Det här gör det väldigt enkelt att skapa egna utseenden på routes, samt lägga in egen validering för dem.

Vad gäller ”slugs” som jag nämnde tidigare så kan jag rekommendera en titt på modelbinders i ASP.NET MVC för information om hur man kan skapa sådana.

No Comments