Australian Privacy Principles: The Scope, Purpose, and How to Comply
Learn the scope and purpose of the Australian Privacy Principles and how to comply to protect personal data and meet privacy regulations.
During the implementation of a feature in my work, I came across a problem:
With these criteria in mind, I searched the internet for inspiration. Unfortunately, I could not find anything helpful. So, I decided to solve the problem myself and share the idea with you.
For the sake of demonstration, I created an example project you can find on GitHub. In this article, I will guide you through the code and explain the idea so you can apply it in your own project.
The example here is the HTTP-triggered AreaCalculator function, which can compute the area of common geometrical shapes. It is triggered via the POST method with the route calculateArea/{shape}. The {shape} route parameter represents the shape for which the area will be calculated. The shape data is specified as JSON in the request body.
As you can see, we are injecting the IAreaCalculatorFactory here. The factory provides the required implementation of the calculator based on the shape type parsed from the route parameter. The calculator then computes the area of the specific shape, and the result is returned as JSON in the OK response.
public class AreaCalculator
{
private readonly IAreaCalculatorFactory _calculatorFactory;
private readonly ILogger<AreaCalculator> _logger;
public AreaCalculator(
IAreaCalculatorFactory calculatorFactory,
ILogger<AreaCalculator> log)
{
_calculatorFactory = calculatorFactory;
_logger = log;
}
[FunctionName(nameof(AreaCalculator))]
[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiParameter(
name: "shape",
In = ParameterLocation.Path,
Required = true,
Type = typeof(string),
Description = "The shape (circle, square, rectangle, or triangle)"
)]
[OpenApiResponseWithBody(
statusCode: HttpStatusCode.OK,
contentType: "application/json",
bodyType: typeof(AreaResult),
Description = "The area of the specified shape, rounded to 2 decimal places"
)]
public async Task<IActionResult> RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "calculateArea/{shape}")]
HttpRequest req,
string shape,
CancellationToken ct)
{
_logger.LogInformation("Executing area calculator for shape: {shape}", shape);
if (!Enum.TryParse(shape, true, out Shape shapeType))
return new BadRequestObjectResult($"{shape} shape not supported");
var calculator = _calculatorFactory.GetAreaCalculatorForShape(shapeType);
var result = await calculator.CalculateAreaAsync(req.Body, ct);
return new OkObjectResult(new AreaResult(result));
}
}
The area calculator factory is straightforward. It holds a dictionary of registered calculator services for each available shape type and implements the GetAreaCalculatorForShape method. This method retrieves registered calculator services directly from the IServiceProvider based on the provided shape type.
public class AreaCalculatorFactory : IAreaCalculatorFactory
{
private readonly Dictionary<Shape, Type> _registeredCalculators;
private readonly IServiceProvider _serviceProvider;
public AreaCalculatorFactory(
IServiceProvider serviceProvider,
Dictionary<Shape, Type> registeredCalculators)
{
_registeredCalculators = registeredCalculators;
_serviceProvider = serviceProvider;
}
public IAreaCalculatorService GetAreaCalculatorForShape(Shape shape)
{
if (!_registeredCalculators.TryGetValue(shape, out var calculatorType) || calculatorType == null)
throw new InvalidOperationException($"No calculator registered for shape {shape}");
return (IAreaCalculatorService)_serviceProvider.GetRequiredService(calculatorType);
}
}
Now you might wonder how this can be easily initialized and registered so it can later be injected where needed. The service provider injected into the factory is immutable and must already contain all calculator services and their dependency tree.
Where and how do you register calculators then? The answer is AreaCalculatorFactoryBuilder. It takes the service collection in its constructor, which means you can register specific calculator services for specific shape types and save the Shape → IAreaCalculatorService mapping at the same time using the RegisterCalculatorForType method.
public class AreaCalculatorFactoryBuilder
{
private readonly Dictionary<Shape, Type> _registeredCalculators;
private readonly IServiceCollection _services;
public AreaCalculatorFactoryBuilder(IServiceCollection services)
{
_registeredCalculators = new Dictionary<Shape, Type>();
_services = services;
}
public AreaCalculatorFactoryBuilder RegisterCalculatorForType<TCalculator>(Shape shape)
where TCalculator : IAreaCalculatorService
{
_services.AddScoped(typeof(TCalculator));
_registeredCalculators.Add(shape, typeof(TCalculator));
return this;
}
public IAreaCalculatorFactory Build(IServiceProvider serviceProvider)
{
return new AreaCalculatorFactory(serviceProvider, _registeredCalculators);
}
}
The factory and its services are then registered as shown below. The AreaCalculatorFactoryBuilder is initialized beforehand, and the Build method is called inside the service provider delegate when the IAreaCalculatorFactory implementation is needed.
You can also see the registration of the custom JSON parser used within the calculator services to deserialize the request body stream into the data needed for area calculation. This demonstrates that the services provided by the factory can have other dependencies injected using .NET dependency injection.
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddServices(this IServiceCollection services)
{
// Parsers
services.AddScoped<ICustomParser, ShapeParser>();
// Area calculators
var areaCalculatorFactoryBuilder = new AreaCalculatorFactoryBuilder(services)
.RegisterCalculatorForType<TriangleAreaCalculator>(Shape.Triangle)
.RegisterCalculatorForType<SquareAreaCalculator>(Shape.Square)
.RegisterCalculatorForType<RectangleAreaCalculator>(Shape.Rectangle)
.RegisterCalculatorForType<CircleAreaCalculator>(Shape.Circle);
services.AddScoped(x => areaCalculatorFactoryBuilder.Build(x));
return services;
}
}
Lastly, here is a short explanation of the CircleAreaCalculator and ShapeParser implementations. As you can see, CircleAreaCalculator is an implementation of the abstract class AreaCalculatorBase. AreaCalculatorBase handles parsing and provides data to specific implementations so they can perform the required calculations. The parser uses the built-in .NET JSON serializer with custom options to deserialize the stream into the required type.
public class CircleAreaCalculator : AreaCalculatorBase<Circle>
{
public CircleAreaCalculator(ICustomParser shapeParser) : base(shapeParser) { }
protected override double CalculateArea(Circle circle)
=> Math.Pow(circle.R, 2) * Math.PI;
}
public abstract class AreaCalculatorBase<TShape> : IAreaCalculatorService
{
private readonly ICustomParser _shapeParser;
public AreaCalculatorBase(ICustomParser shapeParser)
{
_shapeParser = shapeParser;
}
private async Task<TShape> GetShapeAsync(Stream stream, CancellationToken ct)
=> await _shapeParser.ParseJsonBodyAsync<TShape>(stream, ct);
public async Task<double> CalculateAreaAsync(Stream stream, CancellationToken ct)
=> CalculateArea(await GetShapeAsync(stream, ct));
protected abstract double CalculateArea(TShape shape);
}
public class ShapeParser : ICustomParser
{
private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
public async Task<T> ParseJsonBodyAsync<T>(Stream stream, CancellationToken ct = default)
{
T? val = await JsonSerializer.DeserializeAsync<T>(stream, SerializerOptions, ct);
if (val == null)
{
throw new JsonException("Null payload");
}
return val;
}
}
The resulting solution is:
The example project can be run locally. Just run the function app and open http://localhost:{function-app-port}/api/swagger/ui in your browser. There you can use Swagger UI to make API calls.
Learn the scope and purpose of the Australian Privacy Principles and how to comply to protect personal data and meet privacy regulations.
Learn the 6 key GDPR changes, including consent rules, breach reporting, fines, and data protection requirements impacting businesses.
Safetica earns 3 InfoSec honors at RSA 2025 for leading insider threat protection and smart DLP. Discover what makes us a top innovator.