How to pass DataTable via FromBody to Web API POST method (C#)

B. Clay Shannon-B. Crow Raven picture B. Clay Shannon-B. Crow Raven · Jan 6, 2016 · Viewed 12.8k times · Source

I am successfully calling a POST method in a Web API app from a Winforms client that passes some parameters for a Stored Procedure.

I would prefer, though, to pass the results of the Stored Procedure (which I have to run on the client first) to the POST method via the FromBody functionality, if possible.

It's a lot of data to send over the wire, but the way I'm doing it now I have to run the SP twice - first on the client Winforms app, then on the Web API server app, and the simultaneous calling of this SP seems to sometimes cause some problems.

So, I'd like to, if feasible, either send the DataTable via "FromBody" or, if preferable, an XMLized or jsonized version of the data (and then unpack it on the other end, where I convert it into html for retrieval when the corresponding GET method is called.

Does anybody have any code that does this that they could display?

My existing code that just passes the params can be seen here.

UPDATE

Okay,based on Amit Kumar Ghosh's answer, I changed my code to this:

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new    
HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );


    config.Formatters.Add(new DataTableMediaTypeFormatter());
}

public class DataTableMediaTypeFormatter : BufferedMediaTypeFormatter
{
    public DataTableMediaTypeFormatter()
        : base()
    {
        SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("test/dt"));
    }

    public override object ReadFromStream(Type type, Stream readStream,
        HttpContent content, IFormatterLogger formatterLogger, System.Threading.CancellationToken cancellationToken)
    {
        var data = new StreamReader(readStream).ReadToEnd();
        var obj = JsonConvert.DeserializeObject<DataTable>(data);
        return obj;
    }

    public override bool CanReadType(Type type)
    {
        return true;
    }

    public override bool CanWriteType(Type type)
    {
        return true;
    }
}

CONTROLLER

[Route("{unit}/{begindate}/{enddate}/{stringifiedjsondata}")]
[HttpPost]
public void Post(string unit, string begindate, string enddate, DataTable stringifiedjsondata)
{
    DataTable dt = stringifiedjsondata;
    . . .

CLIENT

private async Task SaveProduceUsageFileOnServer(string beginMonth, string beginYear, string endMonth, string endYear)
{
    string beginRange = String.Format("{0}{1}", beginYear, beginMonth);
    string endRange = String.Format("{0}{1}", endYear, endMonth);
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:52194");
    string dataAsJson = JsonConvert.SerializeObject(_rawAndCalcdDataAmalgamatedList, Formatting.Indented);
    String uriToCall = String.Format("/api/produceusage/{0}/{1}/{2}/{3}", _unit, beginRange, endRange, @dataAsJson);
    HttpResponseMessage response = await client.PostAsync(uriToCall, null);
}

...but the Controller is still not reached; specifically, the breakpoint in "DataTable dt = dtPassedAsJson;" is never reached.

Actually, it kind of surprises me that it doesn't crash, as a string is being passed, yet the data type there declared is "DataTable"

UPDATE 2

I also tried this, after realizing it's not really a stringified/jsonized DataTable that I'm passing from the client, but a stringified/jsonized generic list:

WEB API CONTROLLER

[Route("{unit}/{begindate}/{enddate}/{stringifiedjsondata}")]
[HttpPost]
public void Post(string unit, string begindate, string enddate, List<ProduceUsage> stringifiedjsondata)
{
    List<ProduceUsage> _produceUsageList = stringifiedjsondata;

WebApiConfig.cs

I added this to the Register method:

config.Formatters.Add(new GenericProduceUsageListMediaTypeFormatter());

...and this new class:

// adapted from DataTableMediaTypeFormatter above
public class GenericProduceUsageListMediaTypeFormatter : BufferedMediaTypeFormatter
{
    public GenericProduceUsageListMediaTypeFormatter()
        : base()
    {
        SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("test/dt"));
    }

    public override object ReadFromStream(Type type, Stream readStream,
        HttpContent content, IFormatterLogger formatterLogger, System.Threading.CancellationToken cancellationToken)
    {
        var data = new StreamReader(readStream).ReadToEnd();
        var obj = JsonConvert.DeserializeObject<List<ProduceUsage>>(data);
        return obj;
    }

    public override bool CanReadType(Type type)
    {
        return true;
    }

    public override bool CanWriteType(Type type)
    {
        return true;
    }
}

Still, though, the main breakpointed line in the Controller:

List<ProduceUsage> _produceUsageList = stringifiedjsondata;

...is not reached.

Answer

Amit Kumar Ghosh picture Amit Kumar Ghosh · Jan 6, 2016

or jsonized version of the data (and then unpack it on the other end

I ended up to this -

public class ParentController : ApiController
{
    public string Post(DataTable id)
    {
        return "hello world";
    }
}

in the config

config.Formatters.Add(new DataTableMediaTypeFormatter());

And -

public class DataTableMediaTypeFormatter : BufferedMediaTypeFormatter
{
    public DataTableMediaTypeFormatter()
        : base()
    {
        SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("test/dt"));
    }

    public override object ReadFromStream(Type type, System.IO.Stream readStream,
        System.Net.Http.HttpContent content, IFormatterLogger formatterLogger, System.Threading.CancellationToken cancellationToken)
    {
        var data = new StreamReader(readStream).ReadToEnd();
        var obj = JsonConvert.DeserializeObject<DataTable>(data);
        return obj;
    }

    public override bool CanReadType(Type type)
    {
        return true;
    }

    public override bool CanWriteType(Type type)
    {
        return true;
    }
}

header of my request -
User-Agent: Fiddler
Host: localhost:60957
Content-Type : test/dt
Content-Length: 28

Body -

[{"Name":"Amit","Age":"27"}]