Riesgos y buenas prácticas para configurar el Wi-Fi en el lugar de trabajo
El «Wi-Fi semilibre» es cuando una empresa ofrece a sus empleados un acceso casi libre al Wi-Fi, junto con algunas medidas de monitorización sobre lo...
Durante la implementación de una funcionalidad en mi trabajo me encontré con un problema:
Con estos criterios en mente, busqué inspiración en internet. Por desgracia, no encontré nada útil. Así que decidí resolver el problema por mi cuenta y compartir la idea contigo.
A modo de demostración, he creado un proyecto de ejemplo disponible en GitHub. En este artículo te guiaré por el código y explicaré la idea para que puedas aplicarla en tu propio proyecto.
El ejemplo es la función AreaCalculator, activada vía HTTP, que puede calcular el área de figuras geométricas comunes. Se invoca mediante el método POST con la ruta calculateArea/{shape}. El parámetro de ruta {shape} representa la forma para la cual se calculará el área. Los datos de la forma se especifican como JSON en el cuerpo de la petición.
Como puedes ver, aquí estamos inyectando IAreaCalculatorFactory. La factory proporciona la implementación adecuada del calculador en función del tipo de forma extraído del parámetro de ruta. El calculador entonces calcula el área de la forma específica, y el resultado se devuelve como JSON en la respuesta OK.
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));
}
}
La factory del calculador de áreas es directa. Mantiene un diccionario de servicios calculadores registrados para cada tipo de forma disponible e implementa el método GetAreaCalculatorForShape. Este método recupera los servicios calculadores registrados directamente del IServiceProvider según el tipo de forma proporcionado.
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);
}
}
Quizás te preguntes cómo inicializar y registrar todo esto fácilmente para poder inyectarlo donde haga falta. El service provider que se inyecta en la factory es inmutable y debe contener ya todos los servicios calculadores y su árbol de dependencias.
Entonces, ¿dónde y cómo se registran los calculadores? La respuesta es AreaCalculatorFactoryBuilder. Recibe la colección de servicios en su constructor, lo que significa que puedes registrar servicios calculadores específicos para tipos de forma concretos y guardar el mapeo Shape → IAreaCalculatorService al mismo tiempo mediante el método RegisterCalculatorForType.
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);
}
}
La factory y sus servicios se registran como se muestra a continuación. AreaCalculatorFactoryBuilder se inicializa de antemano, y el método Build se invoca dentro del delegate del service provider cuando se necesita la implementación de IAreaCalculatorFactory.
También se aprecia el registro del parser JSON personalizado utilizado por los servicios calculadores para deserializar el stream del cuerpo de la petición y convertirlo en los datos necesarios para calcular el área. Esto demuestra que los servicios proporcionados por la factory pueden tener otras dependencias inyectadas mediante la inyección de dependencias de .NET.
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;
}
}
Por último, una breve explicación de las implementaciones de CircleAreaCalculator y ShapeParser. Como puedes ver, CircleAreaCalculator es una implementación de la clase abstracta AreaCalculatorBase. AreaCalculatorBase se encarga del parseo y proporciona los datos a las implementaciones específicas para que estas puedan realizar los cálculos requeridos. El parser utiliza el serializador JSON integrado de .NET con opciones personalizadas para deserializar el stream al tipo requerido.
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;
}
}
La solución resultante es:
El proyecto de ejemplo se puede ejecutar en local. Basta con arrancar la function app y abrir http://localhost:{function-app-port}/api/swagger/ui en el navegador. Allí puedes usar Swagger UI para hacer llamadas a la API.
El «Wi-Fi semilibre» es cuando una empresa ofrece a sus empleados un acceso casi libre al Wi-Fi, junto con algunas medidas de monitorización sobre lo...
La integración de la solución de Data Loss Prevention de Safetica con los productos de Fortinet ofrece a las empresas una herramienta potente para...
Descubre cómo fue la Safetica Accelerate Partner Conference en Medellín. Intelligent Data Security y una experiencia única de conexión.