我在.net核心中有一个现有的webjob(V3.0),其功能由手动触发器(本质上是由Webhook)调用。我想向应该每20分钟在Timer触发器上调用的同一webjob中添加另一个函数。是否可以将它们都放在同一个webjob中。如果可能的话,我需要做什么主机配置。我尝试浏览Microsoft的文档,但几乎没有关于具有多个触发器的主机配置部分的文档
答案 0 :(得分:0)
是的,但是您的功能将需要由Azure存储中的诸如队列之类的东西来触发。此代码可能比您可能需要的更多。我所有的服务都实现了自定义接口IServiceInvoker。我的CTOR要求
IEnumerable<IServiceInvoker>
获得所有服务。然后,我可以使用常量或传入的值来确定要运行的服务。因为我只希望一个函数能够运行,所以我使用传递String.Empty的Singleton属性。我的队列上也有以下设置
b.AddAzureStorage(a =>
{
a.BatchSize = 1;
a.NewBatchThreshold = 1;
a.MaxDequeueCount = 1;
a.MaxPollingInterval = TimeSpan.FromSeconds(60);
a.VisibilityTimeout = TimeSpan.FromSeconds(60);
});
最后,我发现在测试过程中有时需要关闭一个或多个功能,因此需要关闭ServiceConfigurationProvider类。
代码示例如下,我删除了很多代码,因此YMMV
public static async Task Main(string[] args)
{
await CreateHostBuilder(args).Build().RunAsync();
}
更多代码
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;
}
}