Complex Type Action Parameters with ComplexTypeAwareActionSelector in ASP.NET Web API - Part 1

We will see how to make complex type action parameters play nice with controller action selection in ASP.NET Web API by using ComplexTypeAwareActionSelector from WebAPIDoodle NuGet package.
2012-10-07 13:56
Tugberk Ugurlu


Couple of days ago, I wrote a blog post on complex type action parameters and controller action selection with ASP.NET Web API with a solution that I’ve come up with and I encourage you to check that blog post out to get a sense of what this is really about. However, that solution was sort of noisy. Yesterday, I tweaked the ComplexTypeAwareActionSelector implementation a bit and now it directly supports the complex type action parameters without any additional attributes for the action methods. In this post, we will see how we can use it and in the next post, we will see how it works under the covers.

First of all, install the latest WebAPIDoodle package rom the Official NuGet feed:

PM> Install-Package WebAPIDoodle

Let’s go through the scenario briefly. Assume that we have the following controller with two action methods that are expected to serve for different GET requests:

public class CarsByCategoryRequestCommand {

    public int CategoryId { get; set; }
    public int Page { get; set; }

    [Range(1, 50)]
    public int Take { get; set; }
}

public class CarsByColorRequestCommand {

    public int ColorId { get; set; }
    public int Page { get; set; }

    [Range(1, 50)]
    public int Take { get; set; }
}

[InvalidModelStateFilter]
public class CarsController : ApiController {

    public string[] GetCarsByCategoryId(
        [FromUri]CarsByCategoryRequestCommand cmd) {

        return new[] { 
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }

    public string[] GetCarsByColorId(
        [FromUri]CarsByColorRequestCommand cmd) {

        return new[] { 
            "Car 1",
            "Car 2"
        };
    }
}

If we now send a GET request to /api/cars?colorId=23&page=2&take=12 with the default action selector registered, we would get the ambiguity error message because the default action selector doesn’t consider the complex type action parameters while performing the action selection.

Let’s replace the default action selector with our ComplexTypeAwareActionSelector as below. Note that ComplexTypeAwareActionSelector preserves all the features of the ApiControllerActionSelector.

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    config.Routes.MapHttpRoute(
        "DefaultApiRoute",
        "api/{controller}/{id}",
        new { id = RouteParameter.Optional }
    );

    // Replace the default action IHttpActionSelector with
    // WebAPIDoodle.Controllers.ComplexTypeAwareActionSelector
    config.Services.Replace(
        typeof(IHttpActionSelector),
        new ComplexTypeAwareActionSelector());
}

As explained inside the previous related post, we previously had to mark the action methods with UriParametersAttribute to give a hint about the action parameters we want to support. However, with the current implementation of the the ComplexTypeAwareActionSelector, it just works as it is. Only thing required to perform is to mark the complex action parameter with FromUriAttribute. By marking the complex type parameters with FromUriAttribute, you are making it possible to bind the route and query string values.

After replacing the default action selector with our own implementation, we will see it working if we send a GET request to /api/cars?colorId=23&page=2&take=12.

image

Now, let’s send a request to /api/cars?categoryId=23&page=2&take=12 and see what we will get back:

image

Working perfectly as expected. The ComplextTypeAwareActionSelector considers simple types inside a complex type parameter which are all primitive .NET types, System.String, System.DateTime, System.Decimal, System.Guid, System.DateTimeOffset, System.TimeSpan and underlying simple types (e.g: Nullable<System.Int32>).

In the next post, we will see how ComplextTypeAwareActionSelector works and behaves with complex type action parameters. Stay tuned! ;)



Comments

Ganapati
by Ganapati on Saturday, Dec 15 2012 14:46:28 +02:00

Hi, 

this post is very useful for me as well as all other who are newly  working wen API ASP.Net.

but i have some questions here.

For example,

Case 1:  i want to search users based on Name 

Case 2: i want to search users based on Name and Date of birth 

I tried to work using this post but it alwase calls get() method.

Please give some suggestions about my problem...

 

Tugberk
by Tugberk on Monday, Dec 17 2012 17:01:25 +02:00

@Ganapati

Are u sure? The below one for example works for me:

 

    public class User {

        public int Id { get; set; }

        public string Name { get; set; }

        public string Surname { get; set; }

        public DateTime Bithdate { get; set; }

    }

 

    public class UsersByNameRequestCommand {

        public string Name { get; set; }

    }

    public class UsersByNameBirthDateRequestCommand : UsersByNameRequestCommand {

        public DateTime Birthdate { get; set; }

    }

    public class UsersController : ApiController {

        List<User> _users = new List<User> { 

            new User { Id = 1, Name = "Foo", Surname = "Bar", Bithdate = DateTime.Parse("1987-12-07") },

            new User { Id = 1, Name = "Foo", Surname = "BarFoo", Bithdate = DateTime.Parse("1989-01-07") },

            new User { Id = 2, Name = "Bar", Surname = "Foo", Bithdate = DateTime.Parse("1978-02-09") },

            new User { Id = 2, Name = "FooBar", Surname = "BarFoo", Bithdate = DateTime.Parse("1988-06-09") }

        };

        public IEnumerable<User> GetUsers() {

            return _users;

        }

        public IEnumerable<User> GetUsersByName([FromUri]UsersByNameRequestCommand cmd) {

            return _users.Where(x => x.Name.Equals(

                cmd.Name, 

                StringComparison.InvariantCultureIgnoreCase));

        }

 

        public IEnumerable<User> GetUsersByNameAndBirthData([FromUri]UsersByNameBirthDateRequestCommand cmd) {

            return _users.Where(x => x.Name.Equals(

                cmd.Name,

                StringComparison.InvariantCultureIgnoreCase) 

                && x.Bithdate.Equals(cmd.Birthdate));

        }

    }

With the following configuration:

 

    protected void Application_Start(object sender, EventArgs e) {

        var config = GlobalConfiguration.Configuration;

        config.Routes.MapHttpRoute(

                "DefaultApiRoute",

                "api/{controller}/{id}",

                new { id = RouteParameter.Optional }

            );

        // Replace the default action IHttpActionSelector with

        // WebAPIDoodle.Controllers.ComplexTypeAwareActionSelector

        config.Services.Replace(

            typeof(IHttpActionSelector),

            new ComplexTypeAwareActionSelector());

    }

 

I can send requests against the below resources and hit those three different actions:

  • http://localhost:32893/api/users
  • http://localhost:32893/api/users?name=foo
  • http://localhost:32893/api/users?name=foo&birthdate=1987-12-07

You must be missing something. If you cannot solve the issue, repro it and upload the code somewhere so that I can have a look.

 

Herb Stahl
by Herb Stahl on Tuesday, Jun 11 2013 17:29:48 +03:00

Did you file a defect against ASP.NET MVC 4.

This is something that you shouldn't have to work around for too long, as I would consider this to be a show stopper bug on Microsofts part.

Tugberk
by Tugberk on Thursday, Jun 13 2013 15:40:10 +03:00

@Herb

This is not a bug I think as this was an intented choice on their side. I fact I even sent a pull request to make this baked into the framework:

http://aspnetwebstack.codeplex.com/SourceControl/network/forks/tugberk/aspnetwebstack/contribution/3590

I got no response for that as you can see

New Comment