Improve performance by using Linq methods - c#

I have used the following code to retrieve the content of a JSON feed and as you see I have used the paging techniques and Skip and Take methods like this. My paging works fine, however I can't see any performance improvement and I know this is because every time I request a new page it calls the API again and again and after retrieving all contents, it filters it using Skip and Take methods, I am looking for a way to apply the Skip and Take methods with my HttpClient so that it only retrieves the needed records for every page. Is it possible? If so, how??:
[HttpGet("[action]")]
public async Task<myPaginatedReturnedData> MyMethod(int page)
{
int perPage = 10;
int start = (page - 1) * perPage;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("externalAPI");
MediaTypeWithQualityHeaderValue contentType =
new MediaTypeWithQualityHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(contentType);
HttpResponseMessage response = await client.GetAsync(client.BaseAddress);
string content = await response.Content.ReadAsStringAsync();
IEnumerable<myReturnedData> data =
JsonConvert.DeserializeObject<IEnumerable<myReturnedData>>(content);
myPaginatedReturnedData datasent = new myPaginatedReturnedData
{
Count = data.Count(),
myReturnedData = data.Skip(start).Take(perPage).ToList(),
};
return datasent;
}
}
If there isn't anyway to do this, I can retrieve the full collection from the external api and moving it to my front-end (Anguar) to page it there, by using this way the data is transferred just once and then paged at the client side that seems to be very better than making the external API to return a full set of data to my server every time the client changes the page. Is this correct and server side paging useless for me in this case?

Related

"Pass through" controller action (gets and returns JSON) in .NET Core 3.1

Someone's probably done this before but I can't seem to formulate the question properly to find results. I want to make AJAX calls from a view, but I can't directly call the external API from javascript because there's a key that I can't expose. My idea is to have another controller action that I call from the page that calls the actual external REST API I want to get data from and just passes it on as a JSON. I see lots of examples of getting a JSON through C# and deserializing it but not many where you get a JSON and then return it and consume it from the view. Any help appreciated.
public JsonResult GetStuff()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("Stuff/?Id=" + id).Result;
*code to take response and pass it on as a JSON that I can consume from Javascript
}
Here is what I recommend.
[HttpGet("myapi/{id}");
public async Task MyApi(int id) {
// Replace these lines as needed to make your API call properly.
using HttpClient client = new() {
BaseAddress = REMOTE_SERVER_BASE
}
// Make sure to properly encode url parameters if needed
using HttpResponseMessage response = await client.GetAsync($"myapi/{id}");
this.HttpContext.Response.StatusCode = (int)response.StatusCode;
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers) {
this.HttpContext.Response.Headers[header.Key] = new StringValues(header.Value.ToArray());
}
await response.Content.CopyToAsync(this.HttpContext.Response.Body);
}
This will copy all the common response fields such as status code, headers, and body content, over to your response.
This code isn't tested so you might have to tweak it a bit but it should be enough to get you started.

Microsoft Project Online OData asynchronous query

I am trying to use Microsoft Project OData by querying data in C#. I am having performances issues with delays around 1s for each query. I am trying to query 2 information at once using that method :
public static async Task<string> ReadXml(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.Credentials = Credentials; // SharePointOnlineCredentials
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
using (var response = (HttpWebResponse)await request.GetResponseAsync())
using (var stream = response.GetResponseStream())
using (var reader = new System.IO.StreamReader(stream))
{
var xml = await reader.ReadToEndAsync();
return xml;
}
}
It works fine if I call it and always wait for it to end before calling it again, but I never receive any response from the WebRequest if I call it multiple times at once :
// just an example. I usually put a condition to filter for the tasks of a single project
var query1 = ReadXml(#"https://something.sharepoint.com/sites/pwa/_api/ProjectData/Projects");
var query2 = ReadXml(#"https://something.sharepoint.com/sites/pwa/_api/ProjectData/Tasks");
Task.WaitAll(query1, query2);
If I "await" the first one and then do the second one it works fine, but not with the code above. And this is assuming there is < 300 tasks in the project, if more than that I have to query them in chunk of 300 leading to 4 or 5 seconds for the entire query since I can't to them all at once!
Is there a way to send multiple request at the same time ?
I am able to do it by simply entering the url in multiple chrome tabs really fast / have faster responses. I don't understand why it doesn't work with my code!
Thanks,
According to the following post Webrequest.Create could be the problem, it uses an internally blocking method C# Thread UI is getting blocked | Possible reason WebRequest.Create?.
The code below uses the newer HttpClient and shouldn't have this issue.
public static HttpClient _HttpClient { get; } = new HttpClient(new HttpClientHandler { Credentials=new NetworkCredential("","")});
public static async Task<string> ReadXml(string url)
{
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, url))
{
requestMessage.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
using (var response = await _HttpClient.SendAsync(requestMessage))
{
return await response.Content.ReadAsStringAsync();
}
}
}

API is getting hit twice while returning audio file

i am returning an audio file from web api. requirement is to play the media file instead of downloading,i am using this code.
[HttpGet]
[Route("audiofile/download", Name = "GetAudioFile")]
public HttpResponseMessage GetAudioFile(string q)
{
if (string.IsNullOrWhiteSpace(q))
{
return new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest };
}
String path = HttpContext.Current.Server.MapPath("~/AudioUploads/");
string filePath = Path.Combine(path, q);
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(filePath))
};
var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return response;
}
i noticed that this action method is being hit twice as like
Can anyone suggest why its happening? why my api method is being called twice?
P.S I am using Url.Link in order to make uploaded file url. when i hit that, api method is called twice.
Servers only respond to requests. They can't initiate a communication with a client, without initial request.
That said, your client code is to blame here, as it's sending two requests instead of one, and the server correctly responds to both.

C# consume a web api with multiple http POST requests

So I have 2 http Post requests consuming a web api as follow:-
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://your_url.com:8443/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// 1st request //
var datatobeSent = new ApiSendData()
{
UserID = "xxx",
UserPsw = "yyy",
ApiFunc = "zzz",
clearData = "x1y1z1"
};
HttpResponseMessage response = await client.PostAsJsonAsync("WebApi", datatobeSent);
var resultData = await response.Content.ReadAsStringAsync();
#region Extract Secured Data response from WebApi
JObject JsonObject = JObject.Parse(resultData);
datatobeSent.SecureData = (string)JsonObject["SecureResult"];
#endregion
// 2nd request //
var datatobeSent2 = new ApiSendData()
{
UserID = "xxx",
UserPsw = "yyy",
ApiFunc = "zzz",
SecureData = datatobeSent.SecureData
};
HttpResponseMessage response2 = await client.PostAsJsonAsync("WebApi", datatobeSent2);
var resultData2 = await response2.Content.ReadAsStringAsync();
}
So now I need some clarifications...
1) Are both my http POST requests sent over the same SSL session ?
2) If they are not, then how can I combine the two and send the 2 requests over a single connections/session ?
3) How may I improve performance of this code? currently 100 requests take 11secs to process and respond. ( i just used a for loop that counts 100 http post requests for the above 2 samples)
They are over the same SSL session and connection. The same HttpClient instance shares some configurations and underlying TCP connections. Therefore, you should reuse the same instance, which you are already doing with the using statement.
I would try to improve the performance of your code by asynchronously making the post requests and processing the results. Here's an option:
Create a new class to handle these async requests
public class WebHelper
{
public async Task<string> MakePostRequest(HttpClient client, string route, object dataToBeSent)
{
try{
HttpResponseMessage response = await client.PostAsJsonAsync(route, datatobeSent);
string resultData = await response.Content.ReadAsStringAsync();
return resultData;
}
catch (Exception ex){
return ex.Message;
}
}
}
Notice that the same httpClient instance is being used. In the main code, you could test your performance like this (to simplify our test, I'm just making the post request with the same parameters 101 times):
//Start time measurement
List<Task> TaskList = new List<Task>();
for (int i = 0; i < 100; i++)
{
Task postTask = Task.Run(async () =>
{
WebHelper webRequest = new WebHelper();
string response = await webRequest.MakePostRequest(client, "WebApi", dataToBeSent);
Console.WriteLine(response);
});
TaskList.Add(postTask);
}
Task.WaitAll(TaskList.ToArray());
//end time measurement
And just an improvement for your code: use try/catches to make the requests!
While HttpClient will aim to use the same Ssl session and connection, this cannot be guaranteed as it depends on the server also maintaining the session and connection. If the server drops them, HttpClient will renegotiate new connections and sessions transparently.
That said, while there is some overhead from connection and ssl session establishment, it's unlikely to be the root cause of any performance issues.
Given the simplicity of your code above, I strongly suspect that the performance issue is not in your client code, but in the service you are connecting to.
To determine that, you need to isolate the performance of that service. A few options for how to do that, depending on the level of control you have over that service:
If you are the owner of that service, carry out over the wire performance testing directly against the service, using a tool like https://www.blitz.io/. If 100 sequential requests from blitz.io takes 11 seconds, then the code you have posted is not the culprit, as the service itself just has an average response time of 110ms.
If you are not the owner of that service, use a tool like Mountebank to create an over the wire test double of that service, and use your existing test loop. If the test loop using Mountebank executes quickly, then again, the code you have posted is not the culprit. (Mountebank supports https, but you will need to either have the keypair, or use their self-signed cert and disable certificate verification in your client.)
It's worth noting that it's not unusual for complex web services to take 110ms to respond. Your test appears to be doing a sequential series of requests - one request at a time - most web services are optimised around handling many requests from independent users in parallel. The scalability challenge is in terms of how many concurrent users can I service while still ensuring that the average response time is 110ms. But if only a single user is using the service at a time, it will still take ~110ms.
So - an even earlier validation step you could take is to work out if your test of 100 sequential requests in a loop is a valid representation of your actual performance requirements, or if you should call your method 100 times in parallel to see if you can have 100 concurrent users access your service.

Web Api 2 returning data without formatting it async

After fetching data from the database, I serialize it into XML.
I then write that XML into a Redis caching instance as a string.
I want the endpoint to check if data exists in the cache and based on the result either return the data from the cache, or hit the DB, cache the data and then return that.
My code works just fine when executed synchronously:
Working Synchronous Code
[HttpGet]
public IHttpActionResult Test(int userId)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
var data = Cache.StringGet("Cart:" + userId);
if (string.IsNullOrEmpty(data))
{
// Grab data from DB and format as XML
Cache.StringSet("Cart:" + userId, data, TimeSpan.FromHours(2));
}
response.Content = new StringContent(data);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
return new ResponseMessageResult(response);
}
Where everything goes bananas is when trying to make it Asynchronous.
Broken Async Code
( I included the smallest amount of code necessary to reproduce the issue )
[HttpGet]
public async Task<HttpResponseMessage> TestAsync(int userId)
{
var data = await Cache.StringGetAsync("Cart:" + userId);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent("<test>Test</test>");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
return response;
}
Note that in this example above I'm not even accessing the asynchronously loaded data. If I comment out the await line, things start working again. It only fails if a await is in the code.
The problem that occurs is that 50% of the time, requests to the endpoint just ... stall and never finish. Fiddler screenshot attached to highlight the issue.
Finally, if there is a easier way to skip media formatting and content negotiation, i'd be more than happy to change my approach.
I should add that the service that will consume this endpoint only supports XML, and it made no sense to me to deserialize and reserialize on every request.
Problem Solved!
It ended up being Azure Application Insights.
I guess it does not fully support async or has issues with async in combition with manually creating HttpResponseMessages.
Thank you all for the responses.

Categories