ASP MVC AttributeRoutes Languagetoken in URL, keep original Url - asp.net-mvc-5

I'm using attribute routing with asp mvc 5.2.
I want the user to be able to call the urls with a language token like "de" or "en".
mydomain/en/foo
For this I created a routeattribute with a constraint like descriped in this blog post
This works well when the locale part is at the end like:
[LocaleRoute("home/index/{locale}/","de)]
then i could call home/index or home/index/de
But when I move this to the beginning i can't omit the locale any more.
[LocaleRoute("{locale}/home/index","de)]
There i can only call de/home/index but home/index returns a 404 Notfound. Also the constraint isn't called. It looks like the route isn't just found.
Any hints on what i'm doing wrong and what i have to do?

You probably need to specify that the locale is an optional argument :
[LocaleRoute("{locale?}/home/index","de")]
On a side note, are you using different controllers for de & en ? If you use a single controller you don't need LocaleRoute and can use the default attributes (http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx) :
[RouteArea("{locale?}/home/")]
public class HomeController : Controller
{
...
[Route("index")]
public ActionResult Index (string culture)
{
...
}
}
EDIT
You can also register your action with the following additional route as long as you're careful with providing a default value for culture or treating null.
[Route("/home/index")]

Related

Kentico MVC culture two letter code in URL pattern

I'm trying to create a more friendly page URL in Kentico with the two letter culture code (en) instead of the full culture code (en-US). I modified the page URL pattern as seen in the image below. Is there a better way of doing this, should I create a custom macro expression?
The other thing I wanted to achieve was having the default culture not in the URL. Maybe I could also use the custom macro expression for it.
I have solved the problem with a custom macro expression. This is used in the URL pattern in combination with the DocumentCulture. The macro expression takes the culture alias name which is in case of the default culture empty and else the two letter code, and returns this with a '/'.
The url pattern for a page:
{%Util.GetUrlCultureCode(DocumentCulture)%}{%NodeAliasPath%}
The macro expression I wrote for fixing this problem is as below.
public class CustomCultureMacroExpression : MacroMethodContainer
{
[MacroMethod(typeof(List<string>), "Returns the url culture code of current document", 1)]
[MacroMethodParam(0, "Culture", typeof(string), "Current culture.")]
public static object GetUrlCultureCode(EvaluationContext context, params object[] parameters)
{
// Branches according to the number of the method's parameters
switch (parameters.Length)
{
case 1:
return GetCultureAliasOfCulture(parameters[0].ToString());
case 2:
// Weird bug causing macro expression in url pattern to have two parameters where the first parameter is null.
return GetCultureAliasOfCulture(parameters[1].ToString());
default:
// No other overloads are supported
return string.Empty;
}
}
private static string GetCultureAliasOfCulture(string cultureCode)
{
var culture = CultureInfoProvider.GetCultureInfo(cultureCode);
var culturealias = culture?.CultureAlias ?? string.Empty;
return string.IsNullOrEmpty(culturealias) ? string.Empty : $"/{culturealias}";
}
}
You may try to update culture codes in Localization -> Cultures, however I'm not sure if it won't have any side effect
If you use the Dynamic Routing module, you can hook into the GetCulture and GetPage global events to do what you are looking for.
For the GetCulture, you would check if the URL has a 2 letter culture code, if it does then use that as the culture.
You can also adjust the GetPage if needed, although the URL slugs generated should work properly, unless you want a fall back that on the GetPage.After, if there is no found page, you can try removing any culture from the URL and looking up the page by that.
I recommended using the NodeAliasPath for the remaining part of the UrlPattern for ease of use.

How to get all URLs in a Symfony 4 project to contain the _locale

Am trying to create a language switcher for a Symfony 4 project (with Twig). Basically when clicking a link, the website's language will change.
Configuration I have is this:
config\packages\translation.yaml
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
This is the important lines I got in
config\packages\services.yaml:
parameters:
locale: 'en'
# This parameter defines the codes of the locales (languages) enabled in the application
app_locales: en|fr
My Controller:
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraints\Json;
// Class name really should match Controller name
class ControllerBase extends AbstractController
{
/**
* #Route("/{_locale}", name="app_landing", defaults={"_locale" = "en"}, requirements={"_locale" = "en|fr"})
*/
public function index(Request $request)
{
$locale = $request->getLocale();
return $this->render('landing/index.html.twig');
}
}
The actual language switcher is just two list elements contained in the template file. They look like this:
<li>English</li>
<li>French</li>
This works fine.
Problem arises when clicking another link on the website. It doesn't contain the _locale and the site defaults back to en.
I really would like the _locale to be added to each link automatically (including the index path "/").
After a while of experimenting, I found the answer.
I tried to do the same thing in two different ways. In my case it was best to simply use the code in the template file and omit the {_locale} parameter in the controller.
Like that all works fine.

MvcSiteMapProvider how to make a node match the current url

The current node is coming up null. I can't figure out how to make MvcSiteMapProvider resolve the node under this circumstance.
Here's the node it needs to match
<mvcSiteMapNode title="Policy" route="Details" typeName="Biz.ABC.ShampooMax.Policy" preservedRouteParameters="id" />
Here's the route:
routes.MapRoute(
"Details",
"Details/{id}",
new { Controller = "Object", Action = "Details" }
).RouteHandler = new ObjectRouteHandler();
The link that gets clicked:
http://localhost:36695/MyGreatWebSite/Details/121534455762071
It's hitting the route ok. Just the MvcSiteMapProvider.SiteMaps.Current.CurrentNode is null.
A null result for CurrentNode indicates the incoming request doesn't match any node in the SiteMap In your case there are 4 different problems that may be contributing to this:
The URL you are inputting http://localhost:36695/MyGreatWebSite/Details/121534455762071 does not (necessarily) match the URL pattern you specified in the route, "Details/{id}". It may if your site is hosted as an IIS application under IISROOT/MyGreatWebSite/.
Your mvcSiteMapNode doesn't specify a controller or action to match. route only acts like an additional piece of criteria (meaning only the named route will be considered in the match), but all of the parameters also need to be provided in order for there to be a match with the route.
You are passing in a custom RouteHandler, which could alter how the route matches the URL. Without seeing the code from your ObjectRouteHandler, it is impossible to tell if or how that will affect how the route matches the URL.
You have a custom attribute, typeName in your mvcSiteMapNode configuration. Unless you have specified to ignore this attribute, it will also be required in the URL to match, i.e. http://localhost:36695/MyGreatWebSite/Details/121534455762071?typeName=Biz.ABC.ShampooMax.Policy.
I recommend against using a custom RouteHandler for the purpose of matching URLs. The effect of doing so makes your incoming routes (URLs into MVC) act differently than your outgoing routes (URLs generated to link to other pages). Since MvcSiteMapProvider uses both parts of the route, it will cause URL generation problems if you only change the incoming routes without also changing the outgoing routes to match. Instead, I recommend you subclass RouteBase, where you can control both sides of the route. See this answer for an example of a custom RouteBase subclass.
However, do note that conventional routing is pretty powerful out of the box and you probably don't need to subclass RouteBase for this simple scenario.
Solution
I arrived at the answer by adding the mvc sitemap provider project into my own solution and stepping through the mvc sitemap provider code to see why my node wasn't being matched. A few things had to be changed. I fixed it by doing the following:
Mvc.sitemap
<mvcSiteMapNode title="Policy" controller="Object" action="Details" typeName="Biz.ABC.ShampootMax.Policy" preservedRouteParameters="id" roles="*"/>
RouteConfig.cs
routes.MapRoute(
name: "Details",
url: "details/{id}",
defaults: new { controller = "Object", action = "Details", typeName = "*" }
).RouteHandler = new ObjectRouteHandler();
Now at first it didn't want to work like this, but I modified the provider like so:
RouteValueDictionary.cs (added wildcard to match value)
protected virtual bool MatchesValue(string key, object value)
{
return this[key].ToString().Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) || value.ToString() == "*";
}
SiteMapNode.cs (changed requestContext.RouteData.Values)
/// <summary>
/// Sets the preserved route parameters of the current request to the routeValues collection.
/// </summary>
/// <remarks>
/// This method relies on the fact that the route value collection is request cached. The
/// values written are for the current request only, after which they will be discarded.
/// </remarks>
protected virtual void PreserveRouteParameters()
{
if (this.PreservedRouteParameters.Count > 0)
{
var requestContext = this.mvcContextFactory.CreateRequestContext();
var routeDataValues = requestContext.HttpContext.Request.RequestContext.RouteData.Values;// requestContext.RouteData.Values;
I think the second modification wasn't strictly necessary because my request context wasn't cached; it would have worked if it was. I didn't know how to get it cached.
It's the first modification to make route values honor a wildcard (*) that made it work. It seems like a hack and maybe there's a built in way.
Note
Ignoring the typeName attribute with:
web.config
<add key="MvcSiteMapProvider_AttributesToIgnore" value="typeName" />
makes another node break:
Mvc.sitemap
<mvcSiteMapNode title="Policies" url="~/Home/Products/HarvestMAX/Policy/List" productType="HarvestMax" type="P" typeName="AACOBusinessModel.AACO.HarvestMax.Policy" roles="*">
so that's why I didn't do that.

Is it possible to use T4MVC with Attribute routing (RoutePrefix/Route)?

Are attribute-based routes supported by T4MVC in some way?
I have applied a RoutePrefixAttribute to my MVC 5 controller and a Route attribute on my action. T4MVC, as it stands, does not seem to provide routes based on these attributes. The route that it provides is the convention-based routes of /Area/Controller/Action.
Folder structure is:
/
Areas
Ratio
Controllers
RatioSet
PresetGroupController.cs
Views
RatioSet
GroupDetails.cshtml
Controller:
[RoutePrefix("Ratio/RatioSet/Preset/Group")]
public partial class PresetGroupController
{
[Route("Details")]
public virtual ActionResult Details()
{
//.....
return View(MVC.Ratio.RatioSet.Views.GroupDetails, model);
}
}
Now, if I try the following:
return RedirectToAction(MVC.Ratio.PresetGroup.Details());
I get a 404 error, because the requested URL is:
<app_root>/Ratio/PresetGroup/Details
which is the "default" route, rather than the correct attribute-specified:
<app_root>/Ratio/RatioSet/Preset/Group/Details
So, does T4MVC only work with the convention-based routes, inferred from the folder structure, and not any routes that are specified via attributes?
I know this is an old question, but I had the same problem and ended up fixing it adding the RouteArea attribute to the Controller.
Something like this:
[RouteArea("Ratio")]
[RoutePrefix("Ratio/RatioSet/Preset/Group")]
public partial class PresetGroupController
{
[Route("Details")]
public virtual ActionResult Details()
{
//.....
return View(MVC.Ratio.RatioSet.Views.GroupDetails, model);
}
}
Have you verified that the non-T4MVC equivalent is working? If so, what does that line look like?
Note that T4MVC really doesn't generate routes itself, but instead calls into standard MVC framework methods to do this. See section 1.1 in the docs.
My guess is that you're running into an issue that is unrelated to T4MVC. e.g. see this issue where the problem is the order of registration calls.

IIS gives 404 when MS-DOS reserved names are used in parameters values

I have an ASP.NET Web API application and I have an action that takes one string parameter, such as:
[HttpGet]
public HttpResponseMessage GetById(string id)
{
// ...
}
It is registered accordingly:
config.Routes.MapHttpRoute("Resources", "api/resources/{id}", new
{
controller = "Resources",
action = "GetById"
});
Now, when someone calls api/resources/con, IIS gives 404. Almost any other value is OK, like api/resources/something or api/resources/nothing.
We have looked over and found that none of the reserved MS-DOS Device Driver Names can be used as a parameter value.
This appears to be a global issue, since MSDN has it too:
http://msdn.microsoft.com/con
http://msdn.microsoft.com/prn
http://msdn.microsoft.com/nul
Is there any way to allow these names to be used as route parameters values?
Add this in Web.config, under system.web.
<httpRuntime relaxedUrlToFileSystemMapping="true" />

Resources