Uno de los aspectos fundamentales en el desarrollo de aplicaciones es mantener un código limpio y organizado. Esto no solo hace que el proceso de programación sea más eficiente, sino que también mejora la seguridad y el rendimiento del software. Una de las mejores prácticas que se puede implementar desde el inicio es el uso de parámetros únicos en las funciones. Por ejemplo, el parámetro GetFileParameters en lugar de varios parámetros individuales. Esta estrategia no solo ayuda a mantener la firma de la función intacta, sino que también facilita la evolución de la aplicación sin cambiar la interfaz de la función. En este caso, el parámetro puede tomar muchas propiedades diferentes, lo que otorga flexibilidad sin sacrificar claridad.
Otro punto clave es la nomenclatura utilizada en el código. Es fundamental que las variables, los parámetros de los métodos, los métodos, las clases y los campos se nombren de acuerdo con convenciones bien definidas. En este caso, se usa el formato Pascal para los nombres de las clases, namespaces y propiedades, y el formato Camel para los nombres de las variables y parámetros de métodos. Este tipo de convenciones no solo aumenta la legibilidad del código, sino que también garantiza que todos los desarrolladores puedan entender rápidamente la estructura del código sin necesidad de explicaciones adicionales.
En cuanto a la seguridad, la organización OWASP (Open Worldwide Application Security Project) ofrece directrices clave que todos los desarrolladores deben tener en cuenta al crear aplicaciones web, como APIs REST. La primera de estas directrices es la autenticación y autorización débiles. Aunque una aplicación pueda tener algún sistema de autenticación, siempre debe ser capaz de resistir ataques como los de fuerza bruta. Un método para mitigar este riesgo es el uso de Rate Limiting, que se abordará más a fondo en el capítulo correspondiente de este libro.
Además, las inyecciones (en especial las inyecciones SQL) siguen siendo uno de los vectores de ataque más comunes. Las aplicaciones que permiten la entrada de datos no validados son vulnerables a ataques de inyección, que permiten a un atacante manipular las consultas a la base de datos o ejecutar código malicioso en el servidor. Un ejemplo muy conocido es el Cross-Site Scripting (XSS), donde un atacante puede inyectar JavaScript malicioso que podría robar cookies de autenticación o realizar otras acciones perjudiciales.
La falta de un control adecuado de acceso es otra de las vulnerabilidades más frecuentes. No todas las personas en una organización deben tener acceso a la misma información o a las mismas funcionalidades. El uso adecuado de roles y permisos es esencial para evitar que usuarios no autorizados accedan a datos sensibles, como información bancaria de los clientes. La implementación de autenticación de usuario y gestión de permisos es otro tema que abordaremos en profundidad en el capítulo dedicado a ello.
Otro aspecto crítico es la insuficiencia en los registros y monitoreo de las actividades dentro de la aplicación. Los registros no solo sirven para depurar, sino que también son fundamentales para detectar ataques, como los intentos de fuerza bruta o aumentos anómalos en la actividad de la aplicación. Sin una estrategia adecuada de monitoreo, puede ser difícil detectar cuándo una aplicación está siendo atacada.
La integridad de los datos también juega un papel importante. Las aplicaciones que no validan correctamente la entrada o que permiten la serialización/deserialización de datos sin medidas de seguridad pueden ser vulnerables a ataques. Este tipo de problemas puede dar lugar a la ejecución de código malicioso en el servidor, lo que puede poner en riesgo la seguridad de toda la infraestructura. Aunque este libro se centrará más en la validación de datos de entrada en las APIs, es un tema que debe ser considerado seriamente en todas las fases de desarrollo.
La falla en la criptografía es otro riesgo importante. Aunque en este libro nos centraremos en el uso de HTTPS para la comunicación entre el cliente y el servidor, es importante comprender que no solo se trata de usar conexiones seguras, sino también de garantizar que los datos sensibles estén adecuadamente encriptados dentro de la aplicación, si es necesario.
El diseño débil de la aplicación también puede ser una puerta abierta a vulnerabilidades. Un caso común es el abuso de promociones o beneficios dentro de las aplicaciones. Por ejemplo, un código de promoción que debería ser de uso único, pero que por un error en el diseño se puede utilizar varias veces. Aunque no profundizaremos en este tema en este libro, es importante recordar que un diseño seguro comienza desde el principio y no solo desde la perspectiva del código.
Un punto igualmente crítico es la configuración de seguridad débil. Las aplicaciones deben tener configuraciones estrictas en cuanto a contraseñas, autenticación y cuentas inactivas. Dejar cuentas sin actualizar o contraseñas que nunca expiran puede abrir la puerta a accesos no autorizados. Asegurarse de que todas las cuentas de usuario estén bien gestionadas y que las contraseñas se cambien regularmente es fundamental para mantener la seguridad.
Otro ataque relevante es el Server-Side Request Forgery (SSRF). Este tipo de ataque ocurre cuando una aplicación permite que un usuario envíe una solicitud hacia un recurso remoto sin validar adecuadamente la URL proporcionada. Aunque este problema es más común en aplicaciones web tradicionales, es algo a tener en cuenta si tu API interactúa con otros servicios.
Finalmente, el uso de componentes obsoletos en las aplicaciones también puede representar un riesgo significativo. Las bibliotecas y frameworks desactualizados son vulnerables a ataques conocidos, ya que los desarrolladores de estos componentes publican parches para corregir fallos de seguridad. Mantener tu aplicación y sus dependencias actualizadas es una de las mejores prácticas que puedes seguir para protegerla.
Una herramienta útil para proteger las aplicaciones es el proyecto OWASP Secure Headers Project (OSHP), que proporciona una serie de encabezados HTTP que pueden ser añadidos a tu aplicación para hacerla más segura. Existe una implementación específica para ASP.NET Core que puedes integrar fácilmente en tu proyecto.
Además de todo lo mencionado, es crucial recordar que la seguridad en una aplicación nunca debe tomarse a la ligera. Cada decisión de diseño y desarrollo debe ser tomada con la seguridad en mente. No basta con añadir medidas de protección al final del proceso; la seguridad debe ser un componente integral en cada fase del desarrollo.
¿Cómo gestionar versiones de una API en ASP.NET Core utilizando grupos de rutas y Swagger?
En el desarrollo de APIs, la gestión de versiones es una parte crucial para garantizar que los cambios en la estructura de la API no afecten a los clientes existentes. ASP.NET Core proporciona una forma eficiente de manejar versiones de endpoints mediante el uso de grupos de rutas y configuraciones específicas en el pipeline de la aplicación.
En el ejemplo presentado, se ha utilizado el método MapGet para crear dos versiones de un endpoint de consulta simple: /version. Este endpoint está disponible en dos grupos de rutas, cada uno asociado a una versión diferente. El primer grupo, GroupVersion1, se asocia con la versión 1 de la API y responde con un mensaje que dice “Hello version 1”. El segundo grupo, GroupVersion2, contiene dos rutas: la misma /version para la versión 2, y además, un endpoint exclusivo /version2only, que no está disponible en la versión 1, mostrando así una clara diferenciación entre ambas versiones.
El código que registra estos grupos en el pipeline de ASP.NET Core es bastante sencillo y puede ser implementado como sigue:
Esta configuración permite que, cuando un cliente acceda a /v1/version, recibirá la respuesta correspondiente a la versión 1, mientras que al acceder a /v2/version, recibirá la respuesta para la versión 2. Además, si un cliente intenta acceder a un endpoint que no está definido para una versión específica, como en el caso de /v1/version2only, obtendrá un error 404 Not Found, lo cual resulta más claro y comprensible que un error 400 Bad Request, que podría ocurrir si se estuviera utilizando otro tipo de versionado, como el basado en encabezados.
Este enfoque también es completamente compatible con Swagger, lo que permite una documentación automática y precisa de los endpoints disponibles para cada versión. Al utilizar Swagger, podemos visualizar las diferentes rutas que corresponden a cada grupo de versión, proporcionando una excelente herramienta de desarrollo y documentación para el equipo de trabajo y los consumidores de la API.
Para implementar la documentación Swagger, primero debemos instalar algunos paquetes NuGet necesarios, como Asp.Versioning.Http, Asp.Versioning.Mvc.ApiExplorer, Microsoft.AspNetCore.OpenApi, y Swashbuckle.AspNetCore. Estos paquetes permiten integrar el versionado de la API con la funcionalidad de Swagger, lo que resulta en una interfaz intuitiva para explorar las versiones de la API y sus respectivos endpoints.
A continuación, se presenta un ejemplo de cómo configurar Swagger para manejar versiones de API en una aplicación de ASP.NET Core. Se debe crear una clase de configuración para Swagger, que se encargará de registrar las versiones de la API en el pipeline de ASP.NET Core y generar la documentación adecuada:
La configuración de Swagger también debe incluir las versiones de la API en el archivo Program.cs, lo que permite que Swagger pueda mostrar las rutas correspondientes a cada versión definida. Además, se puede utilizar HeaderApiVersionReader para gestionar el versionado basado en encabezados, lo que resulta útil para aquellos casos en los que no se quiere especificar la versión directamente en la URL.
Es importante mencionar que este enfoque de versionado es beneficioso porque permite un control más claro y específico de las versiones sin afectar a los clientes actuales. El uso de diferentes grupos de rutas para cada versión asegura que cada versión de la API se mantenga de manera independiente, facilitando la evolución de la API sin romper la compatibilidad con las aplicaciones existentes.
Finalmente, es esencial que el equipo de desarrollo se asegure de documentar cada versión de la API correctamente, ya que una API bien documentada es fundamental para que los consumidores puedan integrarse de manera eficiente con ella. Swagger proporciona una excelente herramienta para automatizar este proceso y mantener actualizada la documentación conforme se desarrollan nuevas versiones o se modifican los endpoints.
¿Cómo acceder a los datos de manera eficiente utilizando Entity Framework Core?
En el desarrollo de software moderno, la eficiencia y seguridad en el acceso a los datos son fundamentales. Utilizando un enfoque basado en capas, es esencial que cada capa esté correctamente configurada y tenga acceso adecuado a los servicios y repositorios necesarios. En este contexto, la inyección de dependencias juega un papel crucial para garantizar la correcta integración y funcionamiento de los distintos componentes. Esta estructura modular permite que la API acceda tanto a las abstracciones como a las implementaciones necesarias para registrar servicios y repositorios en el sistema de inyección de dependencias.
Para ilustrar cómo trabajar con datos en una aplicación utilizando Entity Framework Core (EF Core), tomaremos como ejemplo un caso simple de acceso a datos mediante un framework ORM (Object-Relational Mapping). EF Core, desarrollado por Microsoft, facilita la conexión entre las entidades en el código y las tablas en la base de datos, mapeando las entidades C# a tablas SQL. En este caso, utilizaremos una tabla SQL denominada "Countries" (países), mostrando cómo mapearla a una clase en C# y cómo optimizar este proceso.
Es importante destacar que no se abordará en profundidad el funcionamiento de EF Core, ya que es un tema extenso que podría llenar un libro completo. No obstante, se ofrecerá una introducción a su uso práctico con ejemplos de cómo configurar las entidades y el contexto de base de datos, permitiendo comprender cómo interactúan estos componentes para lograr un acceso eficiente a los datos.
Para comenzar, se debe crear una capa denominada Infrastructure.SQL, que se encargará de gestionar la interacción con la base de datos. En esta capa, se utilizará el paquete NuGet "Microsoft.EntityFrameworkCore.SqlServer", que permite conectar con bases de datos SQL Server. La estructura del proyecto debe reflejar esta división de responsabilidades.
Creación de la clase CountryEntity
El primer paso es la creación de la clase CountryEntity. A diferencia de una clase CountryDto, que representa un objeto de dominio sin vínculo con la base de datos, la clase CountryEntity está diseñada para mapear una tabla SQL en el sistema. A continuación se muestra el código de la clase CountryEntity, que contiene las propiedades básicas como Id, Name, Description y FlagUri (la URI de la bandera):
Creación del contexto de EF Core (DemoContext)
El siguiente paso es crear el contexto de EF Core, que es una clase encargada de gestionar la conexión y el estado de las entidades. Esta clase se hereda de DbContext y contiene un conjunto de datos (DbSet) que representa las entidades asociadas con las tablas de la base de datos. En este caso, se configura un DbSet para la entidad CountryEntity.
Este contexto permite interactuar con la base de datos de manera sencilla, facilitando las operaciones CRUD (crear, leer, actualizar, eliminar) a través de LINQ y otros métodos de EF Core.
Configuración de la entidad CountryEntity
Una vez que tenemos la clase CountryEntity y el contexto DemoContext, el siguiente paso es configurar adecuadamente la entidad para que se mapee correctamente a la base de datos. En EF Core, es necesario definir configuraciones específicas como la clave primaria, los índices y las restricciones de los campos. Esto se puede hacer en el método OnModelCreating dentro de la clase DbContext.
En este ejemplo, se configurarán varias propiedades, como la clave primaria (Id), el índice único para el nombre del país, y las restricciones de longitud para la descripción y la obligatoriedad de los campos.
Esta configuración asegura que el Id sea una clave primaria autoincremental, que el nombre del país sea único, que la descripción tenga un máximo de 200 caracteres y que todos los campos sean obligatorios.
Generación del modelo de base de datos
Una vez configurada la entidad y el contexto, el siguiente paso es generar el modelo de la base de datos. Esto se realiza a través de migraciones, que permiten crear o actualizar la base de datos a partir del código. Para conectar la base de datos, es necesario definir una cadena de conexión en el archivo appsettings.json. A continuación se muestra un ejemplo de cadena de conexión a una base de datos SQL Server local:
Con esta configuración, ASP.NET Core puede generar la base de datos y crear las tablas correspondientes a las entidades definidas en el modelo. Esto se hace ejecutando las migraciones correspondientes en el proyecto.
Optimización y buenas prácticas
Es importante entender que, además de la configuración básica de las entidades y el contexto, existen diversas optimizaciones que pueden mejorar el rendimiento y la seguridad de las consultas. Por ejemplo, al definir índices en los campos más consultados, como el nombre de un país, se puede acelerar significativamente la búsqueda de datos en la base de datos. Asimismo, al establecer restricciones de unicidad y no permitir valores nulos, se garantiza la integridad de los datos, evitando la inserción de registros erróneos o duplicados.
También se debe tener en cuenta que, aunque EF Core es un excelente framework para la gestión de datos, existen casos en los que se puede optar por estrategias más avanzadas, como la implementación de patrones de repositorio o el uso de consultas SQL directas cuando sea necesario optimizar ciertas operaciones.
¿Cómo realizar pruebas unitarias en servicios de APIs?
En la programación de APIs, la calidad y fiabilidad del código son fundamentales. Las pruebas unitarias juegan un papel crucial en asegurar que cada componente de la aplicación funcione correctamente en aislamiento. En este contexto, se debe entender cómo estructurar estas pruebas para que el código sea robusto y fácil de mantener. Aquí te explico cómo puedes estructurar una prueba unitaria de una API utilizando herramientas como NSubstitute, AutoFixture y ExpectedObjects.
Al comenzar, es necesario comprender que las pruebas unitarias se centran en probar funciones individuales de tu código sin depender de servicios externos como bases de datos o APIs. Para ello, se utilizan "mocks" o instancias falsas de interfaces y servicios. En el ejemplo mostrado, se utiliza NSubstitute, una biblioteca para crear estos mocks. Por ejemplo, para probar un servicio que depende de interfaces como ICountryMapper y ICountryService, es necesario crear versiones falsas de estas interfaces para definir su comportamiento y observar cómo el sistema bajo prueba (SUT, por sus siglas en inglés) interactúa con ellas.
Un paso clave es crear una instancia de estas interfaces en el constructor utilizando NSubstitute. De esta forma, se evita que las pruebas unitarias dependan de implementaciones reales, que podrían ser costosas o complicadas de manejar en un entorno de prueba. Usar estas instancias simuladas permite definir el comportamiento esperado de los métodos que serán invocados durante la prueba. Esto es crucial, ya que el objetivo no es probar los métodos reales de los servicios, sino verificar que el código interactúe correctamente con ellos.
Adicionalmente, para simplificar la creación de objetos de prueba, se puede utilizar la clase Fixture de la biblioteca AutoFixture. Esta herramienta permite generar automáticamente objetos completos, lo cual ahorra tiempo y mejora la legibilidad de las pruebas. Esto es particularmente útil cuando los objetos que se manejan son complejos o tienen muchas propiedades que configurar manualmente.
Una vez que se han instanciado las dependencias falsas, se deben definir los parámetros que utilizarán las pruebas. En el caso de la API, se establecen valores como pageIndex y pageSize. Estos parámetros pueden probarse en varios escenarios, como cuando son nulos o cuando tienen valores específicos, para asegurarse de que el sistema responde correctamente a todas las situaciones posibles.
La siguiente parte del proceso es la sección "Act", donde se ejecuta el código del sistema bajo prueba (SUT). En esta etapa, se pasan los parámetros previamente definidos y se invoca el servicio utilizando las instancias falsas de los servicios. El objetivo aquí es observar cómo se comporta el SUT cuando se enfrenta a estos valores y servicios simulados.
La fase final es la de "Assert", donde se validan los resultados de la prueba. Se realiza una comparación entre el resultado obtenido por el SUT y lo que se esperaba que devolviera el servicio. Es importante recordar que en las pruebas unitarias se comparan los valores de los objetos y no sus referencias, por lo que se debe hacer una comparación profunda. Una de las herramientas útiles para esto es ExpectedObjects, que permite comparar los objetos por valor. En este caso, la comparación se realiza entre el resultado devuelto por el SUT y lo que se había configurado como valor esperado.
Por último, se verifica que los métodos de los servicios falsos se hayan invocado correctamente. Esto se hace utilizando el método Received de NSubstitute, que asegura que los métodos esperados se hayan llamado exactamente el número de veces que se especificó, y que hayan recibido los parámetros correctos. Esto es clave para garantizar que el SUT interactúa correctamente con sus dependencias.
Es importante comprender que, más allá de los detalles técnicos y las herramientas utilizadas, lo que realmente se está probando es la lógica de interacción entre el SUT y sus dependencias, no el funcionamiento interno de los servicios simulados. Este enfoque de pruebas unitarias garantiza que el código se pueda testear de forma aislada, sin depender de servicios externos, lo que aumenta la velocidad y confiabilidad de las pruebas.
Al realizar pruebas unitarias de esta manera, se asegura que el código esté bien estructurado y sea más fácil de mantener, ya que cada componente se prueba de forma aislada. Este tipo de pruebas también ayuda a detectar errores en las primeras etapas del desarrollo, antes de que el código se despliegue en un entorno de producción, lo que reduce el riesgo de errores en la API una vez que esté en uso.
¿Es suficiente seguir las recomendaciones médicas tradicionales para prevenir las enfermedades cardíacas?
¿Qué diferencia a los veneradores de Trump en cuanto a sus valores y preocupaciones de seguridad?
¿Cómo la práctica dirigida y la retroalimentación mejoran el aprendizaje?
¿Cómo crear una aplicación de escaneo de documentos con NAPS2 SDK en Linux usando .NET y VSCode?

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский