CommandQuery.AzureFunctions
3.0.0
See the version list below for details.
dotnet add package CommandQuery.AzureFunctions --version 3.0.0
NuGet\Install-Package CommandQuery.AzureFunctions -Version 3.0.0
<PackageReference Include="CommandQuery.AzureFunctions" Version="3.0.0" />
paket add CommandQuery.AzureFunctions --version 3.0.0
#r "nuget: CommandQuery.AzureFunctions, 3.0.0"
// Install CommandQuery.AzureFunctions as a Cake Addin #addin nuget:?package=CommandQuery.AzureFunctions&version=3.0.0 // Install CommandQuery.AzureFunctions as a Cake Tool #tool nuget:?package=CommandQuery.AzureFunctions&version=3.0.0
CommandQuery.AzureFunctions ⚡
Command Query Separation for Azure Functions
- Provides generic function support for commands and queries with HTTPTriggers
- Enables APIs based on HTTP
POST
andGET
Get Started
- Install Azure development workload in Visual Studio
- Create a new Azure Functions (isolated worker process) project
- Install the
CommandQuery.AzureFunctions
package from NuGetPM>
Install-Package CommandQuery.AzureFunctions
- Create functions
- Preferably named
Command
andQuery
- Preferably named
- Create commands and command handlers
- Implement
ICommand
andICommandHandler<in TCommand>
- Or
ICommand<TResult>
andICommandHandler<in TCommand, TResult>
- Implement
- Create queries and query handlers
- Implement
IQuery<TResult>
andIQueryHandler<in TQuery, TResult>
- Implement
- Configure services in
Program.cs
Choose:
- .NET 6.0 Isolated (Long Term Support)
- Http trigger
Commands
using CommandQuery.AzureFunctions;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace CommandQuery.Sample.AzureFunctions.V6
{
public class Command
{
private readonly ICommandFunction _commandFunction;
private readonly ILogger _logger;
public Command(ICommandFunction commandFunction, ILoggerFactory loggerFactory)
{
_commandFunction = commandFunction;
_logger = loggerFactory.CreateLogger<Command>();
}
[Function("Command")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "command/{commandName}")] HttpRequestData req, FunctionContext executionContext, string commandName) =>
await _commandFunction.HandleAsync(commandName, req, _logger, executionContext.CancellationToken);
}
}
- The function is requested via HTTP
POST
with the Content-Typeapplication/json
in the header. - The name of the command is the slug of the URL.
- The command itself is provided as JSON in the body.
- If the command succeeds; the response is empty with the HTTP status code
200
. - If the command fails; the response is an error message with the HTTP status code
400
or500
.
Commands with result:
- If the command succeeds; the response is the result as JSON with the HTTP status code
200
.
Queries
using CommandQuery.AzureFunctions;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace CommandQuery.Sample.AzureFunctions.V6
{
public class Query
{
private readonly IQueryFunction _queryFunction;
private readonly ILogger _logger;
public Query(IQueryFunction queryFunction, ILoggerFactory loggerFactory)
{
_queryFunction = queryFunction;
_logger = loggerFactory.CreateLogger<Query>();
}
[Function("Query")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "query/{queryName}")] HttpRequestData req, FunctionContext executionContext, string queryName) =>
await _queryFunction.HandleAsync(queryName, req, _logger, executionContext.CancellationToken);
}
}
- The function is requested via:
- HTTP
POST
with the Content-Typeapplication/json
in the header and the query itself as JSON in the body - HTTP
GET
and the query itself as query string parameters in the URL
- HTTP
- The name of the query is the slug of the URL.
- If the query succeeds; the response is the result as JSON with the HTTP status code
200
. - If the query fails; the response is an error message with the HTTP status code
400
or500
.
Configuration
Configuration in Program.cs
:
using CommandQuery;
using CommandQuery.AzureFunctions;
using CommandQuery.Sample.Contracts.Commands;
using CommandQuery.Sample.Contracts.Queries;
using CommandQuery.Sample.Handlers;
using CommandQuery.Sample.Handlers.Commands;
using CommandQuery.Sample.Handlers.Queries;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(ConfigureServices)
.Build();
// Validation
host.Services.GetService<ICommandProcessor>()!.AssertConfigurationIsValid();
host.Services.GetService<IQueryProcessor>()!.AssertConfigurationIsValid();
host.Run();
public static partial class Program
{
public static void ConfigureServices(IServiceCollection services)
{
services
//.AddSingleton(new JsonSerializerOptions(JsonSerializerDefaults.Web));
// Add commands and queries
.AddCommandFunction(typeof(FooCommandHandler).Assembly, typeof(FooCommand).Assembly)
.AddQueryFunction(typeof(BarQueryHandler).Assembly, typeof(BarQuery).Assembly)
// Add handler dependencies
.AddTransient<IDateTimeProxy, DateTimeProxy>()
.AddTransient<ICultureService, CultureService>();
}
}
The extension methods AddCommandFunction
and AddQueryFunction
will add functions and all command/query handlers in the given assemblies to the IoC container.
You can pass in a params
array of Assembly
arguments if your handlers are located in different projects.
If you only have one project you can use typeof(Program).Assembly
as a single argument.
Testing
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using CommandQuery.AzureFunctions;
using CommandQuery.Sample.Contracts.Queries;
using FluentAssertions;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
namespace CommandQuery.Sample.AzureFunctions.V6.Tests
{
public class QueryTests
{
public class when_using_the_real_function_via_Post
{
[SetUp]
public void SetUp()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<ILoggerFactory, LoggerFactory>();
Program.ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
var context = new Mock<FunctionContext>();
context.SetupProperty(c => c.InstanceServices, serviceProvider);
ExecutionContext = context.Object;
Subject = new Query(serviceProvider.GetService<IQueryFunction>(), serviceProvider.GetService<ILoggerFactory>());
}
[Test]
public async Task should_work()
{
var req = GetHttpRequestData(ExecutionContext, "POST", content: "{ \"Id\": 1 }");
var result = await Subject.Run(req, ExecutionContext, "BarQuery");
var value = await result.AsAsync<Bar>();
value.Id.Should().Be(1);
value.Value.Should().NotBeEmpty();
}
[Test]
public async Task should_handle_errors()
{
var req = GetHttpRequestData(ExecutionContext, "POST", content: "{ \"Id\": 1 }");
var result = await Subject.Run(req, ExecutionContext, "FailQuery");
await result.ShouldBeErrorAsync("The query type 'FailQuery' could not be found");
}
FunctionContext ExecutionContext;
Query Subject;
}
public class when_using_the_real_function_via_Get
{
[SetUp]
public void SetUp()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<ILoggerFactory, LoggerFactory>();
Program.ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
var context = new Mock<FunctionContext>();
context.SetupProperty(c => c.InstanceServices, serviceProvider);
ExecutionContext = context.Object;
Subject = new Query(serviceProvider.GetService<IQueryFunction>(), serviceProvider.GetService<ILoggerFactory>());
}
[Test]
public async Task should_work()
{
var req = GetHttpRequestData(ExecutionContext, "GET", url: "http://localhost?Id=1");
var result = await Subject.Run(req, ExecutionContext, "BarQuery");
var value = await result.AsAsync<Bar>();
value.Id.Should().Be(1);
value.Value.Should().NotBeEmpty();
}
[Test]
public async Task should_handle_errors()
{
var req = GetHttpRequestData(ExecutionContext, "GET", url: "http://localhost?Id=1");
var result = await Subject.Run(req, ExecutionContext, "FailQuery");
await result.ShouldBeErrorAsync("The query type 'FailQuery' could not be found");
}
FunctionContext ExecutionContext;
Query Subject;
}
static HttpRequestData GetHttpRequestData(FunctionContext executionContext, string method, string content = null, string url = null)
{
var request = new Mock<HttpRequestData>(executionContext);
request.Setup(r => r.Method).Returns(method);
if (content != null)
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
request.Setup(r => r.Body).Returns(stream);
}
if (url != null)
{
request.Setup(r => r.Url).Returns(new Uri(url));
}
request.Setup(r => r.CreateResponse()).Returns(() =>
{
var response = new Mock<HttpResponseData>(executionContext);
response.SetupProperty(r => r.Headers, new HttpHeadersCollection());
response.SetupProperty(r => r.StatusCode);
response.SetupProperty(r => r.Body, new MemoryStream());
return response.Object;
});
return request.Object;
}
}
}
Samples
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- CommandQuery (>= 3.0.0)
- CommandQuery.SystemTextJson (>= 3.0.0)
- Microsoft.Azure.Functions.Worker (>= 1.10.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on CommandQuery.AzureFunctions:
Repository | Stars |
---|---|
hlaueriksson/CommandQuery
Command Query Separation for 🌐ASP.NET Core ⚡AWS Lambda ⚡Azure Functions ⚡Google Cloud Functions
|
Version | Downloads | Last updated |
---|---|---|
4.0.0 | 117 | 7/13/2024 |
3.0.0 | 525 | 1/9/2023 |
2.0.0 | 606 | 7/29/2021 |
1.0.0 | 720 | 2/2/2020 |
0.9.0 | 693 | 11/20/2019 |
0.8.0 | 846 | 2/16/2019 |
0.7.0 | 920 | 9/22/2018 |
0.6.0 | 974 | 9/15/2018 |
0.5.0 | 1,156 | 7/6/2018 |
0.4.0 | 1,133 | 5/16/2018 |
0.3.2 | 1,166 | 5/1/2018 |
0.3.1 | 1,185 | 1/6/2018 |
0.3.0 | 1,157 | 1/3/2018 |
0.2.1-beta | 881 | 6/7/2017 |
0.2.0 | 1,130 | 4/25/2017 |
- Change TargetFramework to netstandard2.0
- Drop support for in-process functions
- Only support isolated worker process functions
- Bump Microsoft.Azure.Functions.Worker to 1.10.0
- Add CancellationToken parameter to HandleAsync method in CommandFunction and QueryFunction