Entiende el Proceso de Ejecución: Funcionamiento de las Aplicaciones .NET


En este artículo, mi objetivo es que entendamos ciertos aspectos relacionados con la ejecución de una aplicación .NET. ¿Ejecutar una aplicación .NET? Sí, has leído correctamente; eso es lo que busco lograr. Aunque pueda sorprenderte, hay algunos desarrolladores que no comprenden algunos aspectos que suceden durante este proceso.

Esto genera algunas dificultades se hacen evidentes, por ejemplo, al intentar ejecutar una aplicación .NET con un servidor proxy inverso (como NGINX ) y no comprender por qué la aplicación falla o no funciona como se espera.

Explorando la estructura de un proyecto .NET

Primero quiero que analicemos que archivos se generan cuando creamos un proyecto .NET. En este caso usare NET 7 y haré todo por consola usando los comandos de dotnet.

dotnet new webapi -n myapp -o myapp -minimal --no-openapi

Al ejecutar el comando anterior estoy generando un proyecto del tipo WebApi. Los parámetros adicionales se utilizan para definir el nombre del directorio de salida, el nombre del proyecto, excluir la configuración Swagger y optar por el esquema de Minimal API. Estos parámetros los incluyo con el propósito de minimizar la cantidad de código generado. Mi intención es que comprendas con claridad qué estamos utilizando y por qué.

Estructura del Proyecto
Estructura del Proyecto

No se creó una carpeta «Controllers» debido al uso de Minimal API.

Ahora, para mantener la simplicidad del proyecto, mi primer ajuste consistirá en reemplazar el contenido del archivo Program.cs. Este cambio busca eliminar el código innecesario y agregar un endpoint que responda con una cadena de texto indicando «home» cuando se accede a la raíz del sitio.

Program.cs
Program.cs

En realidad, lo que estoy haciendo es eliminar el código innecesario y añadir un endpoint que responderá con una cadena de texto que dice «home» cuando se accede a la raíz del sitio. Este paso tiene como objetivo mantener la claridad y concisión en el proyecto, eliminando cualquier elemento que no sea esencial para nuestro propósito.

Ahora, procederemos a ejecutar la aplicación utilizando el comando de dotnet run

dotnet run
dotnet run

Efectivamente, al dirigirnos al navegador y acceder a la URL que se indica, podremos visualizar el resultado de la ejecución.

http://localhost:5247
http://localhost:5247

Analizando lo ocurrido hasta ahora

En este punto, nos sumergimos en una serie de preguntas clave que nos ayudarán a desentrañar el funcionamiento de este proceso.

¿Por qué se utiliza ese puerto?

Cuando creamos una aplicación que escucha peticiones HTTP, se configura un puerto aleatorio para recibir estas solicitudes. Es crucial recordar que dos aplicaciones no pueden escuchar en el mismo puerto simultáneamente.

¿Cómo la herramienta determina qué puerto utilizar?

La información sobre qué puerto utilizar se encuentra en el archivo properties/launchSettings.json. Aquí, se definen los perfiles que podemos emplear para ejecutar nuestra aplicación. En nuestro ejemplo, esto es lo que tenemos.

launchSettings.json
launchSettings.json

Este archivo contiene tres perfiles (http, https e IIS Express), cada uno con configuraciones específicas. Nos centraremos en la propiedad applicationUrl, donde se indica al servidor Kestrel (para http y https) qué puerto y URL utilizar para recibir peticiones. Al ejecutar dotnet run, por defecto, se toma el primer perfil de la lista, configurando así http://localhost:5247.


Si utilizamos Visual Studio, tenemos la capacidad de elegir el perfil que deseamos emplear. En este entorno, observaremos los mismos perfiles que se muestran en el archivo properties/launchSettings.json.

Visual Studio
Visual Studio

En el entorno de Visual Studio, contamos con la capacidad de agregar o eliminar perfiles según nuestras preferencias. Asimismo, tenemos la libertad de modificar perfiles existentes; por ejemplo, podemos asignar un puerto diferente, siempre asegurándonos de no seleccionar uno que ya esté en uso por otra aplicación en nuestro sistema operativo. Además de estas personalizaciones, también podemos configurar variables de entorno según nuestras necesidades.

Es crucial destacar un punto clave antes de proseguir: este archivo, properties/launchSettings.json, se utiliza exclusivamente por la herramienta dotnet y los editores de código como Visual Studio. Es importante tener en cuenta que los servidores IIS ignoran estas configuraciones. Aunque existen métodos para configurar otros servidores HTTP, no se considera la opción ideal.


¿Es imprescindible el archivo launchSettings.json en las aplicaciones .NET?

No, no lo es. Tenemos la libertad de eliminarlo si así lo deseamos, y nuestra aplicación continuará ejecutándose.

Entonces, ¿qué configuraciones se utilizan?

En el caso de no emplear un perfil de configuración específico con dotnet y no especificar parámetros adicionales durante la ejecución, se recurrirá a las configuraciones predeterminadas. Ahora que hemos eliminado el archivo, procedamos a ejecutar nuevamente el comando dotnet run.

dotnet run
dotnet run

Ahora observamos que la aplicación está escuchando peticiones en http://localhost en el puerto 5000, que son los valores predeterminados para este tipo de aplicación .NET. Además, notamos que el entorno ahora está configurado como «Production».

¿Qué sucede si no estamos utilizando el archivo launchSettings.json y deseamos emplear un puerto diferente?

Al revisar la documentación de dotnet run, encontraremos varios parámetros adicionales que podemos utilizar. En este caso, quiero destacar el uso de --urls.

dotnet run --urls "http://localhost:5247"

Con este comando, indicamos la URL y el puerto que deben ser utilizados (también podemos especificar varias opciones separadas por comas). Esto resultará en un comportamiento similar al que teníamos anteriormente con el archivo properties/launchSettings.json.

¿Es obligatorio el archivo appsettings.json?

No, no lo es. A diferencia del archivo anterior, es probable que este te resulte más familiar, ya que permite incluir configuraciones de manera sencilla. Por defecto, se crea un archivo con configuraciones para definir el nivel del registro (logs de la aplicación) que deseamos tener. Asimismo, si el archivo no existe, .NET proporciona valores predeterminados para estas configuraciones.


¿Es obligatorio el archivo .sln?

No, no lo es. El archivo sln es utilizado para agrupar varios proyectos dentro de una misma solución. Cuando creamos nuestro primer proyecto, se genera automáticamente un archivo sln que incluye la referencia al proyecto recién creado. Sin embargo, un proyecto (csproj) puede existir de manera independiente, sin depender de un archivo sln. En nuestro caso, dado que solo tenemos un proyecto, también podríamos eliminar el archivo sln.

Veamos cómo queda la nueva estructura del proyecto después de este ajuste.

Mi Proyecto Simplificado
Mi Proyecto Simplificado

Ahora contamos únicamente con dos archivos, los cuales son los verdaderamente necesarios. Todo lo demás puede ser configurado de otras maneras en tiempo de ejecución.

¿De qué sirve haber reducido la aplicación?

Ahora te preguntarás, ¿de qué sirve haber reducido la aplicación? En realidad, mi objetivo es comprendamos un poco más acerca de lo que ocurre en la ejecución de una aplicación .NET (sin adentrarnos más de lo necesario).

En muchas ocasiones, la falta de comprensión de este proceso aparentemente simple es la causa de horas de frustración para muchos desarrolladores al intentar desplegar sus aplicaciones. Un escenario de ejemplo común que observo es cuando se desea implementar una aplicación con Docker, se agrega un NGINX al frente de la aplicación y, de repente, la aplicación no responde en un puerto o una URL específica. En ocasiones, el administrador del proxy solicita cambios en la configuración del servidor HTTP para que responda y acepte solicitudes de ciertos orígenes, entre otros. Esto son cosas que no nos preocuparía tanto si por ejemplo se despegara en un App Service en Azure, ya que ellos nos abstrae de esta responsabilidad.

Si comprendemos claramente todo lo que hemos discutido hoy, será mucho más sencillo resolver este tipo de problemas.

Conclusión

Si bien los editores y herramientas nos proporcionan numerosas configuraciones que aumentan nuestra productividad, es fundamental tener un entendimiento básico de lo que sucede en el fondo. Comprender este proceso no solo facilitará nuestras vidas como desarrolladores de código, sino que también será invaluable en los procesos de entrega y soporte de aplicaciones. La combinación de conocimientos profundos y el uso eficiente de herramientas nos posiciona para abordar desafíos de implementación y mantenimiento con mayor eficacia.

Te invito a ver mis otros artículos.