Search code examples
asp.net-core-mvcasp.net-apicontrollerreact-table

Manual react-table onFetchData to net-core API Controller


I'm using react-table with a net-core API Controller and having some troubled grabbing the "sorted" and "filtered" fields.

I'm sending the fields in the "onFetchData" method like so:

Axios.get('/api/Dashboard/GetGiftCards', {
    params: {
        page: state.page,
        pageSize: state.pageSize,
        sorted: state.sorted,
        filtered: state.filtered
    }
})

A query string sent that contained, for example, 3 types of sorting and 3 filters would look like this:

http://localhost:64963/api/Dashboard/GetGiftCards?page=0&pageSize=10&sorted[]=%7B%22id%22:%22giftCardType%22,%22desc%22:false%7D&sorted[]=%7B%22id%22:%22membershipId%22,%22desc%22:false%7D&sorted[]=%7B%22id%22:%22createdDate%22,%22desc%22:false%7D&filtered[]=%7B%22id%22:%22imisId%22,%22value%22:%223%22%7D&filtered[]=%7B%22id%22:%22giftCardType%22,%22value%22:%22E%22%7D

Server side, my controller is setup like this:

[HttpGet("GetGiftCards")]
public async Task<IActionResult> GetCardsAsync([FromQuery] GetGiftCardsRequest request)

Any my request object is as follows

public class GetGiftCardsRequest
{
    [JsonProperty(PropertyName = "page")]
    public int Page { get; set; }

    [JsonProperty(PropertyName = "pageSize")]
    public int PageSize { get; set; }

    [JsonProperty(PropertyName = "sorted")]
    public IEnumerable<string> sorted { get; set; }

    [JsonProperty(PropertyName = "filtered")]
    public string[] Filters { get; set; }

    public class Sorting
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }

        [JsonProperty(PropertyName = "desc")]
        public bool Descending { get; set; }
    }

    // Filtering object not created yet
}

I'm struggling to get the controller to consume the URL, but so far I haven't found anything online. I'm thinking I should probably just build my own custom filter using Regex, but I thought I would post here first and see if anyone else has come up with a solution?


Solution

  • I found a workaround for this. I decided to manually read the query string and place the expected values in the request object.

    Here is how the attribute looks on my method

    [HttpGet("GetGiftCards")]
    [ReactTableFilter("sorted", "filtered", "request")]
    public async Task<IActionResult> GetGiftCardsAsync([FromQuery] GetGiftCardsRequest request)
    

    The arguments are the "sorted" list of objects from react-table, the "filtered" list of objects from the react-table, and the name of the ActionArgument we want to populate.

    Here is the code for the attribute:

    public class ReactTableFilterAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// </summary>
        private readonly string _argumentName;
    
        /// <summary>
        ///     The filter query variable name
        /// </summary>
        private readonly string _filterName;
    
        /// <summary>
        ///     The sorting query variable name
        /// </summary>
        private readonly string _sortedName;
    
        /// <summary>
        ///     Creates a new instance of this attribute
        /// </summary>
        /// <param name="sortedName">The name of the sorted variable list</param>
        /// <param name="filterName">The name of the filtered variable list</param>
        /// <param name="argumentName">The name of the <see cref="GetGiftCardsRequest" /> request object we want to full</param>
        public ReactTableFilterAttribute(string sortedName, string filterName, string argumentName)
        {
            if (string.IsNullOrEmpty(sortedName))
            {
                throw new ArgumentException(nameof(sortedName));
            }
    
            if (string.IsNullOrEmpty(filterName))
            {
                throw new ArgumentException(nameof(filterName));
            }
    
            if (string.IsNullOrEmpty(argumentName))
            {
                throw new ArgumentException(nameof(argumentName));
            }
    
            _argumentName = argumentName;
            _filterName = filterName;
            _sortedName = sortedName;
        }
    
        /// <inheritdoc />
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            GetGiftCardsRequest toFill = (GetGiftCardsRequest) context.ActionArguments[_argumentName];
    
            toFill.Sorted =
                GetArrayOfObjects<GetGiftCardsRequest.Sorting>(context.HttpContext.Request.QueryString.Value,
                    _sortedName);
            toFill.Filters =
                GetArrayOfObjects<GetGiftCardsRequest.Filtering>(context.HttpContext.Request.QueryString.Value,
                    _filterName);
    
            base.OnActionExecuting(context);
        }
    
        /// <summary>
        ///     Serializes an array of the specified objects from the given <paramref name="queryString" /> that have the given
        ///     <paramref name="name" />
        /// </summary>
        /// <typeparam name="T">The type of the object you want to serialize</typeparam>
        /// <param name="queryString">The query string to seach</param>
        /// <param name="name">Name of the array parameter to pull</param>
        /// <returns>The array of items if any exist</returns>
        private T[] GetArrayOfObjects<T>(string queryString, string name)
        {
            string pattern = name + @"\[\]=(.*?)&";
            MatchCollection matches = Regex.Matches(queryString + "&", pattern);
            List<T> newList = new List<T>();
    
            foreach (Match m in matches)
            {
                string sortedObj = HttpUtility.UrlDecode(m.Groups[1].Value);
                T deserialized = JsonConvert.DeserializeObject<T>(sortedObj);
    
                newList.Add(deserialized);
            }
    
            return newList.ToArray();
        }
    }
    

    I realize this could be cleaned up a lot to make it more abstract. If I come back around to it, I'll paste the updated code.