In my previous post on complex type action parameters with ComplexTypeAwareActionSelector in ASP.NET Web API, I showed how to leverage the ComplexTypeAwareActionSelector from WebAPIDoodle project to involve complex type action parameters during the action selection process. In this post, we will go into details to see how ComplexTypeAwareActionSelector behaves under the covers.
Assuming that we have a Person class as below:
public class Person { public int FooBar; public Nullable<int> Id { get; set; } public string Name { get; set; } public string Surname { get; set; } public string FullName { get { return string.Format("{0} {1}", Name, Surname); } } [BindingInfo(NoBinding = true)] public string FullName2 { get; set; } public Country Country { get; set; } internal int Foo { get; set; } public bool IsLegitPerson() { return Name.Equals("tugberk", StringComparison.OrdinalIgnoreCase); } } public class Country { public int Id { get; set; } public string Name { get; set; } public string ISOCode { get; set; } }
In a real world scenario, we wouldn't use the Person class to bind its values from the URI but bare with me for sake of this demo. Person class has five publicly-settable properties: Id, Name, Surname, FullName2 and Country. It also has internally-settable property called Foo. There are also one read-only property (FullName) and one public field (FooBar). Besides those, the FullName2 property has been marked with WebAPIDoodle.BindingInfoAttribute by setting its NoBinding property to true.
Let’s also assume that we have the below controller and action.
public class FooBarController : ApiController { public IEnumerable<FooBar> Get([FromUri]Person person) { //... } }
Now, the question is how ComplexTypeAwareActionSelector behaves here and which members of the Person class are going to be involved to perform the action selection. To perform this logic, the ComplexTypeAwareActionSelector uses two helper methods as below:
internal static class TypeHelper { internal static bool IsSimpleType(Type type) { return type.IsPrimitive || type.Equals(typeof(string)) || type.Equals(typeof(DateTime)) || type.Equals(typeof(Decimal)) || type.Equals(typeof(Guid)) || type.Equals(typeof(DateTimeOffset)) || type.Equals(typeof(TimeSpan)); } internal static bool IsSimpleUnderlyingType(Type type) { Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { type = underlyingType; } return TypeHelper.IsSimpleType(type); } }
These two methods belong to ASP.NET Web API source code but they are internal. So, I ported them to my project as they are. As you can see, IsSimpleType method accepts a Type parameter and determines if the type is a simple or primitive type. The IsSimpleUnderlyingType method, on the other hand, looks if the Type is Nullable type. If so, it looks at the underlying type to see if it is a simple type or not. This is how the ComplexTypeAwareActionSelector determines if a parameter is simple type or not.
When the ComplexTypeAwareActionSelector sees a complex type action parameter, it hands that type to another private method to get the useable properties. To mimic how that private helper method filters the properties, I created the a little console application which holds the actual filter logic.
class Program { static void Main(string[] args) { Console.Write(Environment.NewLine); Console.WriteLine("==============================================="); Console.WriteLine("========This is the actual logic in use========"); Console.WriteLine("==============================================="); var propInfos = from propInfo in typeof(Person).GetProperties() where TypeHelper .IsSimpleUnderlyingType(propInfo.PropertyType) && propInfo.GetSetMethod(false) != null let noBindingAttr = propInfo .GetCustomAttributes().FirstOrDefault(attr => attr.GetType() == typeof(BindingInfoAttribute)) as BindingInfoAttribute where (noBindingAttr != null) ? noBindingAttr.NoBinding == false : true select propInfo; foreach (var _propInfo in propInfos) { Console.WriteLine(_propInfo.Name); } Console.ReadLine(); } }
Here is what it is doing here for the Person type:
If we run this little console application, we will see the following result:
As a result, the Id, Name and Surname properties will be considered during the action selection. I would like to point out couple of things before finishing up this post:
Happy coding and give feedback for this little feature :)