Reputation: 21430
I am sending the following request parameters to my service; among which, is the filter
parameter which is a multidimensional array:
filter[0][field]:homeCountry
filter[0][data][type]:string
filter[0][data][value]:united s
page:2
start:200
limit:200
sort:homeCountry
dir:ASC
The querystring is encoded like so:
paymentratetrip.json?filter%5B0%5D%5Bfield%5D=homeCountry&filter%5B0%5D%5Bdata%5D%5Btype%5D=string&filter%5B0%5D%5Bdata%5D%5Bvalue%5D=united%20s&page=2&start=200&limit=200&sort=homeCountry&dir=AS
Currently, my C# request object looks like this:
public class PaymentRateTripRequest
{
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
}
How can I modify my request object to receive the filter
parameter which could be a multidimensional array?
Note: I am using ServiceStack.
The only way I can think is to send the entire request object as a parameter to my method like so:
public object Get(PaymentRateTripRequest req)
{
return _repository.GetAllRates(req.start, req.limit, req.sort, req.dir, this.Request.OriginalRequest);
}
But, this doesn't seem like the best solution.
Edit: this.Request.QueryString
this.Request.QueryString
{filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
[System.Web.HttpValueCollection]: {filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
base {System.Collections.Specialized.NameObjectCollectionBase}: {filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
_all: null
_allKeys: {string[8]}
AllKeys: {string[8]}
Edit: filter
is still empty.
Upvotes: 0
Views: 880
Reputation: 21501
This is an alternative solution that requires no changes to your client and therefore will accept the query string in the format you have currently:
paymentratetrip.json?filter%5B0%5D%5Bfield%5D=homeCountry&filter%5B0%5D%5Bdata%5D%5Btype%5D=string&filter%5B0%5D%5Bdata%5D%5Bvalue%5D=united%20s&page=2&start=200&limit=200&sort=homeCountry&dir=AS
The disadvantage of this method is that it's more code to maintain. The JSV method is simpler.
We can use a ServiceStack filter to intercept the query string before it reaches the action method. It can then parse the custom filter format and populate the filter object of the DTO.
public class FilterAttribute : Attribute, IHasRequestFilter
{
IHasRequestFilter IHasRequestFilter.Copy()
{
return this;
}
public int Priority { get { return int.MinValue; } }
FilterField CreateOrUpdateField(ref Dictionary<string, FilterField> filter, string id)
{
if(filter.ContainsKey(id))
return filter[id];
var field = new FilterField { Data = new Dictionary<string, object>() };
filter.Add(id, field);
return field;
}
public void RequestFilter(IRequest req, IResponse res, object requestDto)
{
var filteredDto = requestDto as IFilter;
if(filteredDto == null)
return;
const string fieldPattern = @"filter\[([A-Za-z0-9]+)\]\[field\]";
const string dataPattern = @"filter\[([A-Za-z0-9]+)\]\[data\]\[([A-Za-z0-9]+)\]";
Dictionary<string, FilterField> filter = new Dictionary<string, FilterField>();
foreach(var property in req.QueryString.AllKeys)
{
Match match = Regex.Match(property, fieldPattern, RegexOptions.IgnoreCase);
if(match.Success)
{
// Field
var id = match.Groups[1].Value;
var field = CreateOrUpdateField(ref filter, id);
field.Field = req.QueryString[property];
} else {
match = Regex.Match(property, dataPattern, RegexOptions.IgnoreCase);
if(match.Success)
{
// Data value
var id = match.Groups[1].Value;
var keyName = match.Groups[2].Value;
var field = CreateOrUpdateField(ref filter, id);
if(!field.Data.ContainsKey(keyName))
field.Data.Add(keyName, req.QueryString[property]);
}
}
}
filteredDto.Filter = filter.Values.ToArray();
}
}
You will also need to add this interface and FilterField
class:
public class FilterField
{
public string Field { get; set; }
public Dictionary<string,object> Data { get; set; }
}
public interface IFilter
{
FilterField[] filter { get; set; }
}
Then you simply need to update your DTO so it looks like this:
[Route("/paymentratetrip", "GET"]
[Filter]
public class PaymentRateTripRequest : IFilter
{
public int page { get; set; }
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
public FilterField[] filter { get; set; }
}
Upvotes: 2
Reputation: 21501
You should add a property with the filter to your DTO, such as below:
public class PaymentRateTripRequest
{
public int page { get; set; }
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
public FilterField[] filter { get; set; }
}
public class FilterField
{
public string field { get; set; }
public Dictionary<string,object> data { get; set; }
}
This will allow you to add any number of fields to filter by, and by making the data
property of the FilterField
a Dictionary<string, object>
you can add as many data properties as needed.
Then you can populate the filter
parameter in your PaymentRateTripRequest
using JSV format. You can learn about JSV format here. JSV Format (i.e. JSON-like Separated Values) is a JSON inspired format that uses CSV-style escaping for the least overhead and optimal performance.
paymentratetrip.json?filter=[{field:homeCountry,data:{type:string,value:"united s"}},{field:other,data:{type:int,value:34,special:true}}]&page=2&start=200&limit=200&sort=homeCountry&dir=ASC
Then you can access the filter as a regular property on your request.
Hope this helps.
Upvotes: 2