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.
Related
I wrote a controller. I wrote it according to the web api I will use here. But how should I make my own created api?
Do I need to have my own created api where I write with HttpPost? I might be wrong as I am new to this.
public class GoldPriceDetailController : Controller
{
string Baseurl = "https://apigw.bank.com.tr:8003/";
public async Task<ActionResult> GetGoldPrice()
{
List<GoldPrice> goldPriceList = new List<GoldPrice>();
using (var client = new HttpClient())
{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Sending request to find web api REST service resource GetDepartments using HttpClient
HttpResponseMessage Res = await client.GetAsync("getGoldPrices");
Console.WriteLine(Res.Content);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
var ObjResponse = Res.Content.ReadAsStringAsync().Result;
goldPriceList = JsonConvert.DeserializeObject<List<GoldPrice>>(ObjResponse);
}
//returning the student list to view
return View(goldPriceList);
}
}
[HttpPost]
public async Task<IActionResult> GetReservation(int id)
{
GoldPrice reservation = new GoldPrice();
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.GetAsync("https://apigw.bank.com.tr:8443/getGoldPrices" + id))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResponse = await response.Content.ReadAsStringAsync();
reservation = JsonConvert.DeserializeObject<GoldPrice>(apiResponse);
}
else
ViewBag.StatusCode = response.StatusCode;
}
}
return View(reservation);
}
}
Basically you need these steps:
HttpClient for local API.
HttpClient for external API.
Local API controller.
Inject external client into the local API.
Then inject the local API client into the razor page/controller.
HttpClient for Local API
public class LocalApiClient : ILocalHttpClient
{
private readonly HttpClient _client;
public LocalAPiClient(HttpClient client)
{
_client = client;
_client.BaseAddress(new Uri("https://localapi.com"));
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
// logic to get prices from local api
var response = await _client.GetAsync($"GetGoldPrices?id={id}");
// deserialize or other logic
}
}
HttpClient for External API
public class ExternalApiClient : IExternalHttpClient
{
private readonly HttpClient _client;
public ExternalAPiClient(HttpClient client)
{
_client = client;
_client.BaseAddress(new Uri("https://externalApi.com"));
// ...
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
// logic to get prices from external api
var response = await _client.GetAsync("getGoldPrices?id=" + id))
}
}
Register your clients in startup
services.AddHttpClient<ILocalHttpClient, LocalHttpClient>();
services.AddHttpClient<IExternalHttpClient, ExternalHttpClient>();
Create Local API Controller
and inject the external http client into it
[ApiController]
public class LocalAPIController : Controller
{
private readonly IExternalHttpClient _externalClient;
public LocalAPIController(IExternalHttpClient externalClient)
{
_externalClient = externalClient;
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
var resoponse = await _externalClient.GetGoldPrices(id);
// ...
}
}
Inject the local client into razor page/controller
public class HomeController : Controller
{
private readonly ILocalHttpClient _localClient;
public HomeController(ILocalHttpClient localClient)
{
_localClient = localClient;
}
public async Task<IActionResult> Index(int id)
{
var response = await _localClient.GetGoldPrices(id);
// ...
}
}
I recently upgraded my project from Asp.Net Core 3.0 to the latest build Asp.Net Core 3.1.0 preview 3.
My project is running Blazor in the client, and I also intercept the pipeline to add a Jwt to the header of every request sent back to the server using my JwtTokenHeaderHandler which extends DelegatingHandler.
I add this in the startup.cs of my blazor.client project as follows;
services.AddSingleton<HttpClient>(s =>
{
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var uriHelper = s.GetRequiredService<NavigationManager>();
var myHandler = s.GetRequiredService<JwtTokenHeaderHandler>();
myHandler.InnerHandler = new WebAssemblyHttpMessageHandler();
return new HttpClient(myHandler)
{
BaseAddress = new Uri(uriHelper.BaseUri)
};
});
However, since the upgrade it appears that WebAssemblyHttpMessageHandler() is no longer available?
I cannot find any documentation as to how to fix this approach.
Can anyone advise on how I can correct this please?
Update based on Aqua's answer
After adding the following code to my Startup.cs;
services.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
.AddTransient<JwtTokenHeaderHandler>();
Calling an Authorized Get endpoint from my .razor file;
var response = await Http.GetAsync<ContactTypeOutputModel[]>("ContactTypes/all");
(where the above is simply a typed wrapped service response using an extension method);
public static async Task<ServiceResponse<T>> GetAsync<T>(
this HttpClient httpClient, string url)
{
var response = await httpClient.GetAsync(url);
return await BuildResponse<T>(response);
}
I am still getting a 401 on the service? and no output logging to say the service is being used?
public class JwtTokenHeaderHandler : DelegatingHandler
{
private readonly IAppLocalStorageService _localStorage;
private readonly ILogger<JwtTokenHeaderHandler> _logger;
private readonly HttpMessageHandler _innerHandler;
private readonly MethodInfo _method;
public JwtTokenHeaderHandler(IAppLocalStorageService localStorage, HttpMessageHandler innerHandler, ILogger<JwtTokenHeaderHandler> logger)
{
_localStorage = localStorage;
_logger = logger;
_innerHandler = innerHandler;
var type = innerHandler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
_logger.LogDebug("Adding Jwt to Header from JwtTokenHeaderHandler: SendAsync");
_logger.LogDebug($"Does header contain 'bearer' token: {!request.Headers.Contains("bearer")}");
if (!request.Headers.Contains("bearer"))
{
_logger.LogDebug("Adding Bearer Token to Header");
var savedToken = await _localStorage.GetTokenAsync();
_logger.LogDebug($"Saved Token is : {savedToken}");
if (!string.IsNullOrWhiteSpace(savedToken))
{
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
}
}
//return await base.SendAsync(request, cancellationToken);
return await (_method.Invoke(_innerHandler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>);
}
}
Any ideas why this is not being hit?
WebAssemblyHttpMessageHandler has been replaced by Mono's WasmHttpMessageHandler by you can't add a dependency to WebAssembly.Net.Http.
To create a DependencyHandler for blazor WASM, I did a little hack :
public class OidcDelegationHandler : DelegatingHandler
{
private readonly IUserStore _userStore;
private readonly HttpMessageHandler _innerHanler;
private readonly MethodInfo _method;
public OidcDelegationHandler(IUserStore userStore, HttpMessageHandler innerHanler)
{
_userStore = userStore ?? throw new ArgumentNullException(nameof(userStore));
_innerHanler = innerHanler ?? throw new ArgumentNullException(nameof(innerHanler));
var type = innerHanler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue(_userStore.AuthenticationScheme, _userStore.AccessToken);
return _method.Invoke(_innerHanler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>;
}
}
Is set it up in DI with:
return services
.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
The full code is here
Working on building signalR hub, I'm able to get data from hub to the client but I'm, not sure how do I push it every 1 second.
I'm not sure where do I set the timer in the controller where getApps method exists or in the hub?
Hub:
public class nphub : Hub
{
public readonly sbController _sbcontroller;
public nphub(sbController sbcontroller)
{
_sbcontroller = sbcontroller;
}
public async Task NotifyConnection()
{
IActionResult result = await _sbcontroller.getApps();
await Clients.All.SendAsync("TestBrodcasting", result);
}
}
In Controller:
public async Task<IActionResult> getApps()
{
// var request = new HttpRequestMessage(HttpMethod.Get, "apps");
// var response = await _client_NP.SendAsync(request);
// var json = await response.Content.ReadAsStringAsync();
return Ok($"Testing a Basic HUB at {DateTime.Now.ToLocalTime()}");
}
Client:
let connection = new signalR.HubConnectionBuilder()
.withUrl("/nphub").build();
connection.start().then(function () {
TestConnection();
}).catch(function (err) {
return console.error(err.toString());
});
function TestConnection() {
connection.invoke("NotifyConnection").catch(function (err) {
return console.error(err.toString());
});
}
connection.on("TestBrodcasting", function (time) {
document.getElementById('broadcastDiv').innerHTML = time.value;
document.getElementById('broadcastDiv').style.display = "block";
});
Just for the test purpose to see realtime changes, I'm trying to return time. I'm able to see time on the client but it's not changing.
You need to use a hosted service, as described in the docs. Add a class like:
internal class SignalRTimedHostedService : IHostedService, IDisposable
{
private readonly IHubContext<nphub> _hub;
private readonly ILogger _logger;
private Timer _timer;
public SignalRTimedHostedService(IHubContext<nphub> hub, ILogger<SignalRTimedHostedService> logger)
{
_hub = hub;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
// send message using _hub
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Note: A hosted service lives in singleton scope. You can inject IHubContext<T> directly, though, because it too is in singleton scope.
Then in ConfigureServices:
services.AddHostedService<SignalRTimedHostedService>();
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 !
I want to add new user to my local database after register in Stormpath. In doc https://docs.stormpath.com/dotnet/aspnetcore/latest/registration.html#registration is section about post-registration handler. I have problem becouse i can't use UserRepository in StartUp file.
I have error:
Unable to resolve service for type
'AppProject.Repositories.IUserRepository' while attempting to
activate 'AppProject.Startup'
.
public void ConfigureServices(IServiceCollection services, IUserRepository userRepository)
{
services.AddStormpath(new StormpathOptions()
{
Configuration = new StormpathConfiguration()
{
Client = new ClientConfiguration()
{
ApiKey = new ClientApiKeyConfiguration()
{
Id = "xxxxxxxxxxx",
Secret = "xxxxxxxxx"
}
}
},
PostRegistrationHandler = (context, ct) =>
{
return MyPostRegistrationHandler(context, ct, userRepository);
}
});
}
private Task MyPostRegistrationHandler(PostRegistrationContext context, CancellationToken ct, IUserRepository userRepository)
{
userRepository.Add(new User(context.Account.Email, context.Account.FullName, context.Account.GivenName, context.Account.Surname, context.Account.Username));
userRepository.SaveChangesAsync();
return Task.FromResult(0);
}
In this scenario, I don't think it can resolve dependency of IUserRepository in StartUp. You can try something like this.
1) Add an extension method.
public static IServiceProvider AddServices(this IServiceCollection services)
{
services.AddTransient<IUserRepository, UserRepository>();
// rest of the things.
return services.BuildServiceProvider();
}
2) Get the userRepository instance like like this.
IServiceCollection services = new ServiceCollection();
services.AddServices();
var provider = services.BuildServiceProvider();
var userRepository = provider.GetRequiredService<IUserRepository>();
ConfigurationServices will not have IUserRepository input parameter.