3

I've got this code in a Web API Controller:

[Route("{unit}/{begindate}/{enddate}")]
[HttpPost]
public void Post(string unit, string begindate, string enddate, 
    [FromBody] string stringifiedjsondata)
{
    List<ProduceUsageSPResults> _produceUsageList = JsonConvert.DeserializeObject<List<ProduceUsageSPResults>>(stringifiedjsondata);
    string _unit = unit;
    string _begindate = String.Format("{0}01", HyphenizeYYYYMM(begindate));
    string _enddate = GetEndOfMonthFor(enddate);
    string appDataFolder =  
HttpContext.Current.Server.MapPath("~/App_Data/");
    string htmlStr = ConvertProduceUsageListToHtml(_produceUsageList);
    string htmlFilename = string.Format("produceUsage_{0}_{1}_{2}.html", 
_unit, _begindate, _enddate); 
    string fullPath = Path.Combine(appDataFolder, htmlFilename);
    File.WriteAllText(fullPath, htmlStr);
}

It works fine when passing only a little bit of (contrived) data from the Client (Winforms app) via the "[FromBody]" arg, like so:

private async Task SaveProduceUsageFileOnServer(string beginMonth, string beginYear, string endMonth, string endYear, DataTable _dtUsage)
{
    string beginRange = String.Format("{0}{1}", beginYear, beginMonth);
    string endRange = String.Format("{0}{1}", endYear, endMonth);
    HttpClient client = new HttpClient {BaseAddress = new Uri("http://localhost:42176")};
    string dataAsJson = "[{\"ItemDescription\": \"DUCKBILLS, GRAMPS-EIER 70CT  42#\",\"PackagesMonth1\": 1467}]";
    //string dataAsJson = JsonConvert.SerializeObject(_dtUsage);
    String uriToCall = String.Format("/api/produceusage/{0}/{1}/{2}", _unit, beginRange, endRange);
    var content = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("", dataAsJson)
    });
    HttpResponseMessage response = await client.PostAsync(uriToCall, content);
}

However, if I reverse the comments on the assignment to dataAsJson so that it sends the real data, like so:

//string dataAsJson = "[{\"ItemDescription\": \"DUCKBILLS, GRAMPS-EIER 70CT  42#\",\"PackagesMonth1\": 1467}]";
string dataAsJson = JsonConvert.SerializeObject(_dtUsage);

...it fails; there is no err msg; the DataTable (_dtUsage) is serialized to json just fine with the JsonConvert.SerializeObject() method; it's just that there's a "lot" of data (a few thousand records). The client's call to PostAsync() is never reached, and so the server's Post method is never reached.

Is there a workaround that will allow this to succeed when sending more than just a trivial amount of data? I don't really like passing that much data over the wire (currently the client and server are both running locally, but thinking about the situation once deployed), but the other option is to execute the same Stored Procedure from both the client (to generate Excel spreadsheet files there) and from the server (to convert the data to HTML). It seems to be one of those "Catch-22" situations ("impounded if I do, sequestered if I don't").

UPDATE

Testing out user3093073's idea, I added this to the system.webServer section of Web.config:

<security>
  <requestFiltering>
    <requestLimits>
      <headerLimits>
        <add header="Content-type" sizeLimit="10000000" />
      </headerLimits>
    </requestLimits>
  </requestFiltering>
</security>

(based on what I found here, but it's still not working...

UPDATE 2

Closer to user user3093073's answer, I also tried this:

<requestFiltering>
   <requestLimits maxAllowedContentLength="10000000">
     <!--<headerLimits>
       <add header="Content-type" sizeLimit="100" />
     </headerLimits>-->
   </requestLimits>
</requestFiltering>

...also to no avail.

UPDATE 3

Note that I put the code above in every Web.config file in the site, namely:

The Web.config file below the \[ProjectName]\Views folder
The Web.config file below the \[ProjectName] folder
The two Web.config files below the \[ProjectName]\Web.config file, namely "Web.Debug.config" and "Web.Release.config"

...or, another way of viewing their locations:

\PlatypusReports\Web.config
\PlatypusReports\Web.config\Web.Debug.config
\PlatypusReports\Web.config\Web.Release.config
\PlatypusReports\Views\Webconfig

UPDATE 4

After being away from this for several days and coming back to it, it now seems plain to me that the "real" data I was trying to pass is a different animal than the phony/test data, which worked.

The test data was a collection of a simple KeyValuePair. The real data, though, is more complex. So when I tried to convert that complex data to a simple KeyValuePair, trouble was sure to follow. So instead of this:

new KeyValuePair<string, string>("", dataAsJson)

...I need something like this:

new List<DeliveryPerformanceClass>("", dataAsJson)

...but that is not working either; I get several compile errors, such as, "'System.Collections.Generic.List' does not contain a constructor that takes 2 arguments'"

If I remove the first arg so that it's:

new List<DeliveryPerformanceClass>(dataAsJson)

...I get other compile errors, such as, "Argument 1: cannot convert from 'string' to 'int'"

B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • _"it fails; there is no error message"_ - then you need to do more debugging. Code either runs to completion or throws an exception. That you don't see an exception doesn't mean there isn't one. Show for example how you call `SaveProduceUsageFileOnServer()`. – CodeCaster Feb 01 '16 at 19:15
  • Stepping through it, there's never any indication of a problem. – B. Clay Shannon-B. Crow Raven Feb 01 '16 at 19:16
  • 1
    That doesn't mean there isn't any. So isolate the issue. Call `JsonConvert.SerializeObject(_dtUsage);` in a try-catch block separate from this method, inspect the result. Read the exception message, or check how large the resulting string is and how long it takes the library to create that serialized representation. That kind of stuff. – CodeCaster Feb 01 '16 at 19:17
  • 1
    What version of Visual Studio are you debugging with? You can set the exception settings to catch more than what it does by default. If you are using Visual Studio 2015, Debug->Windows-> Exception Settings, and check all of them ,and run it to see if you get an error. – Dylan Feb 03 '16 at 19:23
  • How big is the data you plan to send? – Noel Feb 03 '16 at 19:28
  • @Noel: Not huge, but it can be up to a few thousand records - all text and ints and floats and such, no BLObs. – B. Clay Shannon-B. Crow Raven Feb 03 '16 at 19:31
  • @Dylan: VS 2013; there is a Debug > Exceptions... there. – B. Clay Shannon-B. Crow Raven Feb 03 '16 at 19:32
  • What is rough size of the data? An order of magnitude estimate would work (1MB? 10MB? 100MB?). – Noel Feb 03 '16 at 19:57
  • @Noel: I have no idea about that, but I doubt it would be more than 1 MB, usually considerably less. The typical amount fits on a spreadsheet with a dozen columns and a few hundred rows at most - all text and numeric data. – B. Clay Shannon-B. Crow Raven Feb 03 '16 at 19:59
  • 1
    I agree with @CodeCaster. If you set a breakpoint on `await client.PostAsync(uriToCall, content);`, and the breakpoint never gets hit, there is probably an exception being thrown. Enable first chance exceptions by pressing CTRL + ALT + E, and checking the CLR "thrown" checkbox (see this screenshot: http://imgur.com/aamRNlZ). Then debug the client and report back any exceptions. – Noel Feb 03 '16 at 20:20
  • I don't really think a bounty is going to help here. This isn't answerable by anyone else. **You** will need to do the debugging, to which you now got multiple pointers. Did you already put `JsonConvert.SerializeObject(_dtUsage)` in its own try-catch block and inspect the exception, which I'm fairly sure there is? 99% of times it's a circular reference, causing JSON.NET to be unable to serialize your object. – CodeCaster Feb 03 '16 at 20:22
  • This does not pertain to your question specifically but, why do you receive your JSon data as a string and deserialize it on the controller? If you receive the paramater as `List ` the model binder should take care of the deserialization – andyroschy Feb 04 '16 at 20:50

2 Answers2

2

I believe that since you have already specified the <requestFiltering> settings, you then need to also set the maxRequestLength. As @brewsky mentions, this is defaulted it is only 4MB. Check your web.config. This is setup for 2GB

<configuration>
    <system.web>
        <httpRuntime maxRequestLength="2147483648" />
    </system.web>
</configuration>

This SO answer does seem to address a similar issue.

Community
  • 1
  • 1
David Pine
  • 23,787
  • 10
  • 79
  • 107
0

You have to setup IIS to accept big files. By default it is only 4MB or so. Check your web.config. This is setup for 2GB:

        <requestLimits maxAllowedContentLength="2147483648" />
brewsky
  • 607
  • 1
  • 9
  • 15