Is it possible for a single webjob to have a timer triggered function as well as a manually triggered function - azure

I am having an existing webjob(V3.0) in .net core that has a function that is invoked by manual trigger, essentially by a webhook. I want to add another function to the same webjob that should be invoked on a Timer trigger every 20 mins. Is it possible to have both these in the same webjob. If it is possible what should the host configuration that I need to do. I tried going through Microsoft's documentation but there is barely any documentation with respect to the host configuration part with multiple triggers

Yes but your function would need to be triggered by something in Azure Storage like a Queue. This code is probably more then you might need. All of my services implement a custom interface IServiceInvoker. My CTOR asks for an
IEnumerable<IServiceInvoker>
which gets all of the services. Then I either use a constant or a passed in value to determine what service to run. Since I ONLY want ONE function to ever be running I am using the Singleton attribute passing in String.Empty. I also have the following settings on my Queues
b.AddAzureStorage(a =>
{
a.BatchSize = 1;
a.NewBatchThreshold = 1;
a.MaxDequeueCount = 1;
a.MaxPollingInterval = TimeSpan.FromSeconds(60);
a.VisibilityTimeout = TimeSpan.FromSeconds(60);
});
Finally I found that during testing I sometimes needed to turn off on or more functions hence the class ServiceConfigurationProvider.
Code sample follows I removed quite a bit of code so YMMV
public static async Task Main(string[] args)
{
await CreateHostBuilder(args).Build().RunAsync();
}
more code
public class Functions
{
/// <summary>
/// This scopes the singleton attribute to each individual function rather than the entire host
/// </summary>
const String SCOPESINGLETONTOFUNCTION = "";
readonly ILogger<Functions> _logger;
readonly Dictionary<String, IServiceInvoker> _services;
readonly IConfiguration _configuration;
private Functions()
{
_services = new Dictionary<string, IServiceInvoker>();
}
public Functions(IEnumerable<IServiceInvoker> services, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IConfiguration configuration) : this()
{
_logger = loggerFactory.CreateLogger<Functions>();
foreach (var service in services)
{
_services.Add(service.ServiceIdentifier, service);
}
_configuration = configuration;
}
[Disable(typeof(ServiceConfigurationProvider))]
[Singleton(SCOPESINGLETONTOFUNCTION)]
public async Task TimerTriggerFunction([TimerTrigger("%TimerTriggerFunctionExpression%")]TimerInfo myTimer, CancellationToken cancellationToken)
{
try
{
if (_services.TryGetValue("ServiceName", out IServiceInvoker serviceToInvoke))
{
await serviceToInvoke.InvokeServiceAsync(null, cancellationToken, false);
}
}
catch (Exception ex)
{
_logger?.LogError(ex, $"Unhandled exception occurred in method:'{nameof(TimerTriggerFunction)}'");
}
}
[Disable(typeof(ServiceConfigurationProvider))]
[Singleton(SCOPESINGLETONTOFUNCTION)]
public async Task ServiceInvokerQueueFunction([QueueTrigger("%ServiceInvokerQueueName%", Connection = "AzureWebJobsStorage")] ServiceInvokerMessage serviceInvokerMessage, CancellationToken cancellationToken)
{
if (serviceInvokerMessage is null || String.IsNullOrEmpty(serviceInvokerMessage.ServiceIdentifier))
{
_logger?.LogError("The queue message received in the ServiceInvokerQueueFunction could not be serialized into a ServiceInvokerMessage instance.");
}
else
{
Boolean serviceExists = _services.TryGetValue(serviceInvokerMessage.ServiceIdentifier, out IServiceInvoker serviceToInvoke);
if (serviceExists)
{
try
{
await serviceToInvoke.InvokeServiceAsync(null, cancellationToken, true);
}
catch (Exception exception)
{
_logger?.LogError(exception, $"Unhandled exception occurred in method:'{nameof(ServiceInvokerQueueFunction)}' for service:'{serviceInvokerMessage.ServiceIdentifier}'");
}
}
}
}
[Disable(typeof(ServiceConfigurationProvider))]
[Singleton(SCOPESINGLETONTOFUNCTION)]
public async Task RecordQueueFunction([QueueTrigger("%RecordQueueName%", Connection = "RecordConnectString")] string message, CancellationToken cancellationToken)
{
{
_logger?.LogInformation(message);
try
{
if (_services.TryGetValue("ServiceName", out IServiceInvoker serviceToInvoke))
{
await serviceToInvoke.InvokeServiceAsync(message, cancellationToken, false);
}
}
catch (Exception ex)
{
_logger?.LogError(ex, $"Unhandled exception occurred in method:'{nameof(RecordQueueFunction)}'");
throw;
}
}
}
}
public class ServiceConfigurationProvider
{
readonly IConfiguration _configuration;
public ServiceConfigurationProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public bool IsDisabled(MethodInfo method)
{
Boolean returnValue = false;
String resultConfiguration = _configuration[$"{method.Name}Disable"];
if (!String.IsNullOrEmpty(resultConfiguration))
{
Boolean.TryParse(resultConfiguration, out returnValue);
}
return returnValue;
}
}

Related

How to raise Event in HostedService and consume it in a Blazor Component?

i am building a BlazorServer-Side App und i try to Update one of my Components when a Event occurs in a HostedService.
The Problem is solved! Check the Comments for the Solution.
This is how i try to do it:
Configuration in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddHostedService<WeatherForecastService>();
}
WeatherForecastService:
public class WeatherForecastService : IHostedService
{
private System.Timers.Timer timer = new System.Timers.Timer(10000);
public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler NewWeather;
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private void TimeOver(object sender, ElapsedEventArgs e)
{
NewWeather?.Invoke(this, null);
}
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray());
}
public Task StartAsync(CancellationToken cancellationToken)
{
timer.Elapsed += TimeOver;
timer.AutoReset = true;
timer.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
FetchData.razor:
#code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
ForecastService.NewWeather += UpdateWeather;
}
private async void UpdateWeather(object sender, object args)
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
StateHasChanged();
}
}
UpdateWeather() on the FetchData.razor never gets called.
I am guessing the FetchData Component does not notice the event is fired because of some threading issue. But I dont konw yet what to do about it...
Your Help is appreciated.
Best Regards
The Code above generates two Instances of the WeatherForecastService!
The Solution is to change the registration of the HostedService
from:
services.AddHostedService();
to:
services.AddHostedService(sp => sp.GetRequiredService());

How to add custom data on exception handling using azure webjobs SDK?

I have a simple Azure Function which returns to a queue:
private readonly TelemetryClient _telemetryClient;
[return: Queue("%ReturnQueue%")]
public async Task<string> Run([QueueTrigger("%RequestQueue%")] string msg, ILogger log)
{
try
{
//Some dependency calls
}
catch(Exception ex)
{
var dic = new Dictionary<string,string>();
dic.Add("Id", someId);
dic.Add("CustomData", cusomData);
_telemetryClient.TrackException(ex, dic);
}
}
I obviously get a compilation error saying that not all code paths returns a value.
The problem is that if I add a throw at the end of the catch block the Azure Functions runtime replicate the excpetion on the appinsights portal. How can I add custom data to my exceptions like this?
You can create your own Exception type:
public class MyCustomException : Exception
{
public string Id {get;set;}
public string CustomData {get;set;}
public Exception RootException {get;set;}
public MyCustomException(string id, string customData, Exception ex)
{
Id = id;
CustomData = customData;
RootException = ex;
}
}
private readonly TelemetryClient _telemetryClient;
[return: Queue("%ReturnQueue%")]
public async Task<string> Run([QueueTrigger("%RequestQueue%")] string msg, ILogger log)
{
try
{
//Some dependency calls
}
catch(Exception ex)
{
//var dic = new Dictionary<string,string>();
//dic.Add("Id", someId);
//dic.Add("CustomData", cusomData);
var customEx = new MyCustomException(someId, cusomData, ex);
_telemetryClient.TrackException(customEx);
}
finally
{
return "";
}
}
PS: inside MyCustomException you can actually have Dictionary rather than string properties.

Botframework how log history

Do You have any idea how to log all outgoing/incoming messages? I am not sure how to capture outgoing messages.
I use Chains and Forms.
For example
await Conversation.SendAsync(activity, rootDialog.BuildChain);
AND
activity.CreateReply(.....);
I found better solution
public class BotToUserLogger : IBotToUser
{
private readonly IMessageActivity _toBot;
private readonly IConnectorClient _client;
public BotToUserLogger(IMessageActivity toBot, IConnectorClient client)
{
SetField.NotNull(out _toBot, nameof(toBot), toBot);
SetField.NotNull(out _client, nameof(client), client);
}
public IMessageActivity MakeMessage()
{
var toBotActivity = (Activity)_toBot;
return toBotActivity.CreateReply();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
await _client.Conversations.ReplyToActivityAsync((Activity)message, cancellationToken);
}
}
public class BotToUserDatabaseWriter : IBotToUser
{
private readonly IBotToUser _inner;
public BotToUserDatabaseWriter(IBotToUser inner)
{
SetField.NotNull(out _inner, nameof(inner), inner);
}
public IMessageActivity MakeMessage()
{
return _inner.MakeMessage();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
// loging outgoing message
Debug.WriteLine(message.Text);
//TODO log message for example into DB
await _inner.PostAsync(message, cancellationToken);
}
In controller use
public MessagesController()
{
var builder = new ContainerBuilder();
builder.RegisterType<BotToUserLogger>()
.AsSelf()
.InstancePerLifetimeScope();
builder.Register(c => new BotToUserTextWriter(c.Resolve<BotToUserLogger>()))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.Update(Microsoft.Bot.Builder.Dialogs.Conversation.Container);
}
Its look like I cant log outgoing message.
I changed SDK source code.
Add event in Conversations.cs
For example like this.
public delegate void MessageSendedEventHandler(object sender, Activity activity, string conversationId);
public static event MessageSendedEventHandler MessageSended;
And add in every Send....HttpMessagesAsync method
this MessageSended?.Invoke(this, activity, conversationId);
its not great solution. But its working

When the ProcessEventsAsync(PartitionContext context, ienumerable<EventData> messages) method will be fired

I am currently working on Internet Of Things, in my current project I was Created the One Azure Cloud Service Project in that I Created the Worker Role, inside the worker role i have wrote below lines of code.
public class WorkerRole : RoleEntryPoint
{
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
private static string connectionString;
private static string eventHubName;
public static ServiceClient iotHubServiceClient { get; private set; }
public static EventHubClient eventHubClient { get; private set; }
public override void Run()
{
Trace.TraceInformation("EventsForwarding Run()...\n");
try
{
this.RunAsync(this.cancellationTokenSource.Token).Wait();
}
finally
{
this.runCompleteEvent.Set();
}
}
public override bool OnStart()
{
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
// For information on handling configuration changes
// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
bool result = base.OnStart();
Trace.TraceInformation("EventsForwarding OnStart()...\n");
connectionString = ConfigurationManager.AppSettings["Microsoft.ServiceBus.ConnectionString"];
eventHubName = ConfigurationManager.AppSettings["Microsoft.ServiceBus.EventHubName"];
string storageAccountName = ConfigurationManager.AppSettings["AzureStorage.AccountName"];
string storageAccountKey = ConfigurationManager.AppSettings["AzureStorage.Key"];
string storageAccountString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}",
storageAccountName, storageAccountKey);
string iotHubConnectionString = ConfigurationManager.AppSettings["AzureIoTHub.ConnectionString"];
iotHubServiceClient = ServiceClient.CreateFromConnectionString(iotHubConnectionString);
eventHubClient = EventHubClient.CreateFromConnectionString(connectionString, eventHubName);
var defaultConsumerGroup = eventHubClient.GetDefaultConsumerGroup();
string eventProcessorHostName = "SensorEventProcessor";
EventProcessorHost eventProcessorHost = new EventProcessorHost(eventProcessorHostName, eventHubName, defaultConsumerGroup.GroupName, connectionString, storageAccountString);
eventProcessorHost.RegisterEventProcessorAsync<SensorEventProcessor>().Wait();
Trace.TraceInformation("Receiving events...\n");
return result;
}
public override void OnStop()
{
Trace.TraceInformation("EventsForwarding is OnStop()...");
this.cancellationTokenSource.Cancel();
this.runCompleteEvent.WaitOne();
base.OnStop();
Trace.TraceInformation("EventsForwarding has stopped");
}
private async Task RunAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
//Trace.TraceInformation("EventsToCommmandsService running...\n");
await Task.Delay(1000);
}
}
}
Next I have wrote the below lines of code in SensorEventProcessor, for receiving the messages from event hub and send those messages to IoT hub.
class SensorEventProcessor : IEventProcessor
{
Stopwatch checkpointStopWatch;
PartitionContext partitionContext;
public async Task CloseAsync(PartitionContext context, CloseReason reason)
{
Trace.TraceInformation(string.Format("EventProcessor Shuting Down. Partition '{0}', Reason: '{1}'.", this.partitionContext.Lease.PartitionId, reason.ToString()));
if (reason == CloseReason.Shutdown)
{
await context.CheckpointAsync();
}
}
public Task OpenAsync(PartitionContext context)
{
Trace.TraceInformation(string.Format("Initializing EventProcessor: Partition: '{0}', Offset: '{1}'", context.Lease.PartitionId, context.Lease.Offset));
this.partitionContext = context;
this.checkpointStopWatch = new Stopwatch();
this.checkpointStopWatch.Start();
return Task.FromResult<object>(null);
}
public async Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
Trace.TraceInformation("\n");
Trace.TraceInformation("........ProcessEventsAsync........");
//string commandParameterNew = "{\"Name\":\"AlarmThreshold\",\"Parameters\":{\"SensorId\":\"" + "Hello World" + "\"}}";
//await WorkerRole.iotHubServiceClient.SendAsync("astranidevice", new Microsoft.Azure.Devices.Message(Encoding.UTF8.GetBytes(commandParameterNew)));
foreach (EventData eventData in messages)
{
try
{
string jsonString = Encoding.UTF8.GetString(eventData.GetBytes());
Trace.TraceInformation(string.Format("Message received at '{0}'. Partition: '{1}'",
eventData.EnqueuedTimeUtc.ToLocalTime(), this.partitionContext.Lease.PartitionId));
Trace.TraceInformation(string.Format("-->Raw Data: '{0}'", jsonString));
SimpleTemperatureAlertData newSensorEvent = this.DeserializeEventData(jsonString);
Trace.TraceInformation(string.Format("-->Serialized Data: '{0}', '{1}', '{2}', '{3}', '{4}'",
newSensorEvent.Time, newSensorEvent.RoomTemp, newSensorEvent.RoomPressure, newSensorEvent.RoomAlt, newSensorEvent.DeviceId));
// Issuing alarm to device.
string commandParameterNew = "{\"Name\":\"AlarmThreshold\",\"Parameters\":{\"SensorId\":\"" + "Hello World" + "\"}}";
Trace.TraceInformation("Issuing alarm to device: '{0}', from sensor: '{1}'", newSensorEvent.DeviceId, newSensorEvent.RoomTemp);
Trace.TraceInformation("New Command Parameter: '{0}'", commandParameterNew);
await WorkerRole.iotHubServiceClient.SendAsync(newSensorEvent.DeviceId, new Microsoft.Azure.Devices.Message(Encoding.UTF8.GetBytes(commandParameterNew)));
}
catch (Exception ex)
{
Trace.TraceInformation("Error in ProssEventsAsync -- {0}\n", ex.Message);
}
}
await context.CheckpointAsync();
}
private SimpleTemperatureAlertData DeserializeEventData(string eventDataString)
{
return JsonConvert.DeserializeObject<SimpleTemperatureAlertData>(eventDataString);
}
}
When I was debug my code, the ProcessEventsAsync(PartitionContext context, IEnumerable messages) method will never call and just enter into OpenAsync() method then itstop the debugging.
Please tell me Where I did mistake in my project and tell me when the ProcessEventsAsync() method will call.
Regards,
Pradeep
IEventProcessor.ProcessEventsAsync is invoked when there are any unprocessed messages in the EventHub.
An Event Hub contains multiple partitions. A partition is an ordered sequence of events. Within a partition, each event includes an offset. This offset is used by consumers (IEventProcessor) to show the location in the event sequence for a given partition. When an IEventProcessor connects (EventProcessorHost.RegisterEventProcessorAsync), it passes this offset to the Event Hub to specify the location at which to start reading. When there are unprocessed messages (events with higher offset), they are delivered to the IEventProcessor. Checkpointing is used to persist the offset of processed messages (PartitionContext.CheckpointAsync).
You can find detailed information about the internals of EventHub: Azure Event Hubs overview
Have you sent any messages to the EventHub (EventHubClient.SendAsync(EventData))?

Entity Framework The context cannot be used while the model is being created

My unit of work class is mentioned below and I am using Ninject and I have tried injecting IUnitOfWork per request per thread scope, transient etc. but I am still getting error which is:
"Message":"An error has occurred.","ExceptionMessage":"The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.","ExceptionType":"System.InvalidOperationException
I get this error when i make two web API (get) calls at the same time using angularJS and it shows error at the point _context.Set<TEntity>().FirstOrDefault(match);
public class UnitOfWork : IUnitOfWork, IDisposable
{
private My_PromotoolEntities _uowDbContext = new My_PromotoolEntities();
private Dictionary<string, object> _repositories;
// Do it like this if no specific class file
private GenericRepository<MysPerson> _personRepository;
//private GenericRepository<MysDataSource> dataSourcesRepository;
//private GenericRepository<MysCountry> countryMasterRepository;
// Or like this if with specific class file.
private DataSourceRepository _dataSourcesRepository;
private CustomerRepository _customerRepository;
private DeviceRepository _deviceRepository;
private DeviceRegistrationRepository _deviceRegistrationRepository;
private EmailQueueRepository _emailQueueRepository;
public void SetContext(My_PromotoolEntities context)
{
_uowDbContext = context;
}
public void CacheThis(object cacheThis, string keyName, TimeSpan howLong)
{
Cacheing.StaticData.CacheStaticData(cacheThis, keyName, howLong);
}
public object GetFromCache(string keyName)
{
return Cacheing.StaticData.GetFromCache(keyName);
}
public GenericRepository<T> GenericRepository<T>() where T : BaseEntity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _uowDbContext);
_repositories.Add(type, repositoryInstance);
}
return (GenericRepository<T>)_repositories[type];
}
public GenericRepository<MysPerson> PersonRepository
{
get
{
if (this._personRepository == null)
{
this._personRepository = new GenericRepository<MysPerson>(_uowDbContext);
}
return _personRepository;
}
}
public DataSourceRepository DataSourcesRepository
{
get
{
if (this._dataSourcesRepository == null)
{
this._dataSourcesRepository = new DataSourceRepository(_uowDbContext);
}
return _dataSourcesRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this._customerRepository == null)
{
this._customerRepository = new CustomerRepository(_uowDbContext);
}
return _customerRepository;
}
}
public DeviceRepository DeviceRepository
{
get
{
if (this._deviceRepository == null)
{
this._deviceRepository = new DeviceRepository(_uowDbContext);
}
return _deviceRepository;
}
}
public DeviceRegistrationRepository DeviceRegistrationRepository
{
get
{
if (this._deviceRegistrationRepository == null)
{
this._deviceRegistrationRepository = new DeviceRegistrationRepository(_uowDbContext);
}
return _deviceRegistrationRepository;
}
}
public EmailQueueRepository emailQueueRepository
{
get
{
if (this._emailQueueRepository == null)
{
this._emailQueueRepository = new EmailQueueRepository(_uowDbContext);
}
return _emailQueueRepository;
}
}
/// <summary>
/// Commits all changes to the db. Throws exception if fails. Call should be in a try..catch.
/// </summary>
public void Save()
{
try
{
_uowDbContext.SaveChanges();
}
catch (DbEntityValidationException dbevex)
{
// Entity Framework specific errors:
StringBuilder sb = new StringBuilder();
var eve = GetValidationErrors();
if (eve.Count() > 0)
{
eve.ForEach(error => sb.AppendLine(error));
}
ClearContext();
// Throw a new exception with original as inner.
var ex = new Exception(sb.ToString(), dbevex);
ex.Source = "DbEntityValidationException";
throw ex;
}
catch (Exception)
{
ClearContext();
throw;
}
}
private void ClearContext()
{
DetachAll();
}
private void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in _uowDbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = EntityState.Detached;
}
}
}
/// <summary>
/// Checks for EF DbEntityValidationException(s).
/// </summary>
/// <returns>Returns a List of string containing the EF DbEntityValidationException(s).</returns>
public List<string> GetValidationErrors()
{
if (_uowDbContext.GetValidationErrors().Count() != 0)
{
return _uowDbContext.GetValidationErrors().Select(e => string.Join(Environment.NewLine, e.ValidationErrors.Select(v => string.Format("{0} - {1}", v.PropertyName, v.ErrorMessage)))).ToList();
}
return null;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_uowDbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
You should never use a context in 2 places at the same time, that's exactly why you are getting this error. From the MSDN documentation:
Thread Safety: Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
It is a little hard to make suggestions without a repro but there is a brute force approach that should resolve the issue. If you have an interception point before/during DI setup then you can cause all the context initialization etc to happen by creating an instance of your context and calling ctx.Database.Initialize(force: false); Passing 'force: false' will ensure that the initialization still only happens once per AppDomain

Resources