Using Forms Authentication with ASP.net Web Api

I found myself recently needing to implement a very simple web api private web service into one of my sites to sync some data between servers. I’ve implemented a full blown API key authentication before with web api in past projects. It really isn’t that hard, but why add all of that extra code when my site already fully implements role based forms authentication? It is so easy to just use the authorize attribute with forms authentication.

        // GET api/sync
        [Authorize (Roles = "ApiUser")]
        public IEnumerable<int> Get(int? days)
        {
             //do some work return some data
        }

The problem isn’t really the code above, that will work fine and happily return an “unauthorized” message when you call it. In order to use the HttpClient properly you first need to login posting to the form that contains your login. This could vary depending on how your form is setup, but this works with the default asp.net template.

        private HttpClient CreateAuthenticatedSession()
        {
            var Client = new HttpClient {BaseAddress = new Uri("https://www.yourdomain.com")};
            var Result = Client.PostAsync("/Account/Logon/",
                             new FormUrlEncodedContent(
                                 new Dictionary<string, string>{ {"UserName", "Your Username"}, 
                                 {
                                     "Password","Your Password"
                                 }})).Result;
            Result.EnsureSuccessStatusCode();
            return Client;
        }

The code above once called will hopefully login to your site through forms authentication and return an HttpClient holding the correct cookie that will be passed with subsequent requests. You can keep talking to your Forms Authenticated web service as long as you hold onto the HttpClient returned after you authenticated. Below is a simple code block that calls CreateAuthenticatedSession() and calls the web api service.

        public void GenerateByDay(int days, string path)
        {
            var HttpClientSession = CreateAuthenticatedSession();
            HttpClientSession.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var Result = HttpClientSession.GetAsync("/api/sync/?days=" + days.ToString(CultureInfo.InvariantCulture)).Result;
            Result.EnsureSuccessStatusCode();
            var ResponseBody = Result.Content.ReadAsStringAsync().Result;
            //deserialize result and continue on 
        }
Tagged: , , ,

Fixing Multiple actions were found that match the request Asp.net Web Api Error

WebApiTest Project Here

I’m currently developing a new project utilizing asp.net Web Api 4.5. I hit a frustrating issue today with regards to routing actions. The error is “Multiple actions were found that match the request” when I added a new method to my code bypassing the built in GET,POST,PUT convention that web API supports out of the box.

The default scaffold controller web api gives you has the following pattern out of the box.

    public class TestController : ApiController
    {
        // GET api/test
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
 
        // GET api/test/5
        public string Get(int id)
        {
            return "value";
        }
 
        // POST api/test
        public void Post([FromBody]string value)
        {
        }
 
        // PUT api/test/5
        public void Put(int id, [FromBody]string value)
        {
        }
 
        // DELETE api/test/5
        public void Delete(int id)
        {
        }
    }

Problems start to appear when you want to go beyond this convention such as another “GET” method that goes by a different name.

    public class TestController : ApiController
    {
        // GET api/test
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
 
        // GET api/test/GetAllWithFilter
        [HttpGet]
        public IEnumerable<string> GetAllWithFilter()
        {
            return new string[] { "value1"};
        }
 
        // GET api/test/5
        public string Get(int id)
        {
            return "value";
        }
 
        // POST api/test
        public void Post([FromBody]string value)
        {
        }
 
        // PUT api/test/5
        public void Put(int id, [FromBody]string value)
        {
        }
 
        // DELETE api/test/5
        public void Delete(int id)
        {
        }
    }

This shouldn’t be that hard to fix on the surface but the built in route that enables this convention actually makes this much more difficult that it should be. I searched around for a while to figure out how to solve this then finally ran across a great Stackoverflow post here that nails it. Sadly this post isn’t even marked as the answer, it should be.

In short replace your built in route in WebApiConfig with this (updated for latest release). Be sure to modify your api path to suit your application.

            config.Routes.MapHttpRoute("DefaultApiWithId", "api/v1/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
            config.Routes.MapHttpRoute("DefaultApiWithAction", "api/v1/{controller}/{action}");
            config.Routes.MapHttpRoute("DefaultApiGet", "api/v1/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
            config.Routes.MapHttpRoute("DefaultApiPost", "api/v1/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
            config.Routes.MapHttpRoute("DefaultApiPut", "api/v1/{controller}", new { action = "Put" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Put) });
            config.Routes.MapHttpRoute("DefaultApiDelete", "api/v1/{controller}", new { action = "Delete" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Delete) });

This honors the GET,POST,PUT,DELETE convention while allowing you to create differently named actions in the same controller!

Update – Sample Project

As requested here is a sample project that I have verified works.

WebApiTest Project Here

Tagged: , , , ,