ASP.NET Core es la nueva apuesta de Microsoft por una solución multiplataforma de desarrollo web. Soporta tanto Windows como Mac come Linux, y permite una transición de páginas web desarrolladas con ASP.NET MVC a su versión multiplataforma ASP.NET MVC Core. Esta nueva tecnología además, destaca por ser una de las soluciones con mejor rendimiento a la hora de desarrollar aplicaciones web.

Instalando el SDK

Para poder trabajar con ASP.NET Core, necesitamos descargarnos el runtime de .NET Core. Estos son los pasos a seguir para instalarlo sobre Ubuntu 16.04 LTS.

# Añadimos los repositorios a nuestro sistema
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update

# Instalamos .NET Core 1.1 y 1.0
sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 dotnet-dev-1.0.0-preview2-003121
  • Si queremos usar un IDE para desarrollar, podemos usar Visual Studio Code, el IDE multiplataforma de Microsoft.

Crear un proyecto web

Una vez instalado todo lo necesario, pasamos a crear nuestro proyecto web con las siguientes instrucciones.

mkdir DotNetCoreWeb && cd DotNetCoreWeb
dotnet new -t web
dotnet restore
# Para instalar las dependencias del lado del cliente
# Necesitamos tener Node.js instalado
npm -g install bower gulp
bower install
npm install

# Inicializamos la base de datos
dotnet ef database update

# Iniciamos el entorno para activar el modo desarrollo
export ASPNETCORE_ENVIRONMENT=development

# Ejecutamos el proyecto
dotnet run

Tras ejecutar estos comandos, podremos abrir un navegador a la URL http://localhost:5000 donde podremos ver la web plantilla creada por el comando dotnet new -t web. Este proyecto plantilla ya viene con ASP.NET Core MVC y Entity Framework Core. Es recomendable echar un ojo al código con el editor que más nos guste para ver ejemplos de controladores, vistas y modelos que nos puedan luego ayudar en como utilizar todas las herramientas ofrecidas por la plataforma ASP.NET Core MVC.

Un caso práctico, TodoList CRUD.

Vamos a empezar a añadir código propio a la aplicación plantilla. Crearemos un listado de todos y un formulario para añadirlos. Para ello vamos a poner el código necesario para hacer un ejemplo funcional básico. También puedes bajarte el proyecto completo desde este repositorio de Github.

// Data/ApplicationContext.cs

public class ApplicationDbContext
{
    // ...

    // Añadir esta línea
    public DbSet<Todo> Todos {get; set;}

    // ...
}
// Models/Todo.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebApplication.Models
{
    public class Todo
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int TodoId { get; set; }

        [Required]
        public string Title { get; set; }
        public string Comments { get; set; }

        public DateTime? DueDate { get; set; }
    }
}
// Controllers/TodoController.cs

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApplication.Data;
using WebApplication.Models;

namespace WebApplication.Controllers
{
    public class TodoController : Controller
    {
        protected readonly ApplicationDbContext _context;

        public TodoController(ApplicationDbContext context)
        {
            _context = context;
        }
        public async Task<IActionResult> Index()
        {
            return View(await _context.Todos.ToListAsync());
        }

        public ActionResult Add()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Add([Bind("Title, Comments, DueDate")] Todo todo)
        {
            if (ModelState.IsValid) {
                _context.Todos.Add(todo);
                await _context.SaveChangesAsync();
                return RedirectToAction("Index");
            }

            return View(todo);
        }
    }
}

También añadiremos las vistas que se cargaran desde el controlador TodoController.

<!-- Views/Todo/Index.cshtml -->

@model IEnumerable<WebApplication.Models.Todo>

@{
    string emptySymbol = "-";
}
<a asp-action="Add" >Add Todo</a>

<table class="table table-responsive">
    <thead>
        <tr>
            <td>Id</td>
            <td>Title</td>
            <td>Comments</td>
            <td>Due Date</td>
        </tr>
    </thead>
    <tbody>
        @foreach(var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.TodoId)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @if(!String.IsNullOrEmpty(item.Comments)) {
                    @Html.DisplayFor(modelItem => item.Comments)
                } else {
                    @emptySymbol
                }
            </td>
            <td>
                @if (item.DueDate.HasValue) {
                    @Html.DisplayFor(modelItem => item.DueDate)
                } else {
                    @emptySymbol
                }
            </td>
        </tr>
        }
    </tbody>
</table>
<!-- Views/Todo/Add.cshtml -->
@model WebApplication.Models.Todo

<form asp-action="Add">
    <div class="form-horizontal">
        <h4>Add Todo</h4>
        <div asp-validation-summary="ModelOnly" class="text-danger" ></div>
        <div class="form-group">
            <label asp-for="Title" class="col-md-2 control-label" ></label>
            <div class="col-md-10">
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Comments" class="col-md-2 control-label" ></label>
            <div class="col-md-10">
                <textarea asp-for="Comments" class="form-control" ></textarea>
                <span asp-validation-for="Comments" class="text-danger" />
            </div>
        </div>
       <div class="form-group">
            <label asp-for="DueDate" class="col-md-2 control-label" ></label>
            <div class="col-md-10">
                <input type="date" asp-for="DueDate" class="form-control" />
                <span asp-validation-for="DueDate" class="text-danger" />
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save Todo" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

Tras añadir los modelos, controladores y vistas necesarios para ejecutar nuestro ejemplo, vamos a tener que actualizar la base de datos de nuevo. Podemos hacerlo fácilmente con la herramienta de consola de Entity Framework Core.

dotnet ef migrations add Todo1
dotnet ef database update

Tras estos pasos podemos volver a ejecutar dotnet run y abrir un navegador a la URL http://localhost:5000/Todo.

ASP.NET Core application running on Linux

Ya tenemos nuestro listado de Todos y un formulario para poder añadir Todos nuevos.

Dándole más sabor a Linux a nuestra aplicación web

Ahora ya tenemos una aplicación web que corre sobre Linux y usa .NET Core. La plantilla que viene con ASP.NET Core está configurada para usar Sqlite, opción multiplataforma y muy útil para desarrollo, pero que pudiendo usar MySQL como motor de bases de datos queda un poco floja. Por ese motivo, vamos a modificar nuestra aplicación para usar MySQL como gestor de bases de datos.

Desgraciadamente, el proveedor de base de datos oficial para MySQL aún no es estable, pero podemos usar el proveedor proporcionado por Pomelo. Para hacer esto basta con actualizar el fichero project.json con una nueva dependencia y ejecutar dotnet restore.

// project.json
{
    "dependencies": {
       // ...
       "Pomelo.EntityFrameworkCore.MySQL": "1.0.1"
    }
    // ...
}

También tendremos que hacerle saber al servicio ApplicationDbContext que ahora debe usar MySQL como motor y actualizar la cadena de conexión en el fichero appsettings.json.

// Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
    // Other services configuration ...
}
// appsettings.json
{
    "ConnectionStrings": {
        "DefaultConnection": "server=localhost;database=dotnetcore;uid=root;pwd=password"
    }
    // ...
}

Tras esto va a ser necesario regenerar nuestra última migración y actualizar la base de datos desde el modelo de nuevo.

dotnet ef migrations remove
dotnet ef migrations add Todo1
dotnet ef database update

Ya tenemos nuestra aplicación usando MySQL como motor de base de datos.

Desplegar nuestra web ASP.NET Core en un servidor Linux

Para acabar de cerrar el círculo, necesitamos saber como desplegaremos esta aplicación web. No vamos a entrar en detalle en esta sección, pero sí vamos a explicar como ejecutar una instancia de nuestra aplicación preparada para producción detrás de un reverse proxy como Nginx, usando esta guía de Microsoft como referencia. Para instalar Nginx basta con ejecutar:

sudo apt-get update && sudo apt-get install nginx

Con nginx instalado, tendremos que publicar nuestro proyecto para que sea lo más eficiente posible en producción y copiar el resultado de nuestra compilación a un directorio utilizable por el usuario www-data, como haríamos con un proyecto LAMP.

# Compilamos en modo Release
dotnet publish -c Release
# Copiamos nuestra aplicación compilada al directorio raíz de la instancia en producción
cp -r bin/Release/netcoreapp1.1/publish/* /var/aspnetcore/DotNetCoreWeb

Finalmente, pasaremos a seguir los pasos descritos en la guía de Microsoft para crear un servicio manejado a través de systemd. Algo que esta guía no nos dice, es que en el descriptor del servicio, hace falta definir el directorio de trabajo que va a tener nuestro proceso. Por eso, añadimos el descriptor del servicio utilizado en el proyecto de ejemplo.

[Unit]
Description=.NET running on Ubuntu 16.04

[Service]
ExecStart=/usr/bin/dotnet /var/aspnetcore/DotNetCoreWeb/DotNetCoreWeb.dll
Restart=always
RestartSec=10
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
WorkingDirectory=/var/aspnetcore/DotNetCoreWeb

[Install]
WantedBy=multi-user.target

Referencias