{"id":3656,"date":"2016-05-13T17:00:42","date_gmt":"2016-05-13T16:00:42","guid":{"rendered":"https:\/\/u-labs.de\/portal\/?p=3656"},"modified":"2016-05-10T07:04:05","modified_gmt":"2016-05-10T06:04:05","slug":"asp-net-mvc-kleingeschriebene-routen-mit-minus-trennen-statt-pascalcase","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/asp-net-mvc-kleingeschriebene-routen-mit-minus-trennen-statt-pascalcase\/","title":{"rendered":"ASP.NET MVC: Kleingeschriebene Routen mit Minus trennen statt PascalCase"},"content":{"rendered":"<p>PascalCase und camelCase sind tolle Namenskonventionen, um beispielsweise in .NET zu programmieren. Diese auch bei URLs zu verwenden wie bei ASP.NET MVC standardm\u00e4\u00dfig der Fall ist allerdings zuviel des Guten: Hier hat sich die Schreibweise in Kleinbuchstaben durchgesetzt. Mehrere W\u00f6rter trennt man zur besseren Lesbarkeit meist mit einem Minus, selten auch mit Unterstrichen. Von Haus aus bietet ASP.NET leider nur die M\u00f6glichkeit, Controller und Actions klein zu schreiben. Eine Trennung mit Minuszeichen ist nicht vorgesehen, wodurch bei l\u00e4ngeren Bezeichnungen die Lesbarkeit leidet. In folgendem Artikel zeigen wir euch n\u00fctzliche Hilfsklassen, mit denen ihr die URLs nach euren W\u00fcnschen anpassen k\u00f6nnt.<\/p>\n<h3><strong>Schritt #1: Klasse zur Umwandlung der Namenskonventionen<\/strong><\/h3>\n<p>Vom Konzept her werden wir die generierten URLs in unser eigenes Format umwandeln. Bevor die Anfrage von ASP.NET verarbeitet wird, wandeln wir diese wieder in das urspr\u00fcngliche PascalCase-Format um. Denn intern soll ASP.NET schlie\u00dflich nach wie vor mit den CamelCase-Bezeichnungen arbeiten. Wer selbst dies nicht m\u00f6chte, kann den letzten Schritt weglassen. Um dies zu bewerkstelligen, ben\u00f6tigen wir eine Klasse zur Umwandlung in beide Richtungen:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Text;\r\n\r\n\/\/\/&lt;summary&gt;\r\n\/\/\/ Wandelt CamelCase in mit minus-getrennte-urls um und umgekehrt\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class HyphenatedConverter {\r\n    \/\/\/&lt;summary&gt;\r\n    \/\/\/ Wandelt die Gro\u00dfbuchstaben eines PascalCase-Strings in Kleinbuchstaben um, die mit einem Minus getrennt werden (PascalCase =&gt; pascal-case)\r\n    \/\/\/ &lt;\/summary&gt;\r\n    public static string FromPascalCase(string inputContent) {\r\n        List&lt;char&gt; convertedContent = inputContent.ToList();\r\n        for (int i = 0; i &lt; convertedContent.Count; i++) { var currentChar = convertedContent&#x5B;i]; if (currentChar == '?') { break; } if (Char.IsUpper(currentChar)) { convertedContent&#x5B;i] = convertedContent&#x5B;i].ToString().ToLower().FirstOrDefault(); if (i &gt; 0) {\r\n                    convertedContent.Insert(i, '-');\r\n                }\r\n            }\r\n        }\r\n        return new string(convertedContent.ToArray());\r\n    }\r\n    \/\/\/&lt;summary&gt;\r\n    \/\/\/ Revidiert die Umwandlung von FromPascalCase: Aus pascal-case wird wieder PascalCase\r\n    \/\/\/ &lt;\/summary&gt;\r\n    public static string ToPascalCase(string inputContent) {\r\n        if (inputContent.Length == 0)\r\n            return inputContent;\r\n\r\n        var convertedContent = new StringBuilder();\r\n        convertedContent.Append(Char.ToUpperInvariant(inputContent&#x5B;0]));\r\n\r\n        for (int i = 1; i &lt; inputContent.Length; i++) {\r\n            if (inputContent&#x5B;i] == '-') {\r\n                if (i + 1 &lt; inputContent.Length) {\r\n                    convertedContent.Append(Char.ToUpperInvariant(inputContent&#x5B;i + 1]));\r\n                    i++;\r\n                }\r\n            } else {\r\n                convertedContent.Append(inputContent&#x5B;i]);\r\n            }\r\n        }\r\n\r\n        return convertedContent.ToString();\r\n    }\r\n}\r\n \r\n<\/pre>\n<p>Mit\u00a0<strong>FromPascalCase<\/strong> wird PascalCaseUrl in pascal-case-url umwandelt, wogegen\u00a0<strong>ToPascalCase<\/strong> die umgekehrte Konvertierung durchf\u00fchrt. Diese beiden Funktionen ben\u00f6tigen wir im folgenden Schritt.<\/p>\n<h3><strong>Schritt #2: Virtuelle Pfade anpassen<\/strong><\/h3>\n<p>Zuerst widmen wir uns den Links, die \u00fcber virtuelle Pfade generiert werden. Dies ist beispielsweise der Fall, wenn wir mit Razor-Hilfsklassen wie @Html.ActionLink() einen Link erzeugen. Dazu erzeugen wir eine neue Klasse &#8211; hier\u00a0<strong>HyphenatedRoute<\/strong> genannt &#8211; die von\u00a0<strong>Route<\/strong> erbt. Dort muss die Methode\u00a0<strong>GetVirtualPath<\/strong> \u00fcberschrieben werden. Diese ist f\u00fcr die Generierung des Linkes zust\u00e4ndig.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing System.Web.Routing;\r\n\r\n\/\/\/&lt;summary&gt;\r\n\/\/\/ Erzeugt eine Route, in denen die W\u00f6rter nicht durch Gro\u00dfbuchstaben (CamelCase), sondern mithilfe von Bindestrichen getrennt werden (z.B. index-seite)\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class HyphenatedRoute : Route {\r\n    public HyphenatedRoute(string url, object defaults) : base(url, new RouteValueDictionary(defaults), new HyphenatedRouteHandler()) { }\r\n    public HyphenatedRoute(string url, RouteValueDictionary defaults) : base(url, defaults, new HyphenatedRouteHandler()) { }\r\n    public HyphenatedRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constrains, IRouteHandler routeHandler) : base(url, defaults, constrains, routeHandler) { }\r\n    public HyphenatedRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constrains, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constrains, dataTokens, routeHandler) { }\r\n\r\n    \/\/\/&lt;summary&gt;\r\n    \/\/\/ Diese Funktion wird von MVC-Hilfsklassen aufgerufen, um AC-Links zu generieren\r\n    \/\/\/ &lt;\/summary&gt;\r\n    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) {\r\n        values&#x5B;&quot;controller&quot;] = HyphenatedConverter.FromPascalCase(values&#x5B;&quot;controller&quot;] as string);\r\n        values&#x5B;&quot;action&quot;] = HyphenatedConverter.FromPascalCase(values&#x5B;&quot;action&quot;] as string);\r\n        return base.GetVirtualPath(requestContext, values);\r\n    }\r\n}\r\n<\/pre>\n<p>Der Konstruktor der Route-Klasse besitzt etliche \u00dcberladungen, die man leider bei Bedarf alle manuell implementiere muss. In diesem Beispiel sind nur die g\u00e4ngigsten und zwei von mir ben\u00f6tigte mit dabei, dies muss gegebenenfalls angepasst werden.<\/p>\n<h3><strong>Schritt 3: R\u00fcckumwandlung bei eingehenden Anfragen<\/strong><\/h3>\n<p>Wie zu Beginn schon angek\u00fcndigt m\u00fcssen wir unser eigenes URL-Format bei eingehenden Anfragen wieder in das urspr\u00fcngliche PascalCase-Format umwandeln, damit die Anfrage korrekt verarbeitet werden kann. Hierzu ist eine Klasse n\u00f6tig, die von\u00a0<strong>MvcRouteHandlerb<\/strong> erbt. Im Prinzip findet hier der umgekehrte Prozess aus dem letzten Schritt statt:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing System.Web;\r\nusing System.Web.Mvc;\r\nusing System.Web.Routing;\r\n\r\n\/\/\/&lt;summary&gt;\r\n\/\/\/ Wandelt durch Bindestriche getrennte Routen intern wieder in ihre PascalCase-Variante um, damit diese vom MVC-Framework korrekt weiterverarbeitet werden k\u00f6nnen\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class HyphenatedRouteHandler : MvcRouteHandler {\r\n\r\n    protected override IHttpHandler GetHttpHandler(RequestContext requestContext) {\r\n        var values = requestContext.RouteData.Values;\r\n\r\n        values&#x5B;&quot;action&quot;] = HyphenatedConverter.ToPascalCase(values&#x5B;&quot;action&quot;].ToString());\r\n        values&#x5B;&quot;controller&quot;] = HyphenatedConverter.ToPascalCase(values&#x5B;&quot;controller&quot;].ToString());\r\n\r\n        return base.GetHttpHandler(requestContext);\r\n    }\r\n}\r\n<\/pre>\n<p>Nun ist unsere Route einsatzbereit und kann in der Routen-Konfiguration verwendet werden. Standardm\u00e4\u00dfig geschieht dies in der Klasse\u00a0<strong>RouteConfig<\/strong>, die sich im Ordner\u00a0<strong>App_Start<\/strong> befindet. Anstelle der Instanz einer\u00a0<strong>Route<\/strong>-Klasse \u00fcbergeben wir der RouteCollection einfach unsere selbst definierte HyphenatedConverter-Klasse. F\u00fcr die standardm\u00e4\u00dfige Action-Controller-Route mit optionaler Id sieht dies beispielsweise wie folgt aus:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing System.Web.Mvc;\r\nusing System.Web.Routing;\r\npublic class RouteConfig {\r\n    public static void RegisterRoutes(RouteCollection routes) {\r\n        var acRoute = new HyphenatedRoute(\r\n            url: &quot;{controller}\/{action}\/{id}&quot;,\r\n            defaults: new { controller = &quot;Home&quot;, action = &quot;Index&quot;, id = UrlParameter.Optional }\r\n        );\r\n        routes.Add(acRoute);\r\n    }\r\n}\r\n<\/pre>\n<p>Da unsere eigene Route mit der Route-Klasse kompatibel ist, muss lediglich die Klassenbezeichnung angepasst werden. Eventuell n\u00f6tige Konstruktoren k\u00f6nnen identisch mit denen der Route-Klasse bei Bedarf eingef\u00fcgt werden, wie bereits oben erw\u00e4hnt. Man muss lediglich darauf achten, als IRouteHandler jeweils den benutzerdefinierten Handler (hier <strong>HyphenatedRouteHandler<\/strong>) zu \u00fcbergeben.<\/p>\n<p><strong>Nat\u00fcrlich kann man dieses Beispiel auch als Grundlage nehmen, um seine komplett eigenen Namenskonventionen f\u00fcr URLs zu entwickeln.<\/strong> Allerdings ist aus verschiedenen Gr\u00fcnden zu empfehlen, hier nicht zu sehr vom g\u00e4ngigen Standard abzuweichen. Beispielsweise sind die Pfade von gesetzten Cookies schreibungsabh\u00e4ngig. Ein Cookie im Pfad <strong>\/myapp<\/strong> ist daher beim Aufruf von <strong>\/MyApp<\/strong> oder auch <strong>myApp<\/strong> nicht verf\u00fcgbar.<\/p>\n<h3><strong>Data Annotiations als Alternative?<\/strong><\/h3>\n<p>ASP.NET bietet mit dem ActionName-Attribute die M\u00f6glichkeit, den Namen der Action in der URL unabh\u00e4ngig von ihrem internen Namen zu verwenden:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&#x5B;ActionName(&quot;register-account&quot;)]\r\npublic ActionResult RegisterAccount() {\r\n    return View(&quot;RegisterAccount&quot;);\r\n}\r\n<\/pre>\n<p>Die Action <strong>RegisterAccount<\/strong> w\u00fcrde somit in der URL als <strong>register-account<\/strong> bezeichnet. Dieses Vorgehen hat jedoch mehrere Nachteile: Das Attribute muss f\u00fcr jede Action manuell gesetzt werden. Au\u00dferdem wird der benutzerdefinierte Name auch f\u00fcr die Ansicht genutzt. Im obigen Beispiel m\u00fcsste diese also <strong>register-account.cshtml<\/strong> hei\u00dfen statt <strong>RegisterAccount.cshtml<\/strong>. Diese Alternative ist daher eher weniger empfehlenswert.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>PascalCase und camelCase sind tolle Namenskonventionen, um beispielsweise in .NET zu programmieren. Diese auch bei URLs zu verwenden wie bei ASP.NET MVC standardm\u00e4\u00dfig der Fall ist allerdings zuviel des Guten: Hier hat sich die Schreibweise in Kleinbuchstaben durchgesetzt. Mehrere W\u00f6rter trennt man zur besseren Lesbarkeit meist mit einem Minus, selten auch mit Unterstrichen. Von Haus &#8230;<\/p>\n","protected":false},"author":5,"featured_media":3659,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61],"tags":[336,413,426],"class_list":["post-3656","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-softwareentwicklung","tag-asp-net","tag-asp-net-mvc","tag-asp-net-mvc6"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/3656","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/comments?post=3656"}],"version-history":[{"count":14,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/3656\/revisions"}],"predecessor-version":[{"id":3672,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/3656\/revisions\/3672"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/3659"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=3656"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=3656"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=3656"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}