Skip to content

Instantly share code, notes, and snippets.

@joncloud
Last active September 8, 2016 17:21
Show Gist options
  • Select an option

  • Save joncloud/4d4eb078dcf24adab7b5b228f1babfbb to your computer and use it in GitHub Desktop.

Select an option

Save joncloud/4d4eb078dcf24adab7b5b228f1babfbb to your computer and use it in GitHub Desktop.
Range query parameter
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IActionResult Get([FromQuery]Criteria criteria)
{
if (criteria == null)
{
return NotFound();
}
return Ok(criteria);
}
}
public class Criteria
{
public Range<int> integer { get; set; }
public Range<decimal> number { get; set; }
public Range<DateTime> date { get; set; }
}
public class Range<T>
where T : IComparable<T>
{
delegate bool TryParseDelegate(string s, out T result);
delegate bool TryParseDateTime(string s, out DateTime result);
delegate bool TryParseInt32(string s, out int result);
delegate bool TryParseSingle(string s, out decimal result);
internal static readonly char[] Separator = new[] { '~' };
static readonly TryParseDelegate Parser;
static Range()
{
if (typeof(T) == typeof(DateTime))
{
TryParseDateTime d = DateTime.TryParse;
Parser = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), d.Method);
}
if (typeof(T) == typeof(int))
{
TryParseInt32 d = int.TryParse;
Parser = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), d.Method);
}
if (typeof(T) == typeof(decimal))
{
TryParseSingle d = decimal.TryParse;
Parser = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), d.Method);
}
}
public Range(T start, T end)
{
if (end.CompareTo(start) < 0)
throw new ArgumentOutOfRangeException(nameof(end), end, "Range end must occur after start.");
Start = start;
End = end;
}
public T Start { get; }
public T End { get; }
public static bool TryParse(string s, out Range<T> range)
{
range = null;
if (Parser == null) return false;
if (s == null) return false;
var parts = s.Split(Separator);
if (parts.Length == 2)
{
T start, end;
if (Parser(parts[0], out start) &&
Parser(parts[1], out end) &&
start.CompareTo(end) <= 0)
{
range = new Range<T>(start, end);
return true;
}
}
return false;
}
}
public class RangeModelBinder<T> : IModelBinder
where T : IComparable<T>
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var key = bindingContext.ModelName;
var value = bindingContext.ValueProvider.GetValue(key);
Range<T> range;
if (Range<T>.TryParse(value.FirstValue, out range))
bindingContext.Result = ModelBindingResult.Success(range);
return Task.CompletedTask;
}
}
public class RangeModelBinderProvider<T> : IModelBinderProvider
where T : IComparable<T>
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(Range<T>))
return new RangeModelBinder<T>();
return null;
}
}
services.AddMvc().AddMvcOptions(options =>
{
// Binders must come first otherwise default binders will take over.
options.ModelBinderProviders.Insert(0, new RangeModelBinderProvider<DateTime>());
options.ModelBinderProviders.Insert(0, new RangeModelBinderProvider<decimal>());
options.ModelBinderProviders.Insert(0, new RangeModelBinderProvider<int>());
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment