Return progress from Web API [duplicate] - c#

I currently have a web API that
fetches a row of data using FromSqlRaw(...).ToListAsync() within a repository
returns this data as Ok(data.ToArray()) as Task<ActionResult<IEnumerable<MyClass>>> through a controller.
Now I am wondering whether I should or can use IAsyncEnumerable as a return type. The idea was to use this in the repository and the controller. However, in this (now decrepit) thread it states it should not be used. the proposed solution here would be something like:
FromSqlRaw(...).AsNoTracking().AsAsyncEnumerable()
As for the Controller I want keep the response wrapped with ActionResult to explicitly set the return code. However, that currently doesn't seem to work.
Should I just apply the solution for the repository and consume the result as a List in my controller or just keep it as it is?

The IAsyncEnumerable gives you an interface for pull-based asynchronous data retrieval. In other words this API represents an iterator where the next item is fetched asynchronously.
This means that you are receiving the data in several rounds and each in an asynchronous fashion.
Prior IAsyncEnumerable you could use IEnumerable<Task<T>>, which represents a bunch of asynchronous operations with return type T.
Whereas Task<IEnumerable<T>> represents a single asynchronous operation with a return type IEnumerable<T>.
Let's apply these knowledge to a WebAPI:
From an HTTP consumer point of view there is no difference between Task<ActionResult<T>> and ActionResult<T>. It is an implementation detail from users` perspective.
A WebAPI Controller's action implements a request-response model. Which means a single request is sent and a single response is received on the consumer-side.
If a consumer calls the same action again then a new controller will be instantiated and will process that request.
This means that the consumer of your API can't take advantage of IAsyncEnumerable if it is exposed as an action result type.

In .net 6 IAsyncEnumerable handling for MVC was changed when using System.Text.Json:
MVC no longer buffers IAsyncEnumerable instances. Instead, MVC relies on the support that System.Text.Json added for these types.
It means that controller will start sending output immediately and a client may start process it as it receives chunks of the response.
Here is an example with help of new minimal API:
Endpoint binding:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// this endpoint return IAsyncEnumerable<TestData>
app.MapGet("/asyncEnumerable/{count}", (int count) => GetLotsOfDataAsyncEnumerable(count));
// and this one returns Task<IEnumerable<TestData>>
app.MapGet("/{count}", async (int count) => await GetLotsOfDataAsync(count));
app.Run();
Controller methods:
async Task<IEnumerable<TestData>> GetLotsOfDataAsync(int count)
{
var list = new List<TestData>();
for (int i = 0; i < count; i++)
{
await Task.Delay(10);
list.Add(new TestData($"{i}"));
}
return list;
}
async IAsyncEnumerable<TestData> GetLotsOfDataAsyncEnumerable(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(10);
yield return new TestData($"{i}");
}
}
class TestData
{
public string Field { get; }
public TestData(string field)
{
Field = field;
}
}
count path variable allows to control how many data we want to retrieve in a single call.
I've tested it with curl command on a windows machine (here is the answer explaining how to measure performance with curl), results for 100 entries:
/100 /asyncEnumerable/100
time_namelookup: 0.000045s 0.000034s
time_connect: 0.000570s 0.000390s
time_appconnect: 0.000000s 0.000000s
time_pretransfer: 0.000648s 0.000435s
time_redirect: 0.000000s 0.000000s
time_starttransfer: 1.833341s 0.014880s
---------------------
time_total: 1.833411s 1.673477s
Important here to see is time_starttransfer, from curl manpage
The time, in seconds, it took from the start until the first byte was just about to be transferred. This includes time_pretransfer and also the time the server needed to calculate the result.
As you can see /asyncEnumerable endpoint started responding instantly, of course, clients of such endpoints have to be aware of such behavior to make good use of it.
Here how it looks in a cmdline:

Related

Better practice to send a message to Azure Event Hub with Serverless C# funcion

This is my code...
namespaceMyNamespace
{
public class Ping
{
private readonly ILogger<Ping2> _logger;
public Ping2(ILogger<Ping2> log)
{
_logger = log;
}
[FunctionName("Ping2")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "v1/Ping")] HttpRequest req)
{
_logger.LogInformation("Processing: Ping");
if (req.Query["agendaId"] == Microsoft.Extensions.Primitives.StringValues.Empty)
{
return new BadRequestResult();
}
Guid agendaId = Guid.Parse(req.Query["agendaId"]);
string dataTag = "ZZZZ";
string hubName = "myHub";
EventHubProducerClient producerClient;
producerClient = new EventHubProducerClient(connString, hubName);
using EventDataBatch eventBatch = await producerClient.CreateBatchAsync();
MensagemPing data = new MensagemPing() {
ID = Guid.NewGuid(),
agendaId = agendaId,
dataTag = dataTag,
timestamp = DateTime.Now
};
string jsonString = JsonSerializer.Serialize(data);
eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes(jsonString)));
await producerClient.SendAsync(eventBatch);
return new OkResult();
}
}
}
I can't find much documentation about the best way to implemment this. Just to start, I will never had a need to send a message in Batch. I found many examples about how to send a single message to the hub, but in mostly case, are deprecated examples or using classic asp.net application.
Also, this endpoint is taking one and a half second to be executed locally, specially because this snippet here take more than 1 second:
EventHubProducerClient producerClient;
producerClient = new EventHubProducerClient(connString, hubName);
using EventDataBatch eventBatch = await producerClient.CreateBatchAsync();
MensagemPing data = new MensagemPing() {
ID = Guid.NewGuid(),
agendaId = agendaId,
dataTag = dataTag,
timestamp = DateTime.Now
};
string jsonString = JsonSerializer.Serialize(data);
eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes(jsonString)));
I am pretty convinced that this is not the best practice to consume event hubs. Maybe someone can give me some good example or maybe give me good hists about how to improve it and keep it more faster ?
In this scenario, since you're using Azure Functions, you may want to consider using the Event Hubs output binding rather than using the client library directly. The benefit is that it manages the Event Hubs clients for you, including lifetimes and sending patterns.
If you decide to continue to use the client library directly, there are a few suggestions that I'd make:
The producer is intended to be long-lived; I'd strongly suggest creating it as a singleton, either by registering it via DI using the Microsoft.Extensions.Azure library (preferable) or by creating it as a static member of the class.
If you choose to continue creating the producer in the body of the Function, you'll need to close or dispose it. Otherwise, the connection that it owns will be left open until it idles out after ~20 minutes. This will eventually cause socket exhaustion on the host unless your traffic is really, really low.
Creating an explicit batch for a single event doesn't offer much benefit. I'd suggest using the SendAsync overload that accepts an enumerable instead. For example:
await producer.SendAsync(new[] { eventData }, cancellationToken);
Not impactful, but if you'd like to simplify a bit, there's a constructor overload for EventData that accepts a string; no need to manually perform the encoding:
var eventData = new EventData(jsonString);
If you're dealing with a higher call volume, it may be helpful to consider using the EventHubBufferedProducerClient, as it manages the building of batches and sends implicitly to try and maximize throughput. That said, using in a Functions context is awkward and requires more manual configuration in the Function startup in order to manage its lifetime properly and ensure events are flushed at cleanup. It's probably not worth it unless you're seeing a bottleneck on the single event sends, but there's an end-to-end sample that illustrates using it in an ASP.NET host, which is highly similar to Functions.

Does anyone know what a AsyncPageable is?

I'm trying to do a get all from an Azure service and it returns an AsyncPageable. According to the doc it says
A collection of values that may take multiple service requests to iterate over.
Does that mean that it is equal to doing the request for a single item multiple times with a loop?
If a service call returns multiple values in pages it would return Pageable<T>/AsyncPageable<T> as a result. Check out Consuming Service Methods Returning AsyncPageable.
To get more clarity, have a look at below:
This shows control over receiving pages of values from the service use AsyncPageable<T>.AsPages method:
// call a service method, which returns AsyncPageable<T>
AsyncPageable<SecretProperties> response = client.GetPropertiesOfSecretsAsync();
await foreach (Page<SecretProperties> page in response.AsPages())
{
// enumerate through page items
foreach (SecretProperties secretProperties in page.Values)
{
Console.WriteLine(secretProperties.Name);
}
// get continuation token that can be used in AsPages call to resume enumeration
Console.WriteLine(page.ContinuationToken);
}
If your project doesn't have C# 8.0 enabled you can still iterate over AsyncPageable using a while loop:
// call a service method, which returns AsyncPageable<T>
AsyncPageable<SecretProperties> response = client.GetPropertiesOfSecretsAsync();
IAsyncEnumerator<SecretProperties> enumerator = response.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
SecretProperties secretProperties = enumerator.Current;
Console.WriteLine(secretProperties.Name);
}
}
finally
{
await enumerator.DisposeAsync();
}
Check out Azure.Core Response samples to understand more about this.
To change page size, you can use pageSizeHint parameter to AsPages method.

Block Controller Method while already running

I have a controller which returns a large json object. If this object does not exist, it will generate and return it afterwards. The generation takes about 5 seconds, and if the client sent the request multiple times, the object gets generated with x-times the children. So my question is: Is there a way to block the second request, until the first one finished, independent who sent the request?
Normally I would do it with a Singleton, but because I am having scoped services, singleton does not work here
Warning: this is very oppinionated and maybe not suitable for Stack Overflow, but here it is anyway
Although I'll provide no code... when things take a while to generate, you don't usually spend that time directly in controller code, but do something like "start a background task to generate the result, and provide a "task id", which can be queried on another different call).
So, my preferred course of action for this would be having two different controller actions:
Generate, which creates the background job, assigns it some id, and returns the id
GetResult, to which you pass the task id, and returns either different error codes for "job id doesn't exist", "job id isn't finished", or a 200 with the result.
This way, your clients will need to call both, however, in Generate, you can check if the job is already being created and return an existing job id.
This of course moves the need to "retry and check" to your client: in exchange, you don't leave the connection to the server opened during those 5 seconds (which could potentially be multiplied by a number of clients) and return fast.
Otherwise, if you don't care about having your clients wait for a response during those 5 seconds, you could do a simple:
if(resultDoesntExist) {
resultDoesntExist = false; // You can use locks for the boolean setters or Interlocked instead of just setting a member
resultIsBeingGenerated = true;
generateResult(); // <-- this is what takes 5 seconds
resultIsBeingGenerated = false;
}
while(resultIsBeingGenerated) { await Task.Delay(10); } // <-- other clients will wait here
var result = getResult(); // <-- this should be fast once the result is already created
return result;
note: those booleans and the actual loop could be on the controller, or on the service, or wherever you see fit: just be wary of making them thread-safe in however method you see appropriate
So you basically make other clients wait till the first one generates the result, with "almost" no CPU load on the server... however with a connection open and a thread from the threadpool used, so I just DO NOT recommend this :-)
PS: #Leaky solution above is also good, but it also shifts the responsability to retry to the client, and if you are going to do that, I'd probably go directly with a "background job id", instead of having the first (the one that generates the result) one take 5 seconds. IMO, if it can be avoided, no API action should ever take 5 seconds to return :-)
Do you have an example for Interlocked.CompareExchange?
Sure. I'm definitely not the most knowledgeable person when it comes to multi-threading stuff, but this is quite simple (as you might know, Interlocked has no support for bool, so it's customary to represent it with an integral type):
public class QueryStatus
{
private static int _flag;
// Returns false if the query has already started.
public bool TrySetStarted()
=> Interlocked.CompareExchange(ref _flag, 1, 0) == 0;
public void SetFinished()
=> Interlocked.Exchange(ref _flag, 0);
}
I think it's the safest if you use it like this, with a 'Try' method, which tries to set the value and tells you if it was already set, in an atomic way.
Besides simply adding this (I mean just the field and the methods) to your existing component, you can also use it as a separate component, injected from the IOC container as scoped. Or even injected as a singleton, and then you don't have to use a static field.
Storing state like this should be good for as long as the application is running, but if the hosted application is recycled due to inactivity, it's obviously lost. Though, that won't happen while a request is still processing, and definitely won't happen in 5 seconds.
(And if you wanted to synchronize between app service instances, you could 'quickly' save a flag to the database, in a transaction with proper isolation level set. Or use e.g. Azure Redis Cache.)
Example solution
As Kit noted, rightly so, I didn't provide a full solution above.
So, a crude implementation could go like this:
public class SomeQueryService : ISomeQueryService
{
private static int _hasStartedFlag;
private static bool TrySetStarted()
=> Interlocked.CompareExchange(ref _hasStartedFlag, 1, 0) == 0;
private static void SetFinished()
=> Interlocked.Exchange(ref _hasStartedFlag, 0);
public async Task<(bool couldExecute, object result)> TryExecute()
{
if (!TrySetStarted())
return (couldExecute: false, result: null);
// Safely execute long query.
SetFinished();
return (couldExecute: true, result: result);
}
}
// In the controller, obviously
[HttpGet()]
public async Task<IActionResult> DoLongQuery([FromServices] ISomeQueryService someQueryService)
{
var (couldExecute, result) = await someQueryService.TryExecute();
if (!couldExecute)
{
return new ObjectResult(new ProblemDetails
{
Status = StatusCodes.Status503ServiceUnavailable,
Title = "Another request has already started. Try again later.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.4"
})
{ StatusCode = StatusCodes.Status503ServiceUnavailable };
}
return Ok(result);
}
Of course possibly you'd want to extract the 'blocking' logic from the controller action into somewhere else, for example an action filter. In that case the flag should also go into a separate component that could be shared between the query service and the filter.
General use action filter
I felt bad about my inelegant solution above, and I realized that this problem can be generalized into basically a connection number limiter on an endpoint.
I wrote this small action filter that can be applied to any endpoint (multiple endpoints), and it accepts the number of allowed connections:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ConcurrencyLimiterAttribute : ActionFilterAttribute
{
private readonly int _allowedConnections;
private static readonly ConcurrentDictionary<string, int> _connections = new ConcurrentDictionary<string, int>();
public ConcurrencyLimiterAttribute(int allowedConnections = 1)
=> _allowedConnections = allowedConnections;
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var key = context.HttpContext.Request.Path;
if (_connections.AddOrUpdate(key, 1, (k, v) => ++v) > _allowedConnections)
{
Close(withError: true);
return;
}
try
{
await next();
}
finally
{
Close();
}
void Close(bool withError = false)
{
if (withError)
{
context.Result = new ObjectResult(new ProblemDetails
{
Status = StatusCodes.Status503ServiceUnavailable,
Title = $"Maximum {_allowedConnections} simultaneous connections are allowed. Try again later.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.4"
})
{ StatusCode = StatusCodes.Status503ServiceUnavailable };
}
_connections.AddOrUpdate(key, 0, (k, v) => --v);
}
}
}

Web api call in foreach loop - Timeout Error

I am using .NET Core Web API. I am working with some third party web api which I need to call in my Web API (so my .NET Core Web API is kind of wrapper on third party ones).
To get my result, I need to call more then one third party api using foreach loop. Details are as below:
First will be a Web API call which gives me result of around 4000 rows (each row is object of Id and value fields).
After that I need to loop through this 4000 rows and using each Id I need to call another API. On the result of this Web API I need to check some validation and return the valid ones.
I am able to make first Web API call successfully but when I do looping for another API call it gives me timeout error.
I have tried below things
1) making batches of 4000 rows and processing in batches.
2) Adding tasks in `foreach` loop and using `Task.WhenAll`
Example :
var batchSize = 50;
var returnData = new List<Order>();
foreach (var batchedItems in inventoriesList.Batch(batchSize)) //4000 rows
{
var tasks = new List<Task<Order>>();
foreach (var item in batchedItems)
{
tasks.Add(GetOrder(item.Value)); //call to another api
}
foreach (var task in await Task.WhenAll(tasks))
{
returnData.Add(task);
}
}
private async Task<Order> GetOrder(string id)
{
var order = await GetAsync<Order>(api-url);
if (order != null
&& order.IsAvailable == false
&& ValidateOrder(order)))
{
isValidOrder = true;
}
return isValidOrder == true ? order : null;
}
I have tried with LINQ as well rather then doing foreach loop for second API
call. Like below,
tasks = batchedInventories.Select(t => GetOrder(t.Value));
var result = await Task.WhenAll(tasks);
I have also tried with increasing KeepAliveTimeout of Kestrel. But no luck.
Could anybody suggest me correct and working way to do this?
I managed to resolve this error by creating static HttpClient for GetAsync method rather then creating instance for each request. Thanks a ton to John !!
I initialized static HttpClient in constructor with cookiecontainer.
Reference : Using httpclient throughout methods without losing session and cookies

Custom GET action in ASP.NET WebAPI 2 OData Controller

I'm using WebApi2 and OData. I want add custom action, and use it by GET method
GET /odata/Providers(2)/DoSth
but I dont understand how it works exactly. Here is code for one of my controller:
public class ProvidersController : ODataController
{
private Entities db = new Entities();
// GET: odata/Providers
[Queryable]
public IQueryable<PROVIDER> GetProviders()
{
return db.PROVIDER;
}
//... OTHER GENERATED METHODS
//MY TEST METHOD SHOULD BE inoked: GET /odata/Providers(2)/DoSth
public int DoSth()
{
return 22;
}
}
and WebApiConfigFile:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<PROVIDER>("Providers").EntityType.HasKey(o => o.P_ID);
//others entities ...
//my custom action without any parameters, returns int:
ActionConfiguration getTest = builder.Entity<PROVIDER>().Action("DoSth");
getTest.Returns<int>();
Method existing in /odata/$metadata
but cant run this method from the url (still showing 404: "No HTTP resource was found that matches the request URI").
Any ideas how to improve this issue?
In OData an action can only be invoked by the POST method. So just change the request from GET to POST.
If it doesn't work, add an attribute to the method in the controller:
[HttpPost]
public int DoSth()
{
return 22;
}
If you just start to play with OData, I recommend you start from OData V4, which is an OASIS standard. Here is a sample about actions: https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/ODataActionsSample/ .
I solved the problem in a different way... I am not a deep dive programmer only an intermediate... I do however solve problems in any way possible as they arise...
I required a search capability that could not be handled by standard $filter functionality and I needed to return an IQueryable just like any OData 4 controller would (less a get function).
First in my appropriate controller... I took the exact same signature of my Get"Entity" call and added a parameter.
[EnableQuery]
public IQueryable<detail> Getdetails([FromODataUri] int key)
{
return db.masters.Where(m => m.masterid == key).SelectMany(m => m.details);
}
// custom function goes here...
[EnableQuery]
public IQueryable<detail> GetSearchDetails([FromODataUri] int key, [FromODataUri] IEnumerable<int> search)
{
1) do your own function logic here... mine happens to be a very complex search...
2) make sure to return iQueryable as result... I use standard linq queries and then the final return is toList() as IQueryable
3) I also did a simple return search.Count = 0 ? return all results : return queried results.
}
In the WebAPi Config this is the signature;
1) the first line says place the code in the MasterController.
2) the second line tells me what to call the function.
3) the third line tells me what to return.
4) the fourth line tells me what to call the parameter and what type it is...
5) the fifth line is VERY important if you want to avoid having to have to call "://.../namespace.function(param='value')". This removes the dotted namespace constraint. see:this
builder.EntityType<master>()
.Function("GetSearchDetails")
.ReturnsCollectionFromEntitySet<detail>("details")
.CollectionParameter<int>("search");
config.EnableUnqualifiedNameCall(unqualifiedNameCall: true);
This approach solved many of my problems on the client side... Now, i can call h!!p://domain/odata/master(n)/GetSearchDetails(search=[2,10,31]) or if it were an array of string h!!p://domain/odata/master(n)/GetSearchDetails(search=['two','ten','thirtyone']) and it returns an IQueryable just like calling the underlying entity... However, the added benifit is that ALL the standard OData v4 functionality is still there $filter, $select... etc...

Categories

Resources