Creating Routes with an Optional Path Prefix

Creating routes with an optional path prefix

OK, I've managed to sort out this problem:

THere is no way of doing this in Rails by default (at least, not yet). Instead of using namespaces and default values, I needed to install Sven Fuchs' routing filter.

Once the plugin is installed, I added the following file to my lib directory:

require 'routing_filter/base'

module RoutingFilter
class Locale < Base

# remove the locale from the beginning of the path, pass the path
# to the given block and set it to the resulting params hash
def around_recognize(path, env, &block)
locale = nil
path.sub! %r(^/([a-zA-Z]{2})(?=/|$)) do locale = $1; '' end
returning yield do |params|
params[:locale] = locale || 'en'
end
end

def around_generate(*args, &block)
locale = args.extract_options!.delete(:locale) || 'en'
returning yield do |result|
if locale != 'en'
result.sub!(%r(^(http.?://[^/]*)?(.*))){ "#{$1}/#{locale}#{$2}" }
end
end
end

end
end

I added this line to routes.rb:

map.filter 'locale'

This basically fills out a before and after hook, generated by the plugin, that wraps the rails routing.

When a url is recognised, and before Rails gets to do anything with it, the around_recognize method is called. This will extract a two-letter code representing the locale, and pass it through in the params, defaulting to 'en' if no locale is specified.

Likewise, when a url is generated, the locale parameter will be pushed into the URL on the left side.

This gives me the following urls and mappings:

/   => :locale => 'en'
/en => :locale => 'en'
/fr => :locale => 'fr'

All existing url helpers work as before, with the only difference being that unless the locale is specified, it is preserved:

home_path                  => /
home_path(:locale => 'en') => /
home_path(:locale => 'fr') => /fr

React Router with optional path parameter

The edit you posted was valid for an older version of React-router (v0.13) and doesn't work anymore.



React Router v1, v2 and v3

Since version 1.0.0 you define optional parameters with:

<Route path="to/page(/:pathParam)" component={MyPage} />

and for multiple optional parameters:

<Route path="to/page(/:pathParam1)(/:pathParam2)" component={MyPage} />

You use parenthesis ( ) to wrap the optional parts of route, including the leading slash (/). Check out the Route Matching Guide page of the official documentation.

Note: The :paramName parameter matches a URL segment up to the next /, ?, or #. For more about paths and params specifically, read more here.



React Router v4 and above

React Router v4 is fundamentally different than v1-v3, and optional path parameters aren't explicitly defined in the official documentation either.

Instead, you are instructed to define a path parameter that path-to-regexp understands. This allows for much greater flexibility in defining your paths, such as repeating patterns, wildcards, etc. So to define a parameter as optional you add a trailing question-mark (?).

As such, to define an optional parameter, you do:

<Route path="/to/page/:pathParam?" component={MyPage} />

and for multiple optional parameters:

<Route path="/to/page/:pathParam1?/:pathParam2?" component={MyPage} />

Note: React Router v4 is incompatible with react-router-relay (read more here). Use version v3 or earlier (v2 recommended) instead.

Is there a way to have a RoutePrefix that starts with an optional parameter?

I am a bit late to the party, but i have a working solution for this problem. Please find my detailed blog post on this issue here

I am writing down summary below

You need to create 2 files as given below

  • _3bTechTalkMultiplePrefixDirectRouteProvider.cs


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Web.Http.Controllers;
using System.Web.Http.Routing;

namespace _3bTechTalk.MultipleRoutePrefixAttributes {
public class _3bTechTalkMultiplePrefixDirectRouteProvider: DefaultDirectRouteProvider {
protected override IReadOnlyList GetActionDirectRoutes(HttpActionDescriptor actionDescriptor, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) {
return CreateRouteEntries(GetRoutePrefixes(actionDescriptor.ControllerDescriptor), factories, new [] {
actionDescriptor
}, constraintResolver, true);
}

protected override IReadOnlyList GetControllerDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList actionDescriptors, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) {
return CreateRouteEntries(GetRoutePrefixes(controllerDescriptor), factories, actionDescriptors, constraintResolver, false);
}

private IEnumerable GetRoutePrefixes(HttpControllerDescriptor controllerDescriptor) {
Collection attributes = controllerDescriptor.GetCustomAttributes (false);
if (attributes == null)
return new string[] {
null
};

var prefixes = new List ();
foreach(var attribute in attributes) {
if (attribute == null)
continue;

string prefix = attribute.Prefix;
if (prefix == null)
throw new InvalidOperationException("Prefix can not be null. Controller: " + controllerDescriptor.ControllerType.FullName);
if (prefix.EndsWith("/", StringComparison.Ordinal))
throw new InvalidOperationException("Invalid prefix" + prefix + " in " + controllerDescriptor.ControllerName);

prefixes.Add(prefix);
}

if (prefixes.Count == 0)
prefixes.Add(null);

return prefixes;
}


private IReadOnlyList CreateRouteEntries(IEnumerable prefixes, IReadOnlyCollection factories, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
var entries = new List ();

foreach(var prefix in prefixes) {
foreach(IDirectRouteFactory factory in factories) {
RouteEntry entry = CreateRouteEntry(prefix, factory, actions, constraintResolver, targetIsAction);
entries.Add(entry);
}
}

return entries;
}


private static RouteEntry CreateRouteEntry(string prefix, IDirectRouteFactory factory, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
DirectRouteFactoryContext context = new DirectRouteFactoryContext(prefix, actions, constraintResolver, targetIsAction);
RouteEntry entry = factory.CreateRoute(context);
ValidateRouteEntry(entry);

return entry;
}


private static void ValidateRouteEntry(RouteEntry routeEntry) {
if (routeEntry == null)
throw new ArgumentNullException("routeEntry");

var route = routeEntry.Route;
if (route.Handler != null)
throw new InvalidOperationException("Direct route handler is not supported");
}
}
}

  • 3bTechTalkRoutePrefix.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;

namespace _3bTechTalk.MultipleRoutePrefixAttributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class _3bTechTalkRoutePrefix : RoutePrefixAttribute
{
public int Order { get; set; }

public _3bTechTalkRoutePrefix(string prefix) : this(prefix, 0) { }

public _3bTechTalkRoutePrefix(string prefix, int order) : base(prefix)
{
Order = order;
}
}
}

Once done, open WebApiConfig.cs and add this below given line


config.MapHttpAttributeRoutes(new _3bTechTalkMultiplePrefixDirectRouteProvider());

That's it, now you can add multiple route prefix in your controller. Example below



[_3bTechTalkRoutePrefix("api/Car", Order = 1)]
[_3bTechTalkRoutePrefix("{CountryCode}/api/Car", Order = 2)]
public class CarController: ApiController {
[Route("Get")]
public IHttpActionResult Get() {
return Ok(new {
Id = 1, Name = "Honda Accord"
});
}
}

I have uploaded a working solution here

Happy Coding :)



Related Topics



Leave a reply



Submit