9

I have a ASP.NET Web API (.NET 4) application which has a few controllers. We will run several instances of the Web API application on IIS with one difference. Only certain controllers will be available under certain IIS instances. What I was thinking is to disable/unload the controllers that are not applicable to an instance when the instance starts up.

Anyone got some information that could guide me in the right direction on this?

tugberk
  • 57,477
  • 67
  • 243
  • 335
Carl Clark
  • 291
  • 4
  • 13
  • 1
    The routing affects which controllers are 'available'. At application startup you can use RouteTable.Routes.MapHttpRoute to only map routes to the collers you want exposed by the service. You would want to remove the default routing and explicitly define routes for each of your controllers. – Oppositional Nov 21 '12 at 01:18
  • My solution for this problem (stackoverflow link): http://stackoverflow.com/a/43044667/257470 – andrew.fox Mar 27 '17 at 11:38

3 Answers3

5

You can put your own custom IHttpControllerActivator in by decorating the DefaultHttpControllerActivator. Inside just check for a setting and only create the controller if allowed.

When you return null from the Create method the user will receive 404 Not Found message.

My example shows a value in App Settings (App.Config or Web.Config) being checked but obviously this could any other environment aware condition.

public class YourCustomControllerActivator : IHttpControllerActivator
{
    private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();

    public YourCustomControllerActivator()
    {

    }

    public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor,
                                  Type controllerType)
    {
        if (ConfigurationManager.AppSettings["MySetting"] == "Off")
        {
            //Or get clever and look for attributes on the controller in controllerDescriptor.GetCustomAttributes<>(); 
            //Or use the contoller name controllerDescriptor.ControllerName
            //This example uses the type
            if (controllerType == typeof (MyController) ||
                controllerType == typeof (EtcController))
            {
                return null;
            }
        }
        return _default.Create(request, controllerDescriptor, controllerType);

    }
}   

You can switch your activator in like so:

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());

Update

It has been a while since I looked at this question but if I was to tackle it today I would alter the approach slightly and use a custom IHttpControllerSelector. This is called before the activator and makes for a slightly more efficient place to enable and disable controllers... (although the other approach does work). You should be able to decorate or inherit from DefaultHttpControllerSelector.

JackPoint
  • 4,031
  • 1
  • 30
  • 42
Mark Jones
  • 12,156
  • 2
  • 50
  • 62
  • Thanks for everyone's help on this. Taken me a while to get back on this project. Thanks to @MarkJones your code worked a charm, I used this with a combination of my own DefaultAssembliesResolver to get the desired results. – Carl Clark Feb 18 '13 at 06:17
3

Rather than unloading the controllers, I think I'd create a custom Authorize attribute that looked at the instance information in deciding to grant authorization.

You would add the following to each controller at the class level, or you could also add this to individual controller actions:

[ControllerAuthorize (AuthorizedUserSources = new[] { "IISInstance1","IISInstance2","..." })]

Here's the code for the Attribute:

public class ControllerAuthorize : AuthorizeAttribute
{
    public ControllerAuthorize()
    {
        UnauthorizedAccessMessage = "You do not have the required access to view this content.";
    }

    //Property to allow array instead of single string.
    private string[] _authorizedSources;

    public string UnauthorizedAccessMessage { get; set; }

    public string[] AuthorizedSources
    {
        get { return _authorizedSources ?? new string[0]; }
        set { _authorizedSources = value; }
    }

    // return true if the IIS instance ID matches any of the AllowedSources.
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext == null)
            throw new ArgumentNullException("httpContext");

        //If no sources are supplied then return true, assuming none means any.
        if (!AuthorizedSources.Any())
            return true;

        return AuthorizedSources.Any(ut => ut == httpContext.ApplicationInstance.Request.ServerVariables["INSTANCE_ID"]);
    }
JackPoint
  • 4,031
  • 1
  • 30
  • 42
Heather
  • 2,602
  • 1
  • 24
  • 33
  • 1
    +1 - Clever, would be nice if you could have it read config settings to determine if the controller is authorized as well. – Quintin Robinson Nov 21 '12 at 01:22
  • Because there are multiple instances of the same application, I would highly recommend this be configurable in the web.config file. It doesn't make any sense to try to detect the instance or appPool because Administrators have control over that and they may change it for whatever reason. – Erik Philips Nov 21 '12 at 01:59
0

The IHttpControllerActivator implementation doesn't disable the routes defined using attribute routing , if you want to switch on/off a controller and have a default catch all route controller. Switching off using IHttpControllerActivator disables the controller but when the route is requested it doesn't hit the catch all route controller -it simply tries to hit the controller that was removed and returns no controller registered.

Robert
  • 5,278
  • 43
  • 65
  • 115