Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

How best to pass AdditionalParameters in request? #50

Open
spencerflagg opened this issue Mar 15, 2017 · 11 comments
Open

How best to pass AdditionalParameters in request? #50

spencerflagg opened this issue Mar 15, 2017 · 11 comments
Assignees
Labels
enhancement Describes an enhancement or an existing feature
Milestone

Comments

@spencerflagg
Copy link

spencerflagg commented Mar 15, 2017

Great library guys.

I didn't know where else to ask this, hopefully this is semi-appropriate...
I'm trying to send column level custom filter data (date ranges, lookups, etc) back to the server.

I see that IDataTablesRequest can take an AdditionalParameters dictionary, but I'm not sure exactly how to structure my object on the client or where to pass it in. ajax.data perhaps? I'd love to see an example.

Thanks in advance for the help

@spencerflagg
Copy link
Author

It should be noted that I've since found the Mvc5.AdditionalParameters project that was created last week, and have added the data and dataSrc properties to the ajax object as suggested, but I'm still not seeing AdditionalParameters come into my controller as anything but null.

@ALMMa
Copy link
Owner

ALMMa commented Mar 17, 2017

Check out issue #47

As I've said there, this behavior is not optimal and will be improved but for now, you must enable the request parameters on the DataTables.AspNet options and provide a model binder which will parse your parameters.

It's not sufficient to simply send those parameters (at least, for now). I'll implement a automatic bindind but it's important to consider that some complex members might still require a custom binder nonetheless.

@spencerflagg
Copy link
Author

Thanks! Excuse my ignorance, but I'm not sure what you mean by "enable the request parameters on the DataTables.AspNet options".

Thanks in advance for your time

@ALMMa
Copy link
Owner

ALMMa commented Mar 17, 2017

Check code and comments on Global.asax for the additional parameters sample project.
You have to explicitly set the DataTables.AspNet project to allow for additional request/response parameters, as well as provide a custom binder with the appropriate function to parse those additional parameters.

See more here:
https://github.com/ALMMa/datatables.aspnet/blob/dev/samples/DataTables.AspNet.Samples.Mvc5.AdditionalParameters/Global.asax.cs

var options = new DataTables.AspNet.Mvc5.Options()
            .EnableRequestAdditionalParameters()
            .EnableResponseAdditionalParameters();

var binder = new DataTables.AspNet.Mvc5.ModelBinder();
ParseAdditionalParameters = Parser;

AspNet.Mvc5.Configuration.RegisterDataTables(options, binder);

Parser is your function. On Global.asax there is a sample for that function.

@spencerflagg
Copy link
Author

Thanks!
What if you're using DataTables.AspNet.WebApi2 instead of DataTables.AspNet.Mvc5? Seems like the Parser function is considerably different.

@ALMMa
Copy link
Owner

ALMMa commented Mar 23, 2017

Hello, @spencerflagg

Yes, you are correct.
The parser function is different because WebApi provides different binding mechanism. For instance, instead of a ControllerContext, it provides a HttpActionContext.

You might want to check more about HttpActionContext here and more about ModelBindingContext here to correctly implement your parsing function.

This behavior is due to the very nature of the binding mechanism from each platform. Parser function will always return an IDictionary<string, object> but parameters will change to match those from the chosen platform (MVC, WebApi, AspNet Core).

@danielearrighi
Copy link

danielearrighi commented Apr 7, 2017

Hello, what do you think about this generic method to parse the additional parameter?

public static IDictionary<string, object> DataTablesParametersParser(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var collection = controllerContext.HttpContext.Request.QueryString;
var modelKeys = collection.AllKeys
    .Where(m => !m.StartsWith("columns"))
    .Where(m => !m.StartsWith("order"))
    .Where(m => !m.StartsWith("search"))
    .Where(m => m != "draw")
    .Where(m => m != "start")
    .Where(m => m != "length")
    .Where(m => m != "_"); //Cache bust

var dictionary = new Dictionary<string, object>();
foreach (string key in modelKeys)
{
    var value = bindingContext.ValueProvider.GetValue(key).AttemptedValue;
    if (value.Length > 0)   
        dictionary.Add(key, value);
}
return dictionary;
}

Basically, it filters all the "standard" querystrings passed by datatables itself, and parses out the remaining parameters in the dictionary. This way I can pass every parameters I want (should not start with columns, order, search of course) and I get it in the dictionary.

Like:

 "data": function (d) {
  d.buildingDate = '@Model.SearchBuildingDate';
  d.State = '@Model.SearchState';
}

What do you think? Any downsides using this approach?

Thanks

@ALMMa
Copy link
Owner

ALMMa commented Apr 7, 2017

@danielearrighi Thanks!

That sounds interesting. The only downside I can think of if the multiple .Where, which could be replaced by a single (faster compiling/running) statement.

I'll sure give that a try. Maybe provide an automatic binder (with some automatic request parameters parsing) and a manual binder (with override capability to solve complex cases, like nested objects).

I just need to test, however, on POST. Dunno for sure right now but in some cases, after parsing body element, you cannot read values again. I have to double check that on all AspNet Core, MVC and WebApi to make sure that parsing parameters automatically won't break the main request params binding (eg: draw, columns, etc).

@Tegawende
Copy link

Tegawende commented Apr 12, 2017

Hello,

I want to return additional parameters from my controllers.
I enabled the response parameters according to what you said.
My question is about the parser function.
How can I write my parser function if I want to return a string array?

Sorry for my bad english.
Thanks !

@fabriciogs
Copy link

fabriciogs commented May 9, 2017

@danielearrighi @ALMMa

Since I'm using POST to send data using DataTables, I modified @danielearrighi solution to something a little bit better:

public static IDictionary<string, object> DataTablesParametersParser(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var httpMethod = controllerContext.HttpContext.Request.HttpMethod;
    var collection = httpMethod.ToUpper().Equals("POST") ? controllerContext.HttpContext.Request.Form : controllerContext.HttpContext.Request.QueryString;
    var modelKeys = collection.AllKeys.Where(m => !m.StartsWith("columns") && !m.StartsWith("order") && !m.StartsWith("search") && m != "draw" && m != "start" && m != "length" && m != "_"); //Cache bust
    var dictionary = new Dictionary<string, object>();
    foreach (string key in modelKeys)
    {
        var value = bindingContext.ValueProvider.GetValue(key).AttemptedValue;
        if (value.Length > 0)
            dictionary.Add(key, value);
    }
    return dictionary;
}

IE: When HttpMethod is POST I grab Request.Form, else Request.QueryString.

The code isn't pretty, I know, but it works!

@ronnieoverby
Copy link

ronnieoverby commented Feb 9, 2018

In my aspnet core project, I just did this:

// some.js
$('#myTable').DataTable({
    serverSide: true,
    ajax: {
        url: '/someAjaxUrl',
        data: function (d) {
            d.appJson = JSON.stringify({
                someNumber: 123,
                someString: "hello",
                someNumbers: [1,2,3]
            });
        }
    },
    // other stuff
});
// Startup.cs
services.RegisterDataTables(ctx =>
{
    var appJson = ctx.ValueProvider.GetValue("appJson").FirstValue ?? "{}";
    return JsonConvert.DeserializeObject<IDictionary<string, object>>(appJson);
}, true);


// DataTablesExts.cs
public static class DataTablesExts
{
    public static T Get<T>(this IDataTablesRequest request, string key)
    {
        var obj = request.AdditionalParameters[key];
        return Convert<T>(obj);
    }

    public static bool TryGet<T>(this IDataTablesRequest request, string key, out T value)
    {
        if (request.AdditionalParameters.TryGetValue(key, out object obj))
        {
            value = Convert<T>(obj);
            return true;                
        }

        value = default(T);
        return false;
    }

    private static readonly JToken JNull = JToken.Parse("null");
    private static T Convert<T>(object obj)
    {
        T AsNull() => JNull.ToObject<T>();

        if (obj == null)
            return AsNull();

        if (obj is T tobj)
            return tobj;

        if (obj is JToken jt)
            return jt.ToObject<T>();

        try
        {
            return JToken.FromObject(obj).ToObject<T>();
        }
        catch (FormatException) when (obj is string s && string.IsNullOrWhiteSpace(s))
        {
            return AsNull();
        }
    }
}


// SomeController.cs or SomePage.cshtml.cs
public async Task<DataTablesJsonResult> OnGetQueryAsync(IDataTablesRequest request)
{
    var someNumber = request.Get<int>("someNumber");
    var someString = request.Get<string>("someString");
    var someNumbers = request.Get<int[]>("someNumbers");
    // do other work here....
}

Not the best thing in the world, but damn, I just wanted to get on with building my app.

bitmoji

@ALMMa ALMMa self-assigned this Jun 17, 2021
@ALMMa ALMMa added the enhancement Describes an enhancement or an existing feature label Jun 17, 2021
@ALMMa ALMMa added this to the 3.0.0 milestone Jun 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement Describes an enhancement or an existing feature
Projects
None yet
Development

No branches or pull requests

6 participants