Using ASP.NET WebAPI 2.0 and have a conceptual issue.
Would like to keep a global record of any API that is called by any user/ client and it would be awesome if this was stored in the database.
What would be the best mechanism to accomplish this?
I' using a DelegatingHandler for a long time in several projects which is doing just fine.
public class ApiCallLogHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var started = DateTime.UtcNow;
HttpResponseMessage response = null;
Exception baseException = null;
try {
response = await base.SendAsync(request, cancellationToken);
} catch(Exception exception) {
CommonLogger.Logger.LogError(exception);
baseException = exception;
}
try {
var callModel = await GetCallModelAsync(request, response);
if(baseException != null)
callModel.Exception = baseException
callModel.ExecutionTime = (DateTime.UtcNow - started).ToString();
await CommonLogger.Logger.LogApiCallAsync(callModel);
} catch (Exception exception) {
CommonLogger.Logger.LogError(exception);
}
return response;
}
private async Task<ApiCallModel> GetCallModelAsync(HttpRequestMessage request, HttpResponseMessage response) {
// parse request and response and create a model to store in database...
}
}
By this approach you are able to track all requests, exceptions during execution, and even full-response of each API call.
ApiCallModel is just a simple POCO class which you should fill it with your required data from request and response.
CommonLogger.Logger.* is your logging mechanism.
And, you have to register the handler with this snippet:
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MessageHandlers.Add(new ApiCallLogHandler());
}
}
Related
I have a simple controller in ASP.NET Core that proxies images from the web and provides a resized version when query parameters are passed.
Some of the source images do not have a Content-Length and Transfer-Encoding: chunked, but...
The base class FileResultExecutorBase does not set the Transfer-Encoding header, even though the ContentLength can be null. My understanding is, that this header is necessary when Content-Length is not provided.
When I set the header manually it is ignored.
My sample code:
class Controller
{
public async Task<IActionResult> Proxy(string url)
{
var response = await httpClient.GetAsync(url);
return new FileCallbackResult("", (stream, context) =>
{
await using (var sourceStream = await response.Content.ReadAsStreamAsync())
{
await sourceStream.CopyToAsync(stream);
}
};
}
}
public delegate Task FileCallback(Stream body, HttpContext httpContext);
public sealed class FileCallbackResult : FileResult
{
public FileCallback Callback { get; }
public FileCallbackResult(string contentType, FileCallback callback)
: base(contentType)
{
Callback = callback;
}
public override Task ExecuteResultAsync(ActionContext context)
{
var executor = context.HttpContext.RequestServices.GetRequiredService<FileCallbackResultExecutor>();
return executor.ExecuteAsync(context, this);
}
}
public sealed class FileCallbackResultExecutor : FileResultExecutorBase
{
public async Task ExecuteAsync(ActionContext context, FileCallbackResult result)
{
var (_, _, serveBody) = SetHeadersAndLog(context, result, result.FileSize, result.FileSize.HasValue);
if (result.FileSize == null)
{
context.HttpContext.Response.Headers[HeaderNames.TransferEncoding] = "chunked";
}
if (serveBody)
{
var bytesRange = new BytesRange(range?.From, range?.To);
await result.Callback(
context.HttpContext.Response.Body,
context.HttpContext);
}
}
}
It works fine locally, but I deploy it to our kubernetes cluster with istio, the requests cannot be served. I guess that istio or another proxy does not like the header combination. I have not found anything in the logs yet.
I have created a generic try/catch method on base API on a net core 2.2 project, and I am not sure about perfomance of this generic method. Is this a good way to do it?
This is on base api:
protected async Task<IActionResult> TryReturnOk<TReturn>(Func<Task<TReturn>> function)
{
try
{
var result = await function();
return Ok(result);
}
catch (Exception ex)
{
_fileLogger.LogError(ex.Message);
_fileLogger.LogError(ex.StackTrace);
return BadRequest(ex);
}
}
And it is used on post method in the api-s like:
public async Task<IActionResult> Post([FromBody] LogViewModel log)
{
return await TryReturnOk(() => _writeLogService.WriteLog(log));
}
Instead of cluttering up all your controllers, I would centralized logging to middleware like below.
400 Bad Request should be used when e.g. request model is not valid. When an exception is thrown, 500 is more appropriate.
public class LoggerMiddleware
{
private readonly ILogger _fileLogger;
private readonly RequestDelegate _next;
public LoggerMiddleware(RequestDelegate next, ILogger fileLogger)
{
_next = next;
_fileLogger = fileLogger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
_fileLogger.LogError(ex.Message);
_fileLogger.LogError(ex.StackTrace);
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}
}
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseLoggerMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoggerMiddleware>();
}
}
In Startup#Configure
app.UseLoggerMiddleware()
I have asp core app. I use IHostedService for periodic task. I need send periodic http request. This is my code:
class MyService: IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public MyService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
private async void DoWork(object state)
{
using (var scope = _scopeFactory.CreateScope())
{
var clientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = clientFactory.CreateClient();
var response = await client.SendAsync(request);
...
}
}
}
Also I added it in my Startup.cs
services.AddHttpClient();
This is working. But I'm not sure if this is the best performance solution. Any tips?
I am new to the asp core. This is an example that I found on the net. how do i do it right?
If you want to run background service to send the request in asp.net core, I suggest you could directly inject the httpClientFactory instead of using ServiceProvider.GetRequiredService to inject it again.
More details, you could refer to below codes:
Make sure you have add the AddHttpClient service into the startup.cs
services.AddHttpClient();
2.Add below codes into the worker.cs:
public class Worker : IHostedService
{
private IHttpClientFactory httpClientFactory;
public Worker(IHttpClientFactory _httpClientFactory) {
httpClientFactory = _httpClientFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
DoWork("test");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private async void DoWork(object state)
{
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:9012/");
//request.Headers.Add("Accept", "application/vnd.github.v3+json");
//request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = httpClientFactory.CreateClient();
var response = await client.SendAsync(request);
int i = 0;
}
}
Result:
More details about how to use asp.net core background service, you could refer to this article.
I'm trying to write very simple multi-platform app (iOS & Android) in visual studio. This app uses web service, uploaded on my web hosting.
This is code that call WebAPI (get & post):
async void post_async(object sender, System.EventArgs e)
{
Console.WriteLine("POST");
try
{
HttpClient httpClient = new HttpClient();
var BaseAddress = "https://mywebsite";
var response = await httpClient.PostAsync(BaseAddress, null);
var message = await response.Content.ReadAsStringAsync();
Console.WriteLine($"RESPONSE: " + message);
}
catch (Exception er)
{
Console.WriteLine($"ERROR: " + er.ToString());
}
}
async void get_async(object sender, System.EventArgs e)
{
try
{
HttpClient httpClient = new HttpClient();
var BaseAddress = "https://mywebsite";
var response = await httpClient.GetAsync(BaseAddress);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"RESPONSE: " + content);
}
}
catch (Exception er)
{
Console.WriteLine($"ERROR: " + er.ToString());
}
}
This is very simple code for Web Api:
[HttpGet]
public ActionResult<string> Get()
{
return "get method ok";
}
[HttpPost]
public ActionResult<string> Post()
{
return "post method ok";
}
Very strange issue because for every void I always obtain "get method ok". So "get" is ok, but I don't understand why I cannot call post method.
I tried to use Postman: same issue.
I'm using this very simple code:
[ActionName("getmet")]
public ActionResult<string> getmet()
{
return "get method ok";
}
[ActionName("postmet")]
public ActionResult<string> postmet()
{
return "post method ok";
}
Now of course I can call https://mywebsite/getmet or postmet and it works using postman.
If I use [HttpPost] for postmet method, on Postman I get "404 not found". Why?
var BaseAddress = "https://mywebsite"; //
URL hits get method by difualt, thats means works as https://mywebsite/Get where as your actual post method URL is https://mywebsite/Post
instead of calling a different method use code like below.
[HttpPost]
[HttpGet]
public ActionResult<string> Get()
{
return "method ok";
}
OR you can use API ROUTE
OR
[AcceptVerbs(HttpVerbs.Get|HttpVerbs.Post)]
How to setup a .NET core web api that
accepts a string value,
puts into a queue
and return flag that message is accepted (regardless it is processed).
Also, a routine which keeps checking the queue, and process the messages one by one.
As per the requirement, the api is going to act as the receiver of messages which may get hits as much as hundreds of times in a minute, while the messages it receives should be processed one by one.
I am bit new to web apis, so wonder if such setup is good to have and if yes how to put together different components.
Thanks in advance..
Honestly, I don't think that it makes sense to receive and process messages in one process, so I would recommend to use external messaging system like RabbitMQ or Kafka or any other existing system of your preference, where you can put your messages and another process would consume it. It's quite big topic, you can start from this tutorial
If you still want to have it in one process it's also possible, you can create a background task queue, put there your messages and create background task which will consume them from that queue.
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Background task:
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(
CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
Registration:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
}
Inject to controller:
public class ApiController
{
private IBackgroundTaskQueue queue;
public ApiController(IBackgroundTaskQueue queue)
{
this.queue = queue;
}
public IActionResult StartProcessing()
{
queue.QueueBackgroundWorkItem(async token =>
{
// put processing code here
}
return Ok();
}
}
You can modify BackgroundTaskQueue to fit your requirements, but I hope you understand the idea behind this.
Update for latecomers....
I used in Asp.net core 6, you can download sample here: https://learn.microsoft.com/en-us/dotnet/core/extensions/queue-service
Config Program
// and more...
#region Worker Services
builder.Host.ConfigureServices((context, services) =>
{
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(_ =>
{
if (!int.TryParse(context.Configuration["QueueCapacity"], out var queueCapacity))
{
queueCapacity = 100;
}
return new BackgroundTaskQueue(queueCapacity);
});
});
#endregion
#region App
// App config
var app = builder.Build();
// Monitor worker config
var monitorLoop = app.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseHttpsRedirection();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
#endregion
In Controller
// and more..
private readonly IMailService _mailService;
private readonly IBackgroundTaskQueue _queue;
// and more..
public AuthenticateController(
IMailService mailService,
IBackgroundTaskQueue queue)
{
_mailService = mailService;
_queue = queue;
}
[HttpPost]
[Route("forgot-password")]
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordModel model)
{
// and more...
// Queue processing
await _queue.QueueBackgroundWorkItemAsync(async (token) =>
{
await _mailService.SendAsync(mailData, token);
});
return Ok();
}
Hope this help !