All Projects → fbeltrao → AzureFunctionExtensions

fbeltrao / AzureFunctionExtensions

Licence: MIT license
Set of Azure Function Extensions: Redis output and IDatabase resolving, HTTP calls, etc.

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to AzureFunctionExtensions

serverlessnotifications
Serverless notifications with Azure Cosmos DB + Azure Functions + Azure SignalR
Stars: ✭ 60 (+87.5%)
Mutual labels:  azure-functions, signalr
TextMood
A Xamarin + IoT + Azure sample that detects the sentiment of incoming text messages, performs sentiment analysis on the text, and changes the color of a Philips Hue lightbulb
Stars: ✭ 52 (+62.5%)
Mutual labels:  azure-functions, signalr
AspNetCore.Client
On Build client generator for asp.net core projects
Stars: ✭ 14 (-56.25%)
Mutual labels:  azure-functions, signalr
AzureContainerInstancesManagement
Hosting game servers at scale using Azure Container Instances, using Azure Functions and Event Grid. Demo with OpenArena game server!
Stars: ✭ 41 (+28.13%)
Mutual labels:  azure-functions, azure-event-grid
TraceHub
Centralized and distributed logging for Web applications and services, extending System.Diagnostics and Essential.Diagnostics, providing structured tracing and logging withou needing to change 1 line of your application codes
Stars: ✭ 22 (-31.25%)
Mutual labels:  signalr
functions-extension-101
Learn how to create your own Azure Functions extension in 5 steps
Stars: ✭ 35 (+9.38%)
Mutual labels:  azure-functions
powerbi-embed-v2
Power BI Embedded with Custom Controls PoC
Stars: ✭ 34 (+6.25%)
Mutual labels:  azure-functions
shorty
URL shortener available as library, microservice (even containerized), aws lambda, and azure function
Stars: ✭ 31 (-3.12%)
Mutual labels:  azure-functions
Chatazon
Implementing websockets in .NET Core
Stars: ✭ 19 (-40.62%)
Mutual labels:  signalr
signalr
SignalR server and client in go
Stars: ✭ 69 (+115.63%)
Mutual labels:  signalr
online-training
Online Training website using ASP.Net Core 2.0 & Angular 4
Stars: ✭ 26 (-18.75%)
Mutual labels:  signalr
GAPITA
An anonymous and random chat messaging for talking to strangers! (Using SignalR C# and TypeScript)
Stars: ✭ 55 (+71.88%)
Mutual labels:  signalr
Scavenger
A virtual "scavenger hunt" game for mobile devices using Unity, Azure, and PlayFab
Stars: ✭ 19 (-40.62%)
Mutual labels:  azure-functions
QueueBatch
WebJobs/Azure Functions trigger providing batches of Azure Storage Queues messages directly to your function
Stars: ✭ 40 (+25%)
Mutual labels:  azure-functions
Foundatio.Samples
Foundatio Samples
Stars: ✭ 34 (+6.25%)
Mutual labels:  signalr
multicloud
The serverless @multicloud library provides an easy way to write your serverless handlers 1 time and deploy them to multiple cloud providers include Azure & AWS.
Stars: ✭ 79 (+146.88%)
Mutual labels:  azure-functions
azure-maven-archetypes
Maven Archetypes for Microsoft Azure Services
Stars: ✭ 19 (-40.62%)
Mutual labels:  azure-functions
AzureFunctionsSecurity
Azure Functions Security
Stars: ✭ 23 (-28.12%)
Mutual labels:  azure-functions
IotHub
Cloud based IoT system solution. MQTT Broker, MQTT Agent, SignalR Hub, Data Source API
Stars: ✭ 34 (+6.25%)
Mutual labels:  signalr
azure-functions-ts-essentials
Essential interfaces and tools for backend development on Azure Functions with TypeScript
Stars: ✭ 21 (-34.37%)
Mutual labels:  azure-functions

Azure Function Extensions

Azure functions allows you to write code without worrying too much about the infrastructure behind (aka Serverless).

Serverless is often used in an event driven architecture, where your code react as events are happening around it: a file is created, a new user is registered, etc.

This repository contains a few Azure Function extensions that I write to help me succeed using the framework.

Build status

Contents

Using this library

There are two ways to use this library:

Available Bindings in this library

Direction Type Description
Output Redis Allows a function to interact with Redis. Following operations are currently supported: add/insert item to lists, set a key, increment a key value in Redis. For read or more complex operations you can use the [RedisDatabase] attribute that will resolve a IDatabase to your function
Output HttpCall Allows a function to make an HTTP call easily, handy when you need to call a Webhook or an url to notify a change
Output EventGrid Allows a function to easily publish Azure Event Grid events. Important: Subscribing to an Event Grid topic is already part of the Azure Function runtime, no need to use a custom binding
Output Azure SignalR Allows a function to easily send messages to Azure SignalR connected clients. Allowed scopes are: broadcast to hub, to one or more groups and to one or more users. For more information about Azure SignalR Service check here

Have a suggestion? Create an issue and I will take a look.

Examples

Redis: Writing to Redis from an Azure Function

/// <summary>
/// Sets a value in Redis
/// </summary>
/// <param name="req"></param>
/// <param name="redisItem"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName(nameof(SetValueInRedis))]
public static IActionResult SetValueInRedis(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    [RedisOutput(Connection = "%redis_connectionstring%", Key = "%redis_setvalueinredis_key%")]  out RedisOutput redisItem,
    TraceWriter log)
{
    string requestBody = new StreamReader(req.Body).ReadToEnd();

    redisItem = new RedisItem()
    {
        TextValue = requestBody
    };

    return new OkResult();
}

HttpCall: Making a POST request to an URL

/// <summary>
/// Calls a web site to notify about a change
/// </summary>
/// <param name="messages"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName(nameof(CallWebsite))]
public static async Task CallWebsite(     
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
    [HttpCall] IAsyncCollector<HttpCallMessage> messages, 
    TraceWriter log)
{    
    // compute something
    var computedValue = new { payload: "3123213" };

    // computed value will be posted to another web site
    await messages.AddAsync(new HttpCallMessage("url-to-webhook")
        .AsJsonPost(computedValue)
        );   
}

EventGrid: Publishing an Azure Event Grid event

/// <summary>
/// Publishes an Event Grid event
/// </summary>
/// <param name="req"></param>
/// <param name="outputEvent"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName(nameof(PublishEventGridEvent))]
public static IActionResult PublishEventGridEvent(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
    [EventGridOutput(SasKey = "%eventgrid_sas%", TopicEndpoint = "%eventgrid_endpoint%",  EventType = "EventGridSample.PublishEventGridEvent.Event", Subject = "message/fromAzureFunction")] out EventGridOutput outputEvent,
    TraceWriter log)
{
    outputEvent = null;

    // POST? used the body as the content of the event
    string requestBody = new StreamReader(req.Body).ReadToEnd();

    if (!string.IsNullOrEmpty(requestBody))
    {
        outputEvent = new EventGridOutput(JsonConvert.DeserializeObject(requestBody));
    }
    else
    {
        outputEvent = new EventGridOutput(new
        {
            city = "Zurich",
            country = "CHE",
            customerID = 123120,
            firstName = "Mark",
            lastName = "Muller",
            userID = "123",
        })
        .WithEventType("MyCompany.Customer.Created") // override the attribute event type
        .WithSubject($"customer/created"); // override the attribute subject
    }

    return new OkResult();
}

Azure SignalR: Sending messages to Azure SignalR Service from an Azure Function

/// <summary>
/// Http triggered function to broadcast to a SignalR hub
/// </summary>
/// <param name="req"></param>
/// <param name="message"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName(nameof(HttpTriggerBroadcastToHub))]
public static IActionResult HttpTriggerBroadcastToHub(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
    [SignalR(ServiceName = "%service_name%", AccessKey = "%access_key%")] out SignalRMessage message,
    TraceWriter log)
{
    message = new SignalRMessage()
    {
        Target = "broadcastMessage",
        Hub = "chat",
        Arguments = new object[] { $"Hub broadcast from function {nameof(HttpTriggerBroadcastToHub)}", $"Now it is {DateTime.Now}" }
    };

    return new OkResult();
}

What is a Custom Binding?

A function has a trigger (the reason why it should run) and usually an output (what is the end result of running it).

Triggers Outputs
  • An item was added to a queue
  • A direct HTTP call
  • A timer (every x minutes)
  • many more
  • Add an item to a queue
  • Send an email
  • Write to a database

The Azure Functions framework allows you to write your own triggers and outputs. Triggers are a bit complicated to implement, since you need to implement a way to notify the function host once a trigger happens. Outputs, on the other hand, are simple to be customized.

Why should I write my own output binding?

Let's imagine that your team has a couple of workloads based on Azure Functions. Some of your functions need to write computed results to Redis, which will be consumed by a frontend App. Without using custom bindings your code would look like this (assuming you are using StackExchange.Redis):

public static void WriteToRedisFunction1()
{
    // do some magic
    var computedValue = new { magicNumber = 10 };

    // here goes the "write to redis code"
    var db = ConnectionMultiplexer.Connect("connection-string-to-redis").GetDatabase();
    db.StringSet("myKeyValue", computedValue);
}

public static void WriteToRedisFunction2()
{
    // do yet another magic
    var anotherComputedValue = new { magicNumber = 20 };

    // "write to redis" again
    var db = ConnectionMultiplexer.Connect("connection-string-to-redis").GetDatabase();
    db.StringSet("myKeyValue2", anotherComputedValue);
}

You already see a code smell: repeating code. You could move all the Redis specific code to a shared class/library, but that would mean using DI to this library in your unit tests.

What if you could write your functions like this:

public static void WriteToRedisFunction1(
    [RedisOutput] IAsyncCollector<RedisOutput> redisOutput)
{
    // do some magic
    var computedValue = new { magicNumber = 10 };

    // write to a collection
    redisOutput.AddAsync(new RedisOutput()
    {
        ObjectValue = computedValue
    });

}

public static void WriteToRedisFunction2(
    [RedisOutput] IAsyncCollector<RedisOutput> redisOutput
)
{
    // do yet another magic
    var anotherComputedValue = new { magicNumber = 20 };

    // write to a collection
    redisOutput.AddAsync(new RedisOutput()
    {
        ObjectValue = computedValue
    });
}

Testing is easier. You check the contents of the "redisOutput" to ensure the expected items were created. No DI needed.

Creating a custom output binding

Creating a custom binding requires a few steps:

  1. Implement the attribute that tells the Azure Function host about your binding.
/// <summary>
/// Binds a function parameter to write to Redis
/// </summary>
[AttributeUsage(AttributeTargets.ReturnValue | AttributeTargets.Parameter)]
[Binding]
public sealed class RedisOutputAttribute : Attribute, IConnectionProvider
{
    /// <summary>
    /// Redis item key
    /// </summary>
    [AutoResolve(Default = "Key")]
    public string Key { get; set; }

    /// <summary>
    /// Defines the Redis server connection string
    /// </summary>
    [AutoResolve(Default = "ConnectionString")]
    public string Connection { get; set; }

    /// <summary>
    /// Send items in batch to Redis
    /// </summary>
    public bool SendInBatch { get; set; } = true;

    /// <summary>
    /// Sets the operation to performed in Redis
    /// Default is <see cref="RedisOutputOperation.SetKeyValue"/>
    /// </summary>
    public RedisOutputOperation Operation { get; set; } = RedisOutputOperation.SetKeyValue;
    
    /// <summary>
    /// Time to live in Redis
    /// </summary>
    public TimeSpan? TimeToLive { get; set; }
}
  1. Define the Redis output object
/// <summary>
/// Defines a Redis item to be saved into the database
/// </summary>
public class RedisOutput 
{
    /// <summary>
    /// Redis item key
    /// </summary>
    [JsonProperty("key")]
    public string Key { get; set; }

    /// <summary>
    /// Defines the value as text to be set
    /// </summary>
    [JsonProperty("textValue")]
    public string TextValue { get; set; }

    /// <summary>
    /// Defines the value as object to be set. The content will be converted to json using JsonConvert.
    /// </summary>
    [JsonProperty("objectValue")]
    public object ObjectValue { get; set; }

    /// <summary>
    /// Defines the value as a byte array to be set
    /// </summary>
    [JsonProperty("binaryValue")]
    public byte[] BinaryValue { get; set; }

    /// <summary>
    /// Sets the operation to performed in Redis
    /// </summary>
    [JsonProperty("operation")]
    public RedisOutputOperation Operation { get; set; }

    /// <summary>
    /// Time to live in Redis
    /// </summary>
    [JsonProperty("ttl")]
    public TimeSpan? TimeToLive { get; set; }

    /// <summary>
    /// Value to increment by when used in combination with <see cref="RedisOutputOperation.IncrementValue"/>
    /// Default: 1
    /// </summary>
    [JsonProperty("incrementValue")]
    public long IncrementValue { get; set; } = 1;
}
  1. Implement the code which sends single/multiple RedisOutputs to Redis, by implementing an IAsyncCollector:
/// <summary>
/// Collector for <see cref="RedisOutput"/>
/// </summary>
public class RedisOutputAsyncCollector : IAsyncCollector<RedisOutput>
{

    ...

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="config"></param>
    /// <param name="attr"></param>
    public RedisItemAsyncCollector(RedisExtensionConfigProvider config, RedisOutputAttribute attr) : this(config, attr, RedisDatabaseManager.GetInstance())
    {
    }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="config"></param>
    /// <param name="attr"></param>
    public RedisItemAsyncCollector(RedisExtensionConfigProvider config, RedisOutputAttribute attr, IRedisDatabaseManager redisDatabaseManager)
    {
        this.config = config;
        this.attr = attr;
        this.redisDatabaseManager = redisDatabaseManager;
        this.redisOutputCollection = new List<RedisOutput>();
    }

    /// <summary>
    /// Adds item to collection to be sent to redis
    /// </summary>
    /// <param name="item"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task AddAsync(RedisOutput item, CancellationToken cancellationToken = default(CancellationToken))
    {
        ...
    }

    /// <summary>
    /// Flushs all items to redis
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        ...
    }    
}
  1. Implement the extension configuration provider which will instruct the Azure Function Host how to bind attributes, items and collectors together:
/// <summary>
/// Initializes the Redis binding
/// </summary>
public class RedisConfiguration : IExtensionConfigProvider
{
    ...

    /// <summary>
    /// Initializes attributes, configuration and async collector
    /// </summary>
    /// <param name="context"></param>
    public void Initialize(ExtensionConfigContext context)
    {
        // Converts json to RedisItem
        context.AddConverter<JObject, RedisOutput>(input => input.ToObject<RedisOutput>());

        // Redis output binding
        context
            .AddBindingRule<RedisOutputAttribute>()
            .BindToCollector<RedisOutput>(attr => new RedisOutputAsyncCollector(this, attr));

        // Redis database (input) binding
        context
            .AddBindingRule<RedisDatabaseAttribute>()
            .BindToInput<IDatabase>(ResolveRedisDatabase);
    }
}

Important: parts of the code were removed to keep the post simple. Check the source code for the complete implementation.

  1. Register the extensions using the WebJobsStartup attribute
[assembly: WebJobsStartup(typeof(Fbeltrao.AzureFunctionExtensions.Startup))]

namespace Fbeltrao.AzureFunctionExtensions
{
    /// <summary>
    /// Extension initializer
    /// </summary>
    public class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.AddExtension<RedisConfiguration>();
        }
    }
}
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].