Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core API search parameters from path/route

I am porting a PHP/CI API that uses $params = $this->uri->uri_to_assoc() so that it can accept GET requests with many combinations, such as:

  • https://server/properties/search/beds/3/page/1/sort/price_desc
  • https://server/properties/search/page/2/lat/34.1/lon/-119.1
  • https://server/properties/search
  • etc

With lots of code like:

$page = 1;
if (!empty($params['page'])) {
    $page = (int)$params['page'];
}

The two ASP.NET Core 2.1 techniques I've tried both seem like a kludge so I would appreciate any guidance on a better solution:

1) Conventional routing with catchall:

app.UseMvc(routes => {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Properties}/{action=Search}/{*params}"
                );
            });

But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.

2) Attribute routing:

    [HttpGet("properties/search")]
    [HttpGet("properties/search/beds/{beds}")]
    [HttpGet("properties/search/beds/{beds}/page/{page}")]
    [HttpGet("properties/search/page/{page}/beds/{beds}")]
    public IActionResult Search(int beds, double lat, double lon, int page = 1, int limit = 10) {
}

Obviously putting every combination of allowed search parameters and values is tedious.

Changing the signature of these endpoints is not an option.

like image 289
Cce32 Avatar asked Jun 28 '18 23:06

Cce32


1 Answers

Edit

My other answer is a better option.

General Idea

$params = $this->uri->uri_to_assoc() turns a URI into an associative array, which is basically a .NET Dictionary<TKey, TValue>. We can do something similar in ASP.NET Core. Lets say we have the following routes.

app.UseMvc(routes => {
    routes.MapRoute(
        name: "properties-search",
        template: "{controller=Properties}/{action=Search}/{*params}"
    );
});

Bind Uri Path to Dictionary

Action

public class PropertiesController : Controller
{
    public IActionResult Search(string slug)
    {
        var dictionary = slug.ToDictionaryFromUriPath();
         return Json(dictionary);
    }
}

Extension Method

public static class UrlToAssocExtensions
{
    public static Dictionary<string, string> ToDictionaryFromUriPath(this string path) {
        var parts = path.Split('/');
        var dictionary = new Dictionary<string, string>();
        for(var i = 0; i < parts.Length; i++)
        {
            if(i % 2 != 0) continue;
            var key = parts[i];
            var value = parts[i + 1];
            dictionary.Add(key, value);
        }

        return dictionary;
    }
}

The result is an associative array based on the URI path.

{
   "beds": "3",
   "page": "1",
   "sort": "price_desc"
}

But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.

Bind Uri Path to Model

If you want model binding for this, then we need to go a step further.

Model

public class BedsEtCetera 
{
    public int Beds { get; set; }
    public int Page { get; set; }
    public string Sort { get; set; }
}

Action

public IActionResult Search(string slug)
{
    BedsEtCetera model = slug.BindFromUriPath<BedsEtCetera>();
    return Json(model);
}

Additional Extension Method

public static TResult BindFromUriPath<TResult>(this string path)
{
    var dictionary = path.ToDictionaryFromUriPath();
    var json = JsonConvert.SerializeObject(dictionary);
    return JsonConvert.DeserializeObject<TResult>(json);
}
like image 154
Shaun Luttin Avatar answered Oct 06 '22 20:10

Shaun Luttin