How to schedule task for a Worker service - c#

I'm implementing asp.net core 3.1 project. I'm using a Worker service which should read every day some data from SQL server table and the call an api which should send an sms and the sms content should be those data which were read from sql server. Now I could call the sms api and send some static data by it to the user. The problem is although, I defined that the api should be called each 5 seconds but it just send the sms 1 time to the use. I appreciate if anyone tells me how I can fix this. Here below is what I have tried:
public class Worker : BackgroundService, IHostedService, ISendingSMS
{
private readonly ILogger<Worker> _logger;
private MarketsContext _context;
public IServiceScopeFactory _serviceScopeFactory;
//---------------------------------------------------
public Report report { get; set; }
private MsgContent msgContent;
//---------------------------------------------------
public Worker(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
//---------------------------------------------------------------------------------------
public async Task GetReport()
{
IQueryable<Report> reportData = _context.Report.Select(x => new Report
{
ReportDate = x.ReportDate,
TotalMarkets = x.TotalMarkets,
})/*.FirstOrDefault()*/;
report = await reportData.AsNoTracking().FirstOrDefaultAsync();
}
//-------------------------------------------------------------------------------
public override async Task StartAsync(CancellationToken cancellationToken)
{
// DO YOUR STUFF HERE
await base.StartAsync(cancellationToken);
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
// DO YOUR STUFF HERE
await base.StopAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MarketsContext>();
var _smssend = scope.ServiceProvider.GetRequiredService<SendingSMSService>();
_context = dbContext;
await GetReport();
var message = new MsgContent
{
Username = "cse.etsg",
Password = "wrwe",
From = "4500000",
To = "+429124203243",
Message = report.ReportDate + "" + + report.TotalMarkets
};
_smssend.sendSMS(message);
await Task.Delay(120000, stoppingToken);
//Do your stuff
}
}
}
}
SendingSMSService
public class SendingSMSService /*: ISendingSMS*/
{
public Report report { get; set; }
// Here this method calls sms api sccessfully
public async void sendSMS(MsgContent message)
{
using (var client = new HttpClient())
{
var myContent = JsonConvert.SerializeObject(message);
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var result = client.PostAsync("https://api/SendSMS", byteContent).Result)
{
string Response = await result.Content.ReadAsStringAsync();
}
}
}
}
Report
public partial class Report
{
public int Id { get; set; }
public string ReportDate { get; set; }
public int? TotalMarkets { get; set; }
}
Program
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
var configuration = hostContext.Configuration;
services.AddHostedService<Worker>();
//------------------------------------------------------------------------------
services.AddScoped<SendingSMSService>();
var connection = hostContext.Configuration.GetConnectionString("Markets");
var optionsBuilder = new DbContextOptionsBuilder<MarketsContext>();
optionsBuilder.UseSqlServer(connection);
services.AddScoped<MarketsContext>(s => new MarketsContext(optionsBuilder.Options));
});
}

In this scenario you can use a Timed background task see timed-background-tasks.
In your case you would need two of them. They are sheduled according to your parameters of the Timer in the StartAsyncmethod, you not have to call them explicit.
The created services are then injected in IHostBuilder.ConfigureServices (Program.cs) with: services.AddHostedService<TimedHostedService>();

Related

C# Azure DevOps API retrieve all projects

I have the following. It seems to not be waiting after the request has been sent out in the GetAllProjects. It waits for the response, then doesn't wait for the response content to be read in.
NOTE: using nuget package for ReadAsAsync (could have used a json object to deserialize however)
public class Project
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Url { get; set; }
}
public class Main
{
public static void Main(string[] args)
{
AzureClient ac = new AzureClient();
var projects = ac.GetAllProjects();
}
}
public class AzureClient
{
private readonly HttpClient _client;
private const string PAT = "myToken";
private const string API_VERSION = "api-version=5.0";
public AzureClient()
{
_client = new HttpClient()
{
BaseAddress = new Uri("some uri"),
Timeout = TimeSpan.FromSeconds(30)
};
// added media type and passed in auth token to _client. client returns 200 on requests
}
public async Task<ICollection<Project>> GetAllProjects()
{
var response = await _client.GetAsync("_apis/projects?{API_VERSION }");
var projects = await response.Content.ReadAsAsync<dynamic>();
return projects.value.ToObject<ICollection<Project>>();
}
}
You are missing an await here
var projects = await ac.GetAllProjects();
you will also need to make your Main method async

.Net Core Channel in Background Tasks

I want using chanel in backgroundservice, but I have this error when run my code, what I need to do.
Sorry for bad english
Unable to resolve service for type
'System.Threading.Channels.ChannelReader`1[SendMailChanel]'
while attempting to activate 'SendEmailService'
public class SendMailChanel
{
public List<SendMail> SendMails { get; set; }
public List<string> MailTos { get; set; }
}
public class SendEmailService: BackgroundService
{
private readonly ChannelReader<SendMailChanel> _channel;
public HostedService(
ChannelReader<SendMailChanel> channel)
{
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var item in _channel.ReadAllAsync(cancellationToken))
{
try
{
// do your work with data
}
catch (Exception e)
{
}
}
}
}
[ApiController]
[Route("api/data/upload")]
public class UploadController : ControllerBase
{
private readonly ChannelWriter<SendMailChanel> _channel;
public UploadController (
ChannelWriter<SendMailChanel> channel)
{
_channel = channel;
}
public async Task<IActionResult> Upload([FromForm] FileInfo fileInfo)
{
SendMailChanel mailChanel = new SendMailChanel();
mailChanel.SendMails = lstSendMail;
mailChanel.MailTos = lstEmailTo;
await _channel.WriteAsync(mailChanel);
return Ok();
}
}
Startup.cs
services.AddHostedService<SendEmailService>();
follow this guide
https://flerka.github.io/personal-blog/2020-01-23-communication-with-hosted-service-using-channels/
services.AddHostedService<SendEmailService>();
services.AddSingleton<Channel<SendMailChanel>>(Channel.CreateUnbounded<SendMailChanel>(new UnboundedChannelOptions() { SingleReader = true }));
services.AddSingleton<ChannelReader<SendMailChanel>>(svc => svc.GetRequiredService<Channel<SendMailChanel>>().Reader);
services.AddSingleton<ChannelWriter<SendMailChanel>>(svc => svc.GetRequiredService<Channel<SendMailChanel>>().Writer);

Consume a Web API using a .Net Client App

I have been following some tutorials (e.g., https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client) and trying to retrieve a list of jobs and print them out in my .Net console app.
The test data I am using is located at https://boards-api.greenhouse.io/v1/boards/vaulttec/jobs and supplied it to client.BaseAddress.
Since I am able to compile and run the tutorial succesfully, I simply used the same code and changed some of it to run the above test data (see below).
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace GreenhouseJobs
{
public class Job
{
public string Id { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public DateTime LastUpdated { get; set; }
}
class GreenhouseJobsClient
{
static HttpClient client = new HttpClient();
static void ShowJob(Job job)
{
Console.WriteLine($"Id: {job.Id}\tTitle: " +
$"{job.Title}\tLocation: {job.Location}\tLast Updated: {job.LastUpdated}");
}
static async Task<Uri> CreateJobAsync(Job job)
{
HttpResponseMessage response = await client.PostAsJsonAsync(
"vaulttec/jobs", job);
response.EnsureSuccessStatusCode();
// return URI of the created resource.
return response.Headers.Location;
}
static async Task<Job> GetJobAsync(string path)
{
Job job = null;
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
job = await response.Content.ReadAsAsync<Job>();
}
return job;
}
//static async Task<Product> UpdateProductAsync(Product product)
//{
// HttpResponseMessage response = await client.PutAsJsonAsync(
// $"api/products/{product.Id}", product);
// response.EnsureSuccessStatusCode();
// // Deserialize the updated product from the response body.
// product = await response.Content.ReadAsAsync<Product>();
// return product;
//}
//static async Task<HttpStatusCode> DeleteProductAsync(string id)
//{
// HttpResponseMessage response = await client.DeleteAsync(
// $"api/products/{id}");
// return response.StatusCode;
//}
static void Main()
{
RunAsync().GetAwaiter().GetResult();
Console.ReadLine();
}
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("https://boards-api.greenhouse.io/v1/boards/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Create a new product
Job job = new Job
{
Id = "323232",
Title = "Test",
Location = "Test",
LastUpdated = DateTime.Now
};
var url = await CreateJobAsync(job);
Console.WriteLine($"Created at {url}");
// Get the product
job = await GetJobAsync(url.PathAndQuery);
ShowJob(job);
// Update the product
//Console.WriteLine("Updating price...");
//product.Price = 80;
//await UpdateProductAsync(product);
// Get the updated product
//product = await GetProductAsync(url.PathAndQuery);
//ShowProduct(product);
// Delete the product
//var statusCode = await DeleteProductAsync(product.Id);
//Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
}
}
The problem is, when I run the app, it does not return anything. Error message: "Response status code does not indicate success: 404 (Not Found)".
There are couple issues here
You are not just trying to retrieve but also create first. I am not sure if this api supports a POST or not, it probably doesn't, hence a 404
The Job entity is incorrect. Location needs to be an object itself and not a mere string if you need it deserialized correctly. Here is a working version of get call
https://pastebin.com/85NnAQY9
namespace ConsoleApp3
{
public class JobsJson
{
public List<Job> Jobs { get; set; }
}
public class Job
{
public string Id { get; set; }
public string Title { get; set; }
public Location Location { get; set; }
public DateTime LastUpdated { get; set; }
}
public class Location
{
public string Name { get; set; }
}
class GreenhouseJobsClient
{
static HttpClient client = new HttpClient();
static void ShowJobs(List<Job> jobs)
{
foreach (var job in jobs)
{
Console.WriteLine($"Id: {job.Id}\tTitle: " +
$"{job.Title}\tLocation: {job.Location}\tLast Updated: {job.LastUpdated}");
}
}
static async Task<List<Job>> GetJobAsync(string path)
{
var jobs = new List<Job>();
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
var re = JsonConvert.DeserializeObject<JobsJson>(stringResponse);
jobs = re.Jobs;
}
return jobs;
}
static void Main()
{
RunAsync().GetAwaiter().GetResult();
Console.ReadLine();
}
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("https://boards-api.greenhouse.io/v1/boards/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Get the product
var jobs = await GetJobAsync("vaulttec/jobs");
ShowJobs(jobs);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
}
}
The 404 is being returned by this line of code:
HttpResponseMessage response = await client.PostAsJsonAsync("vaulttec/jobs", job);
This is because the URL https://boards-api.greenhouse.io/v1/boards/vaulttec/jobs returns 404 when you try and make a POST request. Probably you need to be authorised to create jobs.
You can however make a GET request to this URL just fine.

Conversation logging combined with AAD v1 authentication

I am having trouble with adding both my AAD1 Authentication and custom Conversation logger onto my ChatBot. One or the other works fine, but when combining the two, I get HTTP timeouts. Any assistance would be greatly appreciated. Relevant code below:
Global.asax.cs
protected void Application_Start()
{
// Adding DocumentDB endpoint and primary key
var docDbServiceEndpoint = new Uri("-----------------------------------");//REMOVED Uri for question, no issue with connection as is
var docDbKey = "--------------------------------------------"; //REMOVED Key for question, no issue with connection as is
Conversation.UpdateContainer(builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new DocumentDbBotDataStore(docDbServiceEndpoint, docDbKey); // requires Microsoft.BotBuilder.Azure Nuget package
builder.RegisterType<DebugActivityLogger>().AsImplementedInterfaces().InstancePerDependency();
});
//authorization stuff
AuthSettings.Mode = ConfigurationManager.AppSettings["ActiveDirectory.Mode"];
AuthSettings.EndpointUrl = ConfigurationManager.AppSettings["ActiveDirectory.endpointUrl"];
AuthSettings.Tenant = ConfigurationManager.AppSettings["ActiveDirectory.Tenant"];
AuthSettings.RedirectUrl = ConfigurationManager.AppSettings["ActiveDirectory.RedirectUrl"];
AuthSettings.ClientId = ConfigurationManager.AppSettings["ActiveDirectory.ClientId"];
AuthSettings.ClientSecret = ConfigurationManager.AppSettings["ActiveDirectory.ClientSecret"];
GlobalConfiguration.Configure(WebApiConfig.Register);
}
AuthenticationHelper.cs
[Serializable]
public class AuthenticationHelper : IDialog<string>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(ProcessMessageAsync);
}
public async Task ProcessMessageAsync(IDialogContext context, IAwaitable<IMessageActivity> item)
{
var message = await item;
if (string.IsNullOrEmpty(await context.GetAccessToken("https://graph.microsoft.com/")))
{
//NO ACCESS TOKEN, GET IT
await context.Forward(new AzureAuthDialog("https://graph.microsoft.com"), this.ProcessAuthResultAsync, message, System.Threading.CancellationToken.None);
}
else
{
//have token
await context.Forward(new LuisAskQuestionDialog(), this.QuitMessageReceivedAsync, message, System.Threading.CancellationToken.None);
}
}
public async Task ProcessAuthResultAsync(IDialogContext context, IAwaitable<string> result)
{
var message = await result;
await context.PostAsync(message);
context.Wait(ProcessMessageAsync);
}
protected async Task QuitMessageReceivedAsync(IDialogContext context, IAwaitable<object> item)
{
var message = await item;
//StartRecordingProcess();
context.Done(message);
}
}
ChatBotLogging.cs
public class DebugActivityLogger : IActivityLogger
{
private const string EndpointUrl = "------------------------------";
private const string PrimaryKey = "------------------------------------";
private DocumentClient client;
// ADD THIS PART TO YOUR CODE
public async Task LogAsync(IActivity activity)
{
//Update this information
//What this needs to have: ConversationID, From, To, Date, Message
//Get all the texts information ready for upload;
//Get connection to table
//upload the inforamtion onto the table
//disconnect from the table
// Retrieve the storage account from the connection string.
//This Task is called to intercept messages
var fromid = activity.From.Id;
var toId = activity.Recipient.Id;
var chatMessage = activity.AsMessageActivity()?.Text;
var timeStamp = activity.Timestamp;
var conversationId = activity.Conversation.Id;
//timestamp converted to string.
string strTimeStamp = timeStamp.ToString();
try
{
this.client = new DocumentClient(new Uri(EndpointUrl), PrimaryKey);
await this.client.CreateDatabaseIfNotExistsAsync(new Database { Id = "botdb" });
await this.client.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri("botdb"), new DocumentCollection { Id = "botcollection" });
ChatLogEntity chatLog1 = new ChatLogEntity
{
TimeStamp = strTimeStamp,
ConversationId = conversationId,
FromID = fromid,
ToID = toId,
ChatMessage = chatMessage
};
await this.CreateChatDocumentIfNotExists("botdb", "botcollection", chatLog1);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
//entity class for demo purposes
// ADD THIS PART TO YOUR CODE
private async Task CreateChatDocumentIfNotExists(string databaseName, string collectionName, ChatLogEntity chatEntity)
{
try
{
await this.client.ReadDocumentAsync(UriFactory.CreateDocumentUri(databaseName, collectionName, chatEntity.TimeStamp));
}
catch (DocumentClientException de)
{
if (de.StatusCode == HttpStatusCode.NotFound)
{
await this.client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), chatEntity);
}
else
{
throw;
}
}
}
public class ChatLogEntity
{
[JsonProperty(PropertyName = "timestamp")]
public string TimeStamp { get; set; }
public string ConversationId { get; set; }
public string ToID { get; set; }
public string FromID { get; set; }
public string ChatMessage { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
}
The AuthBot package has been discontinued. As mentioned in the comments, You should use the BotAuth for AADv1 authentication. The BotAuth supports the state data conversation support (so you will not get the deprecated state client warnings too).

SignalR Web API send message to current user

I have web app project and an angular 2 project.
I would like use SignalR to send message from the server.
Then I found this article about implementing it.
But I don't know how to send message to the current user.
Code for send message C#:
public class EventHub : Hub
{
public async Task Subscribe(string channel)
{
await Groups.Add(Context.ConnectionId, channel);
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} subscribed",
Data = new
{
Context.ConnectionId,
ChannelName = channel
}
};
await Publish(#event);
}
public async Task Unsubscribe(string channel)
{
await Groups.Remove(Context.ConnectionId, channel);
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} unsubscribed",
Data = new
{
Context.ConnectionId,
ChannelName = channel
}
};
await Publish(#event);
}
public Task Publish(ChannelEvent channelEvent)
{
Clients.Caller.OnEvent(Constants.AdminChannel, channelEvent);
return Task.FromResult(0);
}
public override Task OnConnected()
{
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} connected",
Data = new
{
Context.ConnectionId,
}
};
Publish(#event);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} disconnected",
Data = new
{
Context.ConnectionId,
}
};
Publish(#event);
return base.OnDisconnected(stopCalled);
}
}
public static class Constants
{
public const string AdminChannel = "admin";
public const string TaskChannel = "tasks";
}
public class ChannelEvent
{
public string Name { get; set; }
public string ChannelName { get; set; }
public DateTimeOffset Timestamp { get; set; }
public object Data
{
get { return _data; }
set
{
_data = value;
Json = JsonConvert.SerializeObject(_data);
}
}
private object _data;
public string Json { get; private set; }
public ChannelEvent()
{
Timestamp = DateTimeOffset.Now;
}
}
Then in my controller I create IhubContent
private readonly IHubContext _context = GlobalHost.ConnectionManager.GetHubContext<EventHub>();
and invoke my publish event:
private void PublishEvent(string eventName, StatusModel status)
{
_context.Clients.Group(Constants.TaskChannel).OnEvent(Constants.TaskChannel, new ChannelEvent
{
ChannelName = Constants.TaskChannel,
Name = eventName,
Data = status
});
}
But this message sent to all users. Help me to fix this issue and implement code to the send message to the current user.
The IHubContext object your are using has multiple methods, one of which is Clients, of type IHubConnectionContext.
In there you have Groups, Group, Clients & Client methods which abstract what you want to target.
In your case using:
_context.Clients.Client(connectionId).send(message);
should be working fine (you still need to propagate the connectionId though).
N.B.: send is a JS method that should be implemented on client-side.

Categories

Resources