user1477388
user1477388

Reputation: 21430

Complex array in ServiceStack request

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.

enter image description here

Upvotes: 0

Views: 880

Answers (2)

Scott
Scott

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.

Request attribute to populate the filter from the querystring:

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

Scott
Scott

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

Related Questions