INullable
INullable

Reputation: 15

failing to bind string indexed query string parameters

I'm attempting to bind some query string parameters that is indexed by string keys but i can't seem to be getting it to work

here are the values i was trying to bind

search[value]: Exception happ...
search[regex]: false

here is the model i'm trying to bind it with getLogsAjax(DataTableAjaxPostModel model)

public class DataTableAjaxPostModel
{
    public int draw { get; set; }
    public int start { get; set; }
    public int length { get; set; }
    public List<Column> columns { get; set; }
    public search search { get; set; }
    public List<Order> order { get; set; }
}

public class search
{
    public string value { get; set; }
    public string regex { get; set; }
}

the rest of the model is being bind correctly except for the search class object, i tripled check that the request contains values for that object, what am i missing here? p.s. the same code was supposedly working pre .net core

Upvotes: 1

Views: 721

Answers (3)

itminus
itminus

Reputation: 25350

  1. You don't need to bind each field manually. Using reflection will make it easily.
  2. Aslo, there's no need bind those outer model's properties (DataTableAjaxPostModel's properties) manually. That's because they will be done by the built-in model binder.

Implementation

create a custom binder QueryStringDictSyntaxBinder<TModel>:

internal class QueryStringDictSyntaxBinder<TModel> : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));
        try
        {
            var result = Activator.CreateInstance<TModel>();
            foreach(var pi in typeof(TModel).GetProperties())
            {
                var modelName = bindingContext.ModelName;
                var qsFieldName = $"{modelName}[{pi.Name}]";
                var field= bindingContext.HttpContext.Request.Query[qsFieldName].FirstOrDefault();
                if(field != null){
                    pi.SetValue(result,field);
                }
                // do nothing if null , or add model binding failure messages if you like
            }
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        catch
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
}

And then decorate the search property with a [ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))] :

public class DataTableAjaxPostModel
{
    public int draw { get; set; }
    public int start { get; set; }
    public int length { get; set; }
    public List columns { get; set; }

    [ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))]
    public search search { get; set; }

    public List order { get; set; }
}

Test Case:

I test it with the following requests, and it works fine for me:

?draw=1&search[value]=abc&search[regex]=(.*)&
?draw=1&sEarCh[value]=opq&Search[regex]=([^123]*)&
?draw=1&seaRch[value]=rst&Search[regex]=(.*)&
?draw=1&Search[value]=abc&
?draw=1&

Upvotes: 0

INullable
INullable

Reputation: 15

seems like no one has an answer for this, so i took a different route and wrote my own custom binder, if a better answer came, ill accept it instead of this one, probably will refactor it later (hahaha IKR!)

public class DTModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        try
        {
            var result = new DataTableAjaxPostModel();
            if (bindingContext.HttpContext.Request.Query.Keys.Contains("draw"))
                result.draw = int.Parse(bindingContext.ValueProvider.GetValue("draw").FirstValue);
            if (bindingContext.HttpContext.Request.Query.Keys.Contains("search[value]") &&
                bindingContext.HttpContext.Request.Query.Keys.Contains("search[regex]"))
                result.search = new search()
                {
                    regex = bindingContext.ValueProvider.GetValue("search[regex]").FirstValue,
                    value = bindingContext.ValueProvider.GetValue("search[value]").FirstValue
                };
            //...
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        catch
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
}

Upvotes: 0

OptionallyNone
OptionallyNone

Reputation: 11

A little more background of the code would be helpful such as the code section that is actually doing the binding however here is a dotnetcore controller example with query parameter binding. Also common practice in C# are class names and fields are both uppercase FYI.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
public class SampleController : Controller
{
    [HttpGet]
    [Route("")]
    public IActionResult ExampleGet([FromQuery] DataTableAjaxPostModel dataTableAjaxPostModel)
    {
        // You should be able to debug and see the value here
        var result = dataTableAjaxPostModel.search;
        return Ok();
    }

    public class DataTableAjaxPostModel
    {
        public int draw { get; set; }
        public int start { get; set; }
        public int length { get; set; }
        public List<Column> columns { get; set; }
        public search search { get; set; }
        public List<Order> order { get; set; }
    }

    public class search
    {
        public string value { get; set; }
        public string regex { get; set; }
    }
}

Upvotes: 1

Related Questions