4

I'm trying to grab the "status" and "all" key, value from the requested URL, and can't figure out how to build my class object.

The JSON API specification I'm referring to can be found here: http://jsonapi.org/recommendations/#filtering

// requested url
/api/endpoint?filter[status]=all


// my attempt at model binding
public class FilterParams
{
    public Dictionary<string, string> Filter { get; set; }
}

[HttpGet]
public string Get([FromUri] FilterParams filter)
{
    // never gets populated...
    var filterStatus = filter.Filter["status"];
}
Carl Sagan
  • 940
  • 11
  • 29

2 Answers2

1
  1. You could use IModelBinder for that:

    • Define a model binder:

      public class FilterParamsModelBinder : IModelBinder
      {
          public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
          {
              if (bindingContext.ModelType != typeof(FilterParams)) return false;
      
              Dictionary<string, string> result = new Dictionary<string, string>();
      
              var parameters = actionContext.Request.RequestUri.Query.Substring(1);
      
              if(parameters.Length == 0) return false;
      
              var regex = new Regex(@"filter\[(?<key>[\w]+)\]=(?<value>[\w^,]+)");
      
              parameters
                  .Split('&')
                  .ToList()
                  .ForEach(_ =>
                  {
                      var groups = regex.Match(_).Groups;
      
                      if(groups.Count == 0)
                          bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value.");
      
                      result.Add(groups["key"].Value, groups["value"].Value);
                  });
      
              bindingContext.Model = new FilterParams { Filter = result};
      
              return bindingContext.ModelState.IsValid;
          }
      }
      
    • Use it:

      [HttpGet]
      public string Get([ModelBinderAttribute(typeof(FilterParamsModelBinder))] FilterParams filter)
      {
        //your code
      }
      
  2. If you could define a route like "/api/endpoint?filter=status,all" instead, than you could use a TypeConverter for that:

    • Define a converter:

      public class FilterConverter : TypeConverter
      {
          public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
          {
              if (!(value is string)) return base.ConvertFrom(context, culture, value);
      
              var keyValue = ((string)value).Split(',');
      
              return new FilterParams 
              {
                  Filter = new Dictionary<string, string> { [keyValue[0]] = keyValue[1] } 
              };
          }
      
          public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 
          {
             return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
          }            
      }
      
    • Use it:

      [TypeConverter(typeof(FilterConverter))]
      public class FilterParams
      {
          public Dictionary<string, string> Filter { get; set; }
      }
      
      [HttpGet]
      public string Get(FilterParams filter)
      {
          var filterStatus = filter.Filter["status"]; 
      }
      
Andriy Tolstoy
  • 5,016
  • 2
  • 27
  • 26
1

If you're building json:api apps on .Net Core, I strongly recommend checking out this library: https://github.com/json-api-dotnet/JsonApiDotNetCore

It handles all of the heavy lifting for you and for this specific example, (you need to get the filter value) the solution looks like:

public FooController : JsonApiController<Foo> {
    private readonly IQueryAccessor _queryAccessor;
    public FooController(IQueryAccessor queryAccessor, /* ... */) 
    : base(/* ... */) {
       _queryAccessor = queryAccessor;
    }

    [HttpGet]
    public override async Task<IActionResult> GetAsync() {
        var status = _queryAccessor.GetRequired<string>("status");
        // ...
    }
}
jaredcnance
  • 557
  • 1
  • 4
  • 19