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

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.

Upvotes: 1

Views: 18029

Answers (4)

See Hernan Guzman's answer here.

Basically, you have to add "[FromBody]" to the method on the server, and then pass the data from the client, adding it after the URL param.

Upvotes: 0

Amit Kumar Ghosh
Amit Kumar Ghosh

Reputation: 3726

The client is actually passing a data table in form of json, and then according to the special media type webapi runtime converts the json to a data table again at server.

Upvotes: 1

Mr Moose
Mr Moose

Reputation: 6344

I have done this once before although the code has now been superseded so I am only able to get bits and pieces from my TFS history.

From my console app, I would post the data (was a DataTable which I converted to a POCO) as follows;

            using (HttpClient httpClient = new HttpClient())
            {
                MyDataType data = BogusMethodToPopulateData();

                httpClient.BaseAddress = new Uri(Properties.Settings.Default.MyService);
                httpClient.DefaultRequestHeaders.Accept.Clear();
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                HttpResponseMessage response;

                // Add reference to System.Net.Http.Formatting.dll
                response = await httpClient.PostAsJsonAsync("api/revise", data);

                if (response.IsSuccessStatusCode)
                {
                    Console.WriteLine("File generation process completed successfully.");
                }
            }

On the server side, I had the following. The concept here was largely based on the Sending Complex Types section of the linked post. I know you specifically are looking at DataTables, but I'm sure you could mess about with the examples or extract your data into a POCO;

    // https://damienbod.wordpress.com/2014/08/22/web-api-2-exploring-parameter-binding/
    // http://www.asp.net/web-api/overview/advanced/sending-html-form-data,-part-1
    // http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
    [POST("revise")]
    public IEnumerable<Revised_Data> Revise(MyDataType data)
    {
        if (ModelState.IsValid && data != null)
        {
            return ProcessData(data.year, data.period, data.DataToProcess).AsEnumerable();
        }
        return null;
    }

Upvotes: 1

Amit Kumar Ghosh
Amit Kumar Ghosh

Reputation: 3726

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"}]

Upvotes: 1

Related Questions