ALMMa / datatables.aspnet

Microsoft AspNet bindings and automatic parsing for jQuery DataTables along with extension methods to help on data queries.
MIT License
301 stars 136 forks source link

DataTables response AdditionalParams not present on client side. #72

Open alexaka1 opened 4 years ago

alexaka1 commented 4 years ago

I am trying to send additional paramaters to my client in the DataTables response, however in the Json recieved on client-side, no additional params are present.

Using ASP.Net Core 2.2, DataTables.AspNet.AspNetCore 2.0.2

In startup.cs the code below successfully enables request additional parameters (passed under the "appJson" key from client). And I also pass true as the second parameter, to enable it for response as well (I think this is how to do it. Used #50 ronnieoverby's solution.)

services.RegisterDataTables(ctx =>
{
    string appJson = ctx.ValueProvider.GetValue("appJson")
        .FirstValue ?? "{}";
return JsonConvert.DeserializeObject<IDictionary<string, object>>(appJson);
}, true);

In a sample controller I have the following load method:

public async Task<IActionResult> LoadSomeData(IDataTablesRequest request)
{
/* my code here with an await call to get data
*....
*/
DataTablesResponse response = DataTablesResponse.Create(request, myRecordsTotal,
                myRecordsFiltered, myData, myAdditionalParams);

var res = new DataTablesJsonResult(response, false);
return res;
}

This successfully creates a DataTablesResponse object that actually contains myAdditionalParams (of type Dictionary<string, object>, but for simplicity I fill it with only string values, so no fancy conversions). When debugging the server, the DataTablesResponse object contains all my info, even AdditionalParameters. However when this is returned from the controller method, the client recieves only the following (sample) Json Eg.:

{
"draw":1,
"recordsTotal":2,
"recordsFiltered":2,
"data":[{ "someKey": 42 }, { "someKey": 69}]
}

What I want to happen is something like:

{
"draw":1,
"recordsTotal":2,
"recordsFiltered":2,
"data":[{ "someKey": 42 }, { "someKey": 69}],
"additionalParams": {"someKey": "The sum of someKey col is 111." }
}

I have seen some references to making a parse method, for additional params, but I have only found these for the request side and my code above contains that already. I have no idea what I need to do to make this work for response side.

alexaka1 commented 4 years ago

On further investigation, I found how the DataTablesJsonResult is created, and it just calls to ToString() on the DataTablesResponse object. However, this call doesn't put the additional params into the string. I'm not sure how to debug this as to why. But this is definitely the problem. Because the debug windows shows the Locals as they are, but the actual ToString call fails to put in the desired additional params.

alexaka1 commented 4 years ago

After debugging with Rider (awesome third party debugger it has), I can confirm the Configuration.Options.IsResponseAdditionalParametersEnabled call inside the ToString() method of DataTableResponse is false.

Why? I have zero clue, because the request variant of this is true, so it retains that from startup.cs somehow. :(

Manually calling DataTables.AspNet.AspNetCore.Configuration.Options.EnableResponseAdditionalParameters(); literally just before the ToString() still has zero effect, because the Options property doesn't change to true.

alexaka1 commented 4 years ago

Because I really ought to be doing my actual work, I came up with the jankest solution, but it works in my use case.

Here is an inherited class that I use with a constructor that takes the OG DataTablesResponse obj. Also I incorporated #54 bogusz's suggestion to improve the jsonWriter to serialize enumerables, just for further compatibility in my application.

public class DataTablesResponseHelper : DataTables.AspNet.AspNetCore.DataTablesResponse
    {
        protected DataTablesResponseHelper(int draw, string errorMessage) : base(draw, errorMessage)
        {
        }

        protected DataTablesResponseHelper(int draw, string errorMessage, IDictionary<string, object> additionalParameters) : base(draw, errorMessage, additionalParameters)
        {
        }

        protected DataTablesResponseHelper(int draw, int totalRecords, int totalRecordsFiltered, object data) : base(draw, totalRecords, totalRecordsFiltered, data)
        {
        }

        protected DataTablesResponseHelper(int draw, int totalRecords, int totalRecordsFiltered, object data, IDictionary<string, object> additionalParameters) : base(draw, totalRecords, totalRecordsFiltered, data, additionalParameters)
        {
        }

        public DataTablesResponseHelper(DataTablesResponse response) : this(response.Draw,response.TotalRecords,response.TotalRecordsFiltered,response.Data,response.AdditionalParameters)
        {
        }

        private bool IsSuccessResponse()
        {
          return this.Data != null && string.IsNullOrWhiteSpace(this.Error);
        }

        public override string ToString()
        {
          using (StringWriter stringWriter = new StringWriter())
          {
            using (JsonTextWriter jsonTextWriter = new JsonTextWriter((TextWriter) stringWriter))
            {
              if (this.IsSuccessResponse())
              {
                jsonTextWriter.WriteStartObject();
                jsonTextWriter.WritePropertyName(Configuration.Options.ResponseNameConvention.Draw, true);
                jsonTextWriter.WriteValue(this.Draw);
                jsonTextWriter.WritePropertyName(Configuration.Options.ResponseNameConvention.TotalRecords, true);
                jsonTextWriter.WriteValue(this.TotalRecords);
                jsonTextWriter.WritePropertyName(Configuration.Options.ResponseNameConvention.TotalRecordsFiltered, true);
                jsonTextWriter.WriteValue(this.TotalRecordsFiltered);
                jsonTextWriter.WritePropertyName(Configuration.Options.ResponseNameConvention.Data, true);
                jsonTextWriter.WriteRawValue(this.SerializeData(this.Data));
                if (this.AdditionalParameters != null)
                {
                  foreach (KeyValuePair<string, object> additionalParameter in (IEnumerable<KeyValuePair<string, object>>) this.AdditionalParameters)
                  {
                    jsonTextWriter.WritePropertyName(additionalParameter.Key, true);
                    if (additionalParameter.Value is IEnumerable)
                    {
                      jsonTextWriter.WriteRawValue(SerializeData(additionalParameter.Value));
                    }
                    else
                    {
                      jsonTextWriter.WriteValue(additionalParameter.Value);
                    }
                  }
                }
                jsonTextWriter.WriteEndObject();
              }
              else
              {
                jsonTextWriter.WriteStartObject();
                jsonTextWriter.WritePropertyName(Configuration.Options.ResponseNameConvention.Draw, true);
                jsonTextWriter.WriteValue(this.Draw);
                jsonTextWriter.WritePropertyName(Configuration.Options.ResponseNameConvention.Error, true);
                jsonTextWriter.WriteValue(this.Error);
                if (this.AdditionalParameters != null)
                {
                  foreach (KeyValuePair<string, object> additionalParameter in (IEnumerable<KeyValuePair<string, object>>) this.AdditionalParameters)
                  {
                    jsonTextWriter.WritePropertyName(additionalParameter.Key, true);
                    if (additionalParameter.Value is IEnumerable)
                    {
                      jsonTextWriter.WriteRawValue(SerializeData(additionalParameter.Value));
                    }
                    else
                    {
                      jsonTextWriter.WriteValue(additionalParameter.Value);
                    }
                  }
                }
                jsonTextWriter.WriteEndObject();
              }
              jsonTextWriter.Flush();
              return stringWriter.ToString();
            }
          }
        }
    }

This just simply removes the original check for the option. This causes no problems for me, but use at your discretion.