Crear y publicar un servicio REST en Azure con .Net 5 y EF Core

CI/CD con AzureDevOps

Este artículo forma parte del Calendario de Adviento de C# 2020 en cual voluntarios durante el mes de diciembre comparten un artículo sobre C#.

Crearé un servicio con .Net 5 con Code First en el cual creas primero los modelos (clases) y luego creas el script para actualizar tu base de datos.

En Visual Studio crea un nuevo proyecto de API de .Net Core.

Se crea un proyecto de ejemplo con:

Instala el paquete nuget Microsoft.EntityFrameworkCore.SqlServer para conectarte a SQL Server

Install-Package Microsoft.EntityFrameworkCore.SqlServer

Crea una nueva carpeta Models para agregar los modelos (tablas) de tu base de datos.

Agrega una clase Cliente. Cada propiedad es un campo de tu base de datos y puedes agregar atributos para indicar si el campo es obligatorio (Required)

public class Cliente
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClienteId { get; set; }
[Required]
[Range(1, 999)]
public int Clave { get; set; }
[Required]
[StringLength(80)]
public string Nombre { get; set; }
[Required]
public bool Activo { get; set; }
[Required]
public bool CreditoActivo { get; set; }
}

Agrega otra clase Venta y agrega los siguientes campos

public class Venta
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int VentaId { get; set; }
[Required]
public int ClienteId { get; set; }
[Required]
public DateTime Fecha { get; set; }
[Required]
public Decimal SubTotal { get; set; }
[Required]
public Decimal Impuestos { get; set; }
[Required]
public Decimal Total { get; set; }
[Required]
public bool Activa { get; set; }
[Required]
public bool Pagada { get; set; }
[Required]
public int TipoVenta { get; set; }
[Required]
public Decimal TotalPagado { get; set; }
[Required]
public int DiasAtraso { get; set; }
}

Agrega una nueva clase que herede de la clase DBContext, esta clase contiene:

  • Las tablas de tu base de datos
  • Indices, llaves foráneas
  • Registros por ejemplo usuarios y roles
using System;
using Microsoft.EntityFrameworkCore;
namespace Ventas.Model
{
public class VentasContext : DbContext
{
public VentasContext(DbContextOptions options) : base(options)
{
try
{
Database.EnsureCreated();
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
}
}
/// <summary>
/// Agrega una tabla con el nombre cliente
/// </summary>
public virtual DbSet<Cliente> Cliente { get; set; }
/// <summary>
/// Agrega una tabla con el nombre venta
/// </summary>
public virtual DbSet<Venta> Venta { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Cliente>(entity =>
{
//Agrega un índice único
entity.HasIndex(c => c.Clave)
.IsUnique()
.HasDatabaseName("UX_Cliente_Clave");
entity.HasIndex(c => c.Nombre)
.IsUnique()
.HasDatabaseName("UX_Cliente_Nombre");
});
modelBuilder.Entity<Venta>(entity =>
{
entity.HasIndex(v => v.ClienteId)
.HasDatabaseName("IX_Venta_Cliente");
entity.HasIndex(e => e.Fecha)
.HasDatabaseName("IX_Venta_Fecha");
//Agrega una llave foránea
entity.HasOne(typeof(Cliente))
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
});
//Agregar datos
modelBuilder.Entity<Cliente>().HasData(
new Cliente { ClienteId = 1, Nombre="Juan Peréz González", Activo=true, Clave=1, CreditoActivo=true },
new Cliente { ClienteId = 2, Nombre = "Jose Hernández Martínez", Activo = true, Clave = 2, CreditoActivo = true });
//Agregar datos
modelBuilder.Entity<Venta>().HasData(
new Venta { VentaId = 1, ClienteId = 1, Activa = true, DiasAtraso=0, Fecha=DateTime.Now, Impuestos=16, Pagada=true, SubTotal=100, TipoVenta=1, Total=116, TotalPagado=116 },
new Venta { VentaId = 2, ClienteId = 2, Activa = true, DiasAtraso = 10, Fecha = DateTime.Now.AddDays(-10), Vencimiento=DateTime.Today, Impuestos = 32, Pagada = true, SubTotal = 200, TipoVenta = 1, Total = 232, TotalPagado = 50 });
}
}
}

En el archivo appsettings agrega tu cadena de conexión

{
"ConnectionStrings": {
"SQLServerConnection": "Server=localhost;Database=ventas;User Id=UserNormal;Password=tupassword;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

En el archivo startup vamos a configurar la base de datos en el método ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
//Código generado
services.AddDbContext<VentasContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SQLServerConnection")));
}

Crea tu primer migración con el comando

dotnet ef migrations add CreacionInicial

Para agregar los cambios a la base de datos agrega el siguiente comando:

dotnet ef database update

Agrega tu servicio para clientes, con la opción Scaffolding, seleccionas tu modelo, tu contexto y un nombre para el controller y te crea los servicios básicos. En este caso en Model selecciona tu clase Cliente, en Context tu clase VentasContext y como Controller ClientesController

Puedes probar tu servicio de clientes con swagger o Postman.

Para la integración continua crea tu cuenta en Azure DevOps incluye opciones de control de tareas, wiki, entre otros.

Da clic en New project

Ingresa los datos de tu proyecto

  • Project name: Nombre del proyecto
  • Description: Agrega una descripción
  • Visibility: Indica si deseas tu proyecto público o privado

Da clic en Advanced si deseas especificar

  • Control de código fuente: Git o Team Foundation Version Control
  • Metodología: Puede ser Agile, Basic, Scrum, CMMI. Para obtener mas detalles y las diferencias puedes consultar la documentación oficial

https://docs.microsoft.com/en-us/azure/devops/boards/work-items/guidance/choose-process?view=azure-devops&tabs=basic-process

Da clic en repos y copia la url de tu repositorio Git debe ser similar a este

Da clic en Agregar con lo siguiente

En Visual Studio en el menu Git -> Administrar Remotos

Da clic en Agregar

Abre el Panel de Cambios de Git (Ver -> Cambios de Git)

En este panel se muestran lo siguiente:

  • La rama actual: master. Esta en la esquina inferior derecha del Visual Studio.
  • Cambios: Muestra todos los archivos que se han cambiado, en esta caso son todos

Agrega un mensaje para el commit. Ejemplo: Carga inicial. Si todo es correcto se muestra lo siguiente:

En entrantes: 1 /Salientes: 0 te muestra los cambios pendientes de subir a tu repositorio remoto, y salientes los cambios de otros programadores, como es la carga inicial en Salientes no hay nada.

Da clic en la fecha hacia arriba para publicar tus cambios

Selecciona tu cuenta de Azure DevOps

Puedes entrar a tu repositorio verificar que el código se ha agregado correctamente

Registra y crea tu cuenta en Azure, por lo general te dan un año gratis con algunos servicios básicos para conocer el servicio.

Da clic en Agregar App Service

Puedes seleccionar un plan gratis.

Da clic en Revisar y Crear

Revisa que todo sea correcto y da clic en Crear

Si todo es correcto verás lo siguiente y da clic en Ir al recurso

Te muestra el resumen de tu servicio

Comprueba que la url sea correcta

Ahora en Azure DevOps da clic en Pipelines para configurar que cuando le des commit a tu código se compile

Selecciona donde se encuentra tu código (Github, Bitbucket, etc) selecciona Azure Repos Git

Selecciona el nombre de tu proyecto: DevOps

A continuación te pregunta que lenguaje de programación estas utilizando para precargar los pasos básicos. Selecciona ASP Net Core (.Net Framework)

Te agrega los pasos básicos para compilar tu código y correr las pruebas unitarias. El crear las pruebas unitarias esta fuera del alcance, si deseas verlo puedes consultarlo aquí: https://abi.gitbook.io/net-core/8.-pruebas-unitarias

En el script en triggers en lugar de main, agrega el nombre de tu branch master para que se ejecute el pipeline cada vez que se le da commit a la rama master.

Los primeros pasos indican que debe instalar los paquetes Nuget y restaurar tu solución

- task: NuGetToolInstaller@1- task: NuGetCommand@2inputs:restoreSolution: '$(solution)'

Los siguientes pasos compilan tu solución y generan un Zip

- task: VSBuild@1inputs:solution: '$(solution)'msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'platform: '$(buildPlatform)'configuration: '$(buildConfiguration)'

El último paso corre las pruebas unitarias

Agrega en una variable la cadena de conexión, y los datos del issuer, y secret para configurar los valores del servidor. Da clic en Variables

Da clic en New Variable

Teclea:

  • Name: Nombre de tu variable. En este ejemplo: Conexión
  • Value: Teclea el valor de tu cadena de conexión.
  • Keep this value secret: El valor se mostrará oculto. Se recomienda ocultar los valores como el secret y tu cadena de conexión
  • Let users override this value when running this pipeline: Selecciona si deseas que los demás miembros del equipo puedan modificar los valores de las variables cuando ejecuten el pipeline manualmente.

Da clic en OK para guardar el valor

Da clic en Save

En el pipeline agrega el valor en la sección de variables. De acuerdo al valor de tu appsettings. Cada nivel del Json es reemplazado por un . el cual sería ConnectionStrings.SQLServerConnection

{
"ConnectionStrings": {
"SQLServerConnection":

Y para utilizar las variables es $(nombre_variable)

ConnectionStrings.SQLServerConnection: $(Conexion)

Después da clic debajo de la sección Steps para agregar un task que transformará el appsettings.

En la sección de Task busca Transform y selecciona File transform

Selecciona lo siguiente:

  • Package or folder: Selecciona la ruta donde se encuentra el archivo. $(build.SourcesDirectory)/DevOps/
  • File format: En este caso es Json
  • Target Files: Teclea **/appsettings.json

Agrega una tarea de tipo Command Line con lo siguiente:

Instala el paquete de Entity Framework para generar la migración.

dotnet tool install --global dotnet-ef

Después genera el build del proyecto

dotnet build --configuration $(buildConfiguration)

Genera la migración con la opción -i te permite generar un script que puede ser ejecutado siempre y la opción -o te permite especificar la ruta donde deseas que se genere el archivo.

dotnet ef migrations script -p $(build.SourcesDirectory)\Ventas\Ventas.csproj -i -o $(Build.ArtifactStagingDirectory)\Scripts\update_to_latest.sql

Como ya generé el build para generar la migración puedes borrar el task de build y sustituirlo por la tarea de .Net core para publicar el proyecto con los siguientes valores:

  • Command: Permite ejecutar el comando de .net core, en este caso es publish.
  • Publish web projects: Buscará los proyectos web para publicarlo
  • Arguments: Indica los siguientes argumentos para preparar el publish para linux. Lo agregué en una carpeta linux por si se desea publicar el mismo código en servidores linux o windows, puedes agregar otro comando publish para publicar en windows

Para linux

-r linux-x64 --configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)/linux

Para windows

--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)/win

Selecciona zip para que publique todo como un archivo .zip

Agrega una tarea Publish build artifacts con lo siguiente:

  • Path to publish: El path donde se encuentra el archivo que deseas publicar. $(Build.ArtifactStagingDirectory)/linux
  • Artifact name: Un nombre para el archivo. RestLinux
  • Artifact publish location:Indica donde se va a guardar el archivo, en mi caso es Azure Pipelines para luego crear el release

Agrega otra task igual para publicar el script

- task: PublishBuildArtifacts@1displayName: "Upload Script"inputs:pathtoPublish: '$(Build.ArtifactStagingDirectory)/Scripts'artifactName: 'Scripts'

Al final el pipeline queda:

# ASP.NET Core (.NET Framework)# Build and test ASP.NET Core projects targeting the full .NET Framework.# Add steps that publish symbols, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
trigger:- masterpool:vmImage: 'windows-latest'variables:solution: '**/*.sln'buildPlatform: 'Any CPU'buildConfiguration: 'Release'ConnectionStrings.SQLServerConnection: $(Conexion)steps:- task: FileTransform@1inputs:folderPath: '$(build.SourcesDirectory)/Ventas'fileType: 'json'targetFiles: '**/appsettings.json'
- task: NuGetToolInstaller@1- task: NuGetCommand@2inputs:restoreSolution: '$(solution)'- task: CmdLine@2inputs:script:dotnet tool install --global dotnet-efdotnet build --configuration $(buildConfiguration)dotnet ef migrations script -p $(build.SourcesDirectory)\Ventas\Ventas.csproj -i -o $(Build.ArtifactStagingDirectory)\Scripts\update_to_latest.sql- task: DotNetCoreCLI@2inputs:command: 'publish'publishWebProjects: truearguments: '-r linux-x64 --configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)/linux'- task: PublishBuildArtifacts@1inputs:PathtoPublish: '$(Build.ArtifactStagingDirectory)/linux'ArtifactName: 'RestLinux'
- task: PublishBuildArtifacts@1displayName: "Upload Artifacts SQL"inputs:pathtoPublish: '$(Build.ArtifactStagingDirectory)/Scripts'artifactName: 'Scripts'

Da clic en la sección Releases y luego en New pipeline

Da clic en Empty Job

Luego da un nombre a tu stage en mi caso es Deploy Azure Linux.

Da clic en Add an artifact

Selecciona Build y selecciona tu pipeline en mi caso es DevOps

Da clic en el link en el link 1 job, 0 task

Agrega variables de tipo Oculto para guardar los datos de tu cadena de conexión

Luego da clic en el botón + y busca una tarea SQL

Selecciona SQL Query File y agrega los datos de tu cadena de conexión en mi caso tengo la bd en un hosting (www.hostbuddy.com) el cual te ofrece un server gratis por 60 días) y no en azure por el costo.

En SQL File selecciona: $(System.DefaultWorkingDirectory)\_DevOps\Scripts\update_to_latest.sql

Da clic en el botón + para agregar la tarea Azure Web App y da clic en el botón Add

Selecciona tu suscripción a Azure y da clic en Authorize, el cual te pedirá que inicies sesión en tu cuenta de azure

Selecciona Web App en Linux y selecciona el nombre de tu App. Para seleccionar el Package da clic en los …

Por último da clic en Save. Selecciona el path donde deseas guardar el archivo del release

Da clic en Create Release

Selecciona el Stage y da clic en el botón Create

Si todo es correcto se muestra lo siguiente

Al terminar tu release se muestra lo siguiente

Puedes consultar que la BD fue creada correctamente y también tu sitio web.

El código de ejemplo esta en https://wbi1521@dev.azure.com/wbi1521/DevOps/_git/DevOps

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store