Question

I am currently working on an expandable WebApi where individual dynamically loaded modules can register their own routes. My original version was built using ASP.NET WebApi v1 using a technique based on https://github.com/darrelmiller/ApiRouter however I would like to use the Attribute based routing that is available with ASP.NET WebApi v2

So say I have a module with the following Controller

[RoutePrefix("foo")]
public class FooController : ApiController {

    [Route("{id}")]
    public object GetFoo(int id) {
        return id;
    }
}

If I just call config.MapHttpAttributeRoutes() then that controller would be bound to http://localhost/foo/2. What I would like to do is programmatically change the route prefix for the controllers. So it should hopefully be bound to something like http://localhost/modules/FooModule/foo/2

Is there a way to get this done with the provided tools, or would I have to keep using my original method?

Was it helpful?

Solution

If I am not wrong, you basically are looking for a hook where you can examine the end route-prefix or route-template generated by web api's attribute routing probe mechanism and modify them before they get added to the route table. This way you can plug-in 3rd party modules having attributed controllers and modify their route templates to stop them from messing with other modules or your own application's routes...is that right?

Web API provides types like IRoutePrefix and RoutePrefixAttribute (which implements IRoutePrefix) to enable creating custom RoutePrefixes within which you can change the route prefix. But from the scenario you are looking for i think this cannot be of much help.

Unfortunately, I cannot recommend a good way in Web API to do this. This kind of scenario came into picture when designing but due to time constraint we weren't able to provide the necessary hooks.

Update:
Based on your comment, following is an example where I create a custom route attribute and modify the template by adding a module name to the route template before its added to the route table. As mentioned in my comment, you could follow this approach if you own all the modules as you can use this custom route attribute. If you expect 3rd party modules, then you have to come to an agreement with them so that they also use the same attribute. But that is error prone, like in scenarios where they do not use this custom attribute but the default RouteAttribute...

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public class MyRouteAttribute : Attribute, IDirectRouteFactory
{
    private readonly string _template;

    public MyRouteAttribute(string template)
    {
        _template = template;
    }

    public string Template
    {
        get { return _template; }
    }

    public string Name { get; set; }

    public int Order { get; set; }

    public virtual IDictionary<string, object> Defaults
    {
        get { return null; }
    }

    public virtual IDictionary<string, object> Constraints
    {
        get { return null; }
    }

    public virtual IDictionary<string, object> DataTokens
    {
        get { return null; }
    }

    public RouteEntry CreateRoute(DirectRouteFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        IDirectRouteBuilder builder = context.CreateBuilder(Template);

        //You would receive the final template..that is RoutePrefix + the route template
        string finalTemplate = builder.Template;

        //Modify this template to have your ModuleName
        builder.Template = "modulename/" + finalTemplate;

        builder.Name = Name;
        builder.Order = Order;

        IDictionary<string, object> builderDefaults = builder.Defaults;

        if (builderDefaults == null)
        {
            builder.Defaults = Defaults;
        }
        else
        {
            IDictionary<string, object> defaults = Defaults;

            if (defaults != null)
            {
                foreach (KeyValuePair<string, object> defaultItem in defaults)
                {
                    builderDefaults[defaultItem.Key] = defaultItem.Value;
                }
            }
        }

        IDictionary<string, object> builderConstraints = builder.Constraints;

        if (builderConstraints == null)
        {
            builder.Constraints = Constraints;
        }
        else
        {
            IDictionary<string, object> constraints = Constraints;

            if (constraints != null)
            {
                foreach (KeyValuePair<string, object> constraint in constraints)
                {
                    builderConstraints[constraint.Key] = constraint.Value;
                }
            }
        }

        IDictionary<string, object> builderDataTokens = builder.DataTokens;

        if (builderDataTokens == null)
        {
            builder.DataTokens = DataTokens;
        }
        else
        {
            IDictionary<string, object> dataTokens = DataTokens;

            if (dataTokens != null)
            {
                foreach (KeyValuePair<string, object> dataToken in dataTokens)
                {
                    builderDataTokens[dataToken.Key] = dataToken.Value;
                }
            }
        }

        return builder.Build();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top