title: ASP.Net Web API custom model binding with x-www-form-urlencoded posted data - nothing seems to work
body: <p>I am having a lot of trouble getting custom model binding to work when posting <code>x-www-form-urlencoded</code> data. I've tried every way I can think of and nothing seems to produce the desired result. Note when posting JSON data, my JsonConverters and so forth all work just fine. It's when I post as <code>x-www-form-urlencoded</code> that the system can't seem to figure out how to bind my model.</p>
  
  <p>My test case is that I'd like to bind a TimeZoneInfo object as part of my model.</p>
  
  <p>Here's my model binder:</p>
  
  <pre><code>public class TimeZoneModelBinder : SystemizerModelBinder
  {
      protected override object BindModel(string attemptedValue, Action&lt;string&gt; addModelError)
      {
          try
          {
              return TimeZoneInfo.FindSystemTimeZoneById(attemptedValue);
          }
          catch(TimeZoneNotFoundException)
          {
              addModelError("The value was not a valid time zone ID. See the GetSupportedTimeZones Api call for a list of valid time zone IDs.");
              return null;
          }
      }
  }
  </code></pre>
  
  <p>Here's the base class I'm using:</p>
  
  <pre><code>public abstract class SystemizerModelBinder : IModelBinder
  {
      public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
      {
          var name = GetModelName(bindingContext.ModelName);
          var valueProviderResult = bindingContext.ValueProvider.GetValue(name);
          if(valueProviderResult == null || string.IsNullOrWhiteSpace(valueProviderResult.AttemptedValue))
              return false;
  
          var success = true;
          var value = BindModel(valueProviderResult.AttemptedValue, s =&gt;
          {
              success = false;
              bindingContext.ModelState.AddModelError(name, s);
          });
          bindingContext.Model = value;
          bindingContext.ModelState.SetModelValue(name, new System.Web.Http.ValueProviders.ValueProviderResult(value, valueProviderResult.AttemptedValue, valueProviderResult.Culture));
          return success;
      }
  
      private string GetModelName(string name)
      {
          var n = name.LastIndexOf(".", StringComparison.Ordinal);
          return n &lt; 0 || n &gt;= name.Length - 1 ? name : name.Substring(n + 1);
      }
  
      protected abstract object BindModel(string attemptedValue, Action&lt;string&gt; addModelError);
  }
  </code></pre>
  
  <p>I used a base class like this to make it simple to create additional custom model binders.</p>
  
  <p>Here's my model binder provider. Note that this is getting invoked correctly from my IoC container, so I won't bother to show that aspect of my code.</p>
  
  <pre><code>public class SystemizerModelBinderProvider : ModelBinderProvider
  {
      public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
      {
          if(modelType == typeof(TimeZoneInfo))
              return new TimeZoneModelBinder();
  
          return null;
      }
  }
  </code></pre>
  
  <p>Finally, here's the action method and model class:</p>
  
  <pre><code>[DataContract)]
  public class TestModel
  {
      [DataMember]
      public TimeZoneInfo TimeZone { get; set; }
  }
  
  [HttpPost]
  public HttpResponseMessage Test(TestModel model)
  {
      return Request.CreateResponse(HttpStatusCode.OK, model);
  }
  </code></pre>
  
  <p>For the action method, I have tried:</p>
  
  <pre><code>public HttpResponseMessage Test([FromBody] TestModel model)
  </code></pre>
  
  <p>This invokes the <code>FormUrlEncodedMediaFormatter</code>, which seems to ignore my custom model binder altogether.</p>
  
  <pre><code>public HttpResponseMessage Test([ModelBinder] TestModel model)
  </code></pre>
  
  <p>This calls into my custom model binder, as expected, but then it only provides ValueProviders for <code>RouteData</code> and <code>QueryString</code> and for some reason doesn't provide anything for body content. See below:</p>
  
  <p><img src="https://i.stack.imgur.com/zIzba.png" alt="Value Providers"></p>
  
  <p>I've also tried decorating the class itself with <code>ModelBinder(typeof(SystemizerModelBinderProvider))</code></p>
  
  <p>Why does model binding ONLY occur when I use the [ModelBinder] attribute, and why does it ONLY try to read route and querystring values and ignore body content? Why does <code>FromBody</code> ignore my custom model binder provider?</p>
  
  <p>How do I create a scenario where I can receive POSTED <code>x-www-form-urlencoded</code> data and successfully bind model properties using custom logic?</p>