mu88
mu88

Reputation: 5384

Ignore controller in ASP.NET Web API

Our team maintains a self-hosted ASP.NET Web API. The project uses attribute routing and we have dozens of existing controllers. Lets say, the API is exposed via the main path /api/purpose1/... with all the existing controllers being placed as resources underneath.

Now I want to introduce a new parallel main path, e. g. /api/purpose2/. It should be possible to activate both main paths independently of each other via a boolean variable in a config file.

Since all the controllers are within one assembly, the attribute routing approach always finds and adds them to both purpose1 and purpose2. This contradicts the independency of purpose1 and purpose2. So I used attribute routing for purpose1 and convention-based routing for purpose2. That at least worked, but I'm not happy with the mixture of two different routing approaches.

So my question is: can I disable certain controller classes with attribute routing?

Upvotes: 2

Views: 1959

Answers (2)

mu88
mu88

Reputation: 5384

Peter Csala's answer is fine, however, it has a dependency to System.Web.Mvc. In our case, this dependency wasn't there before and I found a solution that does not require adding it.

I've extended ApiControllerActionInvoker the following way:

internal class CustomHttpActionInvoker : ApiControllerActionInvoker
{
    public CustomHttpActionInvoker(IConfigProvider configProvider)
    {
        ConfigProvider = configProvider;
        InvokeActionFunc = base.InvokeActionAsync;
    }

    /// <summary>FOR AUTOMATED TESTS ONLY</summary>
    internal CustomHttpActionInvoker(IConfigProvider configProvider,
                                     Func<HttpActionContext, CancellationToken, Task<HttpResponseMessage>> invokeActionFunc)
    {
        ConfigProvider = configProvider;
        InvokeActionFunc = invokeActionFunc;
    }

    private IConfigProvider ConfigProvider { get; }

    private Func<HttpActionContext, CancellationToken, Task<HttpResponseMessage>> InvokeActionFunc { get; }

    /// <inheritdoc />
    public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        var isRelevantRequest = actionContext.ControllerContext.Controller is MyRelevantController;
        if (isRelevantRequest && ConfigProvider.IsPurpose1)
        {
            return InvokeActionFunc(actionContext, cancellationToken);
        }

        if (!isRelevantRequest && ConfigProvider.IsPurpose2)
        {
            return InvokeActionFunc(actionContext, cancellationToken);
        }

        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
    }
}

The internal constructor was introduced to support easier unit testing.

The following code registers the custom class:

var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Services.Replace(typeof(IHttpActionInvoker), new CustomHttpActionInvoker(MyConfigProvider));

Upvotes: 2

Peter Csala
Peter Csala

Reputation: 22789

OnActionExecuting example:

V1 controller

[Route("api/[controller]")]
[ApiController]
public class SampleV1Controller : VersioningAwareControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new OkObjectResult("V1");
    }
}

V2 controller

[Route("api/[controller]")]
[ApiController]
public class SampleV2Controller : VersioningAwareControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new OkObjectResult("V2");
    }
}

Versioning aware base controller

public abstract class VersioningAwareControllerBase: ControllerBase, IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (!FeatureFlags.ShouldDeprecateV1 ||
            !string.Equals(context.RouteData.Values["controller"].ToString(), "samplev1",
                StringComparison.OrdinalIgnoreCase))
            return;

        context.Result = NotFound();
        context.Canceled = true;
    }

    public void OnActionExecuting(ActionExecutingContext context) { }
}

Upvotes: 2

Related Questions