Search code examples
asp.net-mvcasp.net-web-api2attributerouting

Attribute routing is failing for MVC/WebApi combo project


I am trying to create an ASP.NET app that is both MVC and Web Api. The default controller (HomeController) returns a view that is composed of some HTML and jQuery. I would like to use the jQuery to call the API that is part of the same project.

I have the API setup and have been testing it with Postman but I get the following error when trying to reach the endpoints in the API.

{
  "Message": "No HTTP resource was found that matches the request URI 'http://localhost:19925/api/encryption/encrypt'.",
  "MessageDetail": "No action was found on the controller 'Values' that matches the request."
}

I am attempting to use attribute routing so I am pretty sure that is where I am going wrong.

    [RoutePrefix("api/encryption")]
    public class ValuesController : ApiController
    {
        [HttpPost]
        [Route("encrypt")]
        public IHttpActionResult EncryptText(string plainText, string keyPassPhrase)
        {
            // Method details here

            return Ok(cipherText);
        }
}

I have the route prefix set to api/encryption. I also have the method using the route encrypt and marked as a HttpPost. Below is my WebApiConfig which I think is configured properly for attribute routing.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        // Default MVC routing
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

By my understanding a POST to the following URL should reach the method ..

http://localhost:19925/api/encryption/encrypt

yet it isn't. I am posting the two string values to the method via Postman. I have attached a screen capture (and yes the keyPassPhrase is fake). enter image description here

Here is the global.asax as requested ...

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

One other thing to note ... that when I change from GET to POST in Postman it works .. as long as I am sending the parameters along in the query string. If I send the parameters in the body I get the original error.


Solution

  • The problem was that I was trying to POST two values to an API method that accepted two parameters. This is not possible with the API (well not without some work arounds) as the API method is expecting an object rather than two different primitive types (i.e. String).

    This means on the server side I needed to create a simple class that held the values I wanted to pass. For example ...

    public class EncryptionPayload
    {
        public string PlainText { get; set; }
        public string PassPhrase { get; set; }
    }
    

    I then modified my API method to accept a type of this class

        [Route("encrypt")]
        [HttpPost]
        public IHttpActionResult EncryptText(EncryptionPayload payload)
        {
          string plainText = payload.PlainText;
          string passPhrase = payload.PassPhrase
    
          // Do encryption stuff here
    
          return Ok(cipherText);
        }
    

    Then inside that controller I pulled the Strings I needed from the EncryptionPayload class instance. On the client side I needed to send my data as a JSON string like this ..

    {"plainText":"this is some plain text","passPhrase":"abcdefghijklmnopqrstuvwxyz"}
    

    After changing these things everything worked in Postman. In the end I wasn't taking into account Model Binding, thinking instead that an API endpoint that accepted POST could accept multiple primitive values.

    This post from Rick Strahl helped me figure it out. This page from Microsoft on Parameter Binding also explains it by saying At most one parameter is allowed to read from the message body.