Last active
November 1, 2020 13:45
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Devlight.Azure.Functions.Extensions.Startup | |
{ | |
using System; | |
public sealed class StartupInfo | |
{ | |
public string HostName { get; private set; } | |
public string SiteName { get; private set; } | |
public string Name { get; private set; } | |
public string Version { get; private set; } | |
internal StartupInfo() | |
{ | |
} | |
internal static StartupInfo Create(string name, string version) => new StartupInfo | |
{ | |
Name = name, | |
Version = version, | |
HostName = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME"), | |
SiteName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Devlight.Azure.Functions.Extensions.Startup | |
{ | |
using System; | |
using Microsoft.Azure.WebJobs.Description; | |
[AttributeUsage(AttributeTargets.Parameter)] | |
[Binding] | |
public sealed class StartupTriggerAttribute : Attribute | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Devlight.Azure.Functions.Extensions.Startup.Bindings | |
{ | |
using Listeners; | |
using Microsoft.Azure.WebJobs.Host.Bindings; | |
using Microsoft.Azure.WebJobs.Host.Listeners; | |
using Microsoft.Azure.WebJobs.Host.Protocols; | |
using Microsoft.Azure.WebJobs.Host.Triggers; | |
using Microsoft.Extensions.Options; | |
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
internal sealed class StartupTriggerBinding : ITriggerBinding | |
{ | |
private readonly ParameterInfo _parameter; | |
private readonly IOptions<StartupTriggerOptions> _options; | |
public Type TriggerValueType => typeof(StartupInfo); | |
public IReadOnlyDictionary<string, Type> BindingDataContract => new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); | |
public StartupTriggerBinding(ParameterInfo parameter, IOptions<StartupTriggerOptions> options) | |
{ | |
_parameter = parameter ?? throw new ArgumentNullException(nameof(parameter)); | |
_options = options ?? throw new ArgumentNullException(nameof(options)); | |
} | |
public Task<ITriggerData> BindAsync(object value, ValueBindingContext context) | |
{ | |
var startupInfo = value as StartupInfo ?? StartupInfo.Create(_options.Value?.Title, _options.Value?.Version); | |
var valueProvider = new ValueProvider(startupInfo); | |
var bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); | |
return Task.FromResult<ITriggerData>(new TriggerData(valueProvider, bindingData)); | |
} | |
public Task<IListener> CreateListenerAsync(ListenerFactoryContext context) | |
{ | |
return context == null | |
? throw new ArgumentNullException(nameof(context)) | |
: Task.FromResult<IListener>(new StartupTriggerListener(context.Executor, _options)); | |
} | |
public ParameterDescriptor ToParameterDescriptor() => new StartupTriggerParameterDescriptor | |
{ | |
Name = _parameter.Name | |
}; | |
private class ValueProvider : IValueProvider | |
{ | |
private readonly object _value; | |
public ValueProvider(object value) => _value = value; | |
public Type Type => typeof(StartupInfo); | |
public Task<object> GetValueAsync() => Task.FromResult(_value); | |
public string ToInvokeString() => string.Empty; | |
} | |
private class StartupTriggerParameterDescriptor : TriggerParameterDescriptor | |
{ | |
public override string GetTriggerReason(IDictionary<string, string> _) => "Trigger fired when host started"; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Devlight.Azure.Functions.Extensions.Startup.Bindings | |
{ | |
using System; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using Microsoft.Azure.WebJobs.Host.Triggers; | |
using Microsoft.Extensions.Options; | |
internal sealed class StartupTriggerBindingProvider : ITriggerBindingProvider | |
{ | |
private readonly IOptions<StartupTriggerOptions> _options; | |
public StartupTriggerBindingProvider(IOptions<StartupTriggerOptions> options) | |
=> _options = options ?? throw new ArgumentNullException(nameof(options)); | |
public Task<ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext context) | |
{ | |
if (context == null) | |
{ | |
throw new ArgumentNullException(nameof(context)); | |
} | |
var parameter = context.Parameter; | |
var attribute = parameter.GetCustomAttribute<StartupTriggerAttribute>(inherit: false); | |
if (attribute == null) | |
{ | |
return Task.FromResult<ITriggerBinding>(null); | |
} | |
if (parameter.ParameterType != typeof(StartupInfo)) | |
{ | |
throw new InvalidOperationException( | |
string.Format("Can't bind StartupTriggerAttribute to type '{0}'.", parameter.ParameterType)); | |
} | |
return Task.FromResult<ITriggerBinding>(new StartupTriggerBinding(parameter, _options)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Devlight.Azure.Functions.Extensions.Startup | |
{ | |
using Bindings; | |
using Microsoft.Azure.WebJobs.Description; | |
using Microsoft.Azure.WebJobs.Host.Config; | |
using Microsoft.Extensions.Options; | |
using System; | |
[Extension("Startup Trigger")] | |
public sealed class StartupTriggerExtensionConfigProvider : IExtensionConfigProvider | |
{ | |
private readonly IOptions<StartupTriggerOptions> _options; | |
public StartupTriggerExtensionConfigProvider(IOptions<StartupTriggerOptions> options) | |
=> _options = options ?? throw new ArgumentNullException(nameof(options)); | |
public void Initialize(ExtensionConfigContext context) | |
{ | |
if (context == null) | |
{ | |
throw new ArgumentNullException(nameof(context)); | |
} | |
context.AddBindingRule<StartupTriggerAttribute>().BindToTrigger(new StartupTriggerBindingProvider(_options)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Devlight.Azure.Functions.Extensions.Startup; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Hosting; | |
[assembly: WebJobsStartup(typeof(StartupTriggerExtensionRegistration), "Startup Trigger")] | |
namespace Devlight.Azure.Functions.Extensions.Startup | |
{ | |
public sealed class StartupTriggerExtensionRegistration : IWebJobsStartup | |
{ | |
public void Configure(IWebJobsBuilder builder) => builder.AddExtension<StartupTriggerExtensionConfigProvider>(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Devlight.Azure.Functions.Extensions.Startup.Listeners | |
{ | |
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Host.Executors; | |
using Microsoft.Azure.WebJobs.Host.Listeners; | |
using Microsoft.Extensions.Options; | |
[Singleton(Mode = SingletonMode.Listener)] | |
internal sealed class StartupTriggerListener : IListener | |
{ | |
private readonly ITriggeredFunctionExecutor _executor; | |
private readonly IOptions<StartupTriggerOptions> _options; | |
private readonly CancellationTokenSource _cancellationTokenSource; | |
private bool _disposed; | |
public StartupTriggerListener(ITriggeredFunctionExecutor executor, IOptions<StartupTriggerOptions> options) | |
{ | |
_executor = executor ?? throw new ArgumentNullException(nameof(executor)); | |
_options = options ?? throw new ArgumentNullException(nameof(options)); | |
_cancellationTokenSource = new CancellationTokenSource(); | |
} | |
public async Task StartAsync(CancellationToken cancellationToken) | |
{ | |
ThrowIfDisposed(); | |
await InvokeJobFunction(); | |
} | |
public Task StopAsync(CancellationToken cancellationToken) | |
{ | |
ThrowIfDisposed(); | |
_cancellationTokenSource.Cancel(); | |
return Task.FromResult(true); | |
} | |
public void Cancel() | |
{ | |
ThrowIfDisposed(); | |
_cancellationTokenSource.Cancel(); | |
} | |
public void Dispose() | |
{ | |
if (!_disposed) | |
{ | |
_cancellationTokenSource.Cancel(); | |
_cancellationTokenSource.Dispose(); | |
_disposed = true; | |
} | |
} | |
/// <summary> | |
/// Invokes the job function. | |
/// </summary> | |
internal async Task InvokeJobFunction() | |
{ | |
var token = _cancellationTokenSource.Token; | |
var startupInfo = StartupInfo.Create(_options.Value?.Title, _options.Value?.Version); | |
var input = new TriggeredFunctionData | |
{ | |
TriggerValue = startupInfo | |
}; | |
try | |
{ | |
var result = await _executor.TryExecuteAsync(input, token); | |
if (!result.Succeeded) | |
{ | |
token.ThrowIfCancellationRequested(); | |
} | |
} | |
catch | |
{ | |
// We don't want any function errors to stop the execution | |
// schedule. Errors will be logged to Dashboard already. | |
} | |
} | |
private void ThrowIfDisposed() | |
{ | |
if (_disposed) | |
{ | |
throw new ObjectDisposedException(null); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment