0

I've set breakpoints, and followed the flow. The controller is being instantiated, and the parameters are being validated (I have some Data Annotations set up), but the controller method itself isn't being invoked.

I think the problem is routing, but I'm not sure where it might be broken. I haven't set up any global routing; everything is done via attributes. The Swagger docs are being generating, too, and documenting the controller endpoints.

In fact, everything seems to work OK on the surface. Send a request, and I'll get back a 200 OK. Send a request that fails the declarative validations, and I'll get back a BadRequest.

Here's the controller:

[Route("licensing")]
public class LicenseController
    : Controller
{
    [HttpPut]
    [Route("create-license/{licenseKey}")]
    public async Task<LicenseDetails> CreateLicenseAsync(string licenseKey, [FromBody]CreateLicenseRequest license)
    {
        DoSomething(licenseKey);  //A breakpoint here will never be hit
    }
}

A request looks like this:

{
  "Username": "test@example.com",
  "LicenseKey": "license-key-string",
  "ProductName": "SomeProduct",
  "ProductVersion": "2017.5",
  "ActivationLimit": 2147483647,
  "UtcExpiration": "9998-12-31T23:59:59.9999999"
}

The PUT would be against a URL like this:

https://api.example.com/licensing/create-license/license-key-string

I use Basic Authentication in the headers, which exercises a basic authentication filter I wrote, and that all works fine. (I stepped through; in the event of a failure, a NotAuthorized is returned.)

Here's most of the composition root:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("hosting.json", optional: false, reloadOnChange: true)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
    Configuration = builder.Build();
}

public void ConfigureServices()
{
    //...non-ASP.NET stuff snipped
    services.AddMvc(options =>
    {
        options.Filters.Add(new ValidateModelStateAttribute());
        options.Filters.Add(new BasicAuthenticationAttribute(authorizationManager));
        options.Filters.Add(new ApiExceptionFilter());
        options.OutputFormatters.Clear();
        options.OutputFormatters.Add(new JsonOutputFormatter(jsonSerializerSettings, ArrayPool<char>.Shared));
    })
    .AddJsonOptions(setupAction: options =>
    {
        //...
    });

    services.AddSingleton<IControllerActivator>(new LicenseKeyControllerActivator(keyService));
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "Licensing API", Version = "v1" });
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseMvc();
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Licensing API v1");
    });
}
rianjs
  • 7,767
  • 5
  • 24
  • 40
  • 1
    Can we see an example request? – Claudio Redi Apr 18 '17 at 01:10
  • Sure, I added an example PUT request. – rianjs Apr 18 '17 at 01:30
  • What if you remove `HttpPut`? Are you able to access that url with a `GET` request? – Claudio Redi Apr 18 '17 at 01:37
  • No, that endpoint is one way. (A better REST design might be a `PUT` to `licensing/license/` creates the license, and a `GET` returns it, but that's not related to my problem.) – rianjs Apr 18 '17 at 01:41
  • @rianjs you indicated that the controller action is not being invoked. what response are you getting when you make the request. – Nkosi Apr 18 '17 at 01:41
  • `200 OK`, as I mentioned above. It's almost like it's running all the global filters, and then returning without having done any work. In fact, I'm pretty sure that's exactly what's happening, but I can't seem to find the mismatch that's breaking the routing. It happens with or without SSL, on both Windows and Linux. – rianjs Apr 18 '17 at 01:43
  • If you are creating the license wouldn't HTTPPost be better since Put is update call – mvermef Apr 18 '17 at 01:45
  • @rianjs Show startup configuration. specifically if any other routes are configured. Also check your other controllers to make sure that they do not have routes that would intercept the desired url – Nkosi Apr 18 '17 at 01:45
  • Sure, I added the composition root. – rianjs Apr 18 '17 at 01:51
  • @rianjs so far everything looks in order. to troubleshoot try this. change the route template. like for example `[Route("{licenseKey}")]` and try `PUT https://api.example.com/licensing/license-key-string` and see if it hits the action. – Nkosi Apr 18 '17 at 02:06
  • I know, right? It's maddening. I'll try changing the path tomorrow. – rianjs Apr 18 '17 at 02:12
  • @rianjs: I suggested you to remove `HttpPut` just for testing, to see if something wider is going on. – Claudio Redi Apr 18 '17 at 11:36
  • Changing the route template didn't work. BUT, I think it might be a problem with my controller activator. I set some breakpoints on some `set`s in my `CreateLicenseRequest`. The controller is instantiated, the DTO is populated, the `DataAnnotations` validations are run, and then the controller activator's `Release` method is called. That doesn't seem right. – rianjs Apr 18 '17 at 12:25
  • No, I don't think that's it, either. Yeah, I have no idea WTF is going on. – rianjs Apr 18 '17 at 13:15
  • Disabling my `BasicAuthenticationAttribute` fixed everything. So the bug is in there somewhere... – rianjs Apr 18 '17 at 13:50

0 Answers0