WebApi con ASP .NET Core, Entity Framework Core, AutoMapper y publicación en IIS

¡Hola! Cuando desarrollamos una aplicación móvil hay ocasiones que también necesitamos crear el backend que acceda a la base de datos. En esta entrada aprenderás a crear un WebApi usando ASP .NET Core y Entity Framework Core como tecnología de acceso a los datos de nuestro almacenamiento. La base de datos está en SQL Server. Además, utilizaremos AutoMapper para construir DTOs a partir de los modelos generados por EF Core. Finalmente, publicaremos en IIS el Web Api construido con el fin de acceder a él posteriormente por una aplicación cliente (móvil, construida en Xamarin, pero eso será en otra publicación).

 

Nota #1: El código fuente de esta entrada está disponible en GitHub, solo haz clic aquí.

 

Nota #2: Esta publicación es la guía paso a paso de la sesión en vivo que tuvimos en mi canal de YouTube el sábado 6 de Octubre. La sesión fue grabada y puedes revisarla aquí.

 

¡Manos a la obra!

 

La base de datos con la que vamos a trabajar tiene la siguiente estructura.

 

Paso 1. Conéctate a SQL Server Management Studio y ejecuta el siguiente script para crear la base de datos.

 

create database StoreDB
go
use StoreDB
go
create table Employee(
ID int identity(1,1) primary key not null,
FirstName varchar(50) not null,
LastName varchar(50) not null,
UserName varchar(32) not null,
Password varchar(32) not null
)
create table Customer(
ID int identity(1,1) primary key not null,
FirstName varchar(50) not null,
LastName varchar(50) not null,
UserName varchar(32) not null,
Password varchar(32) not null
)
insert into Customer values (Juan’, Perez’, 123′,
CONVERT(VARCHAR(32), HashBytes(MD5′, abc’), 2))
select * from customer
create table Product(
ID int identity(1,1) primary key not null,
Name varchar(255) not null,
UnitPrice decimal(8,2) not null
)
create table OrderStatus(
ID int identity(1,1) primary key not null,
Name varchar(255) not null
)
insert into OrderStatus(Name)
values (1-Unpaid’), (2-Paid’), (3-Processing’), (4-Shipped’), (5-Delivered’)
create table CustomerOrder(
ID int identity(1,1) primary key not null,
CustomerID int not null,
Date smalldatetime not null,
OrderStatusID int not null,
Amount decimal(10,2) not null,
constraint FK_Customer_CustomerOrder foreign key (CustomerID) references Customer(ID),
constraint FK_OrderStatus_CustomerOrder foreign key (OrderStatusID) references OrderStatus(ID)
)
create table OrderDetail(
CustomerOrderID int not null,
ProductID int not null,
Quantity int not null,
Amount decimal(10,2) not null,
constraint FK_CustomerOrder_OrderDetail foreign key (CustomerOrderID) references CustomerOrder(ID),
constraint FK_Product_OrderDetail foreign key (ProductID) references Product(ID),
constraint PK_OrderDetail primary key (CustomerOrderID, ProductID)
)

 

Paso 2. Abre Visual Studio y crea un proyecto de tipo Aplicación web ASP .NET Core (dentro de la categoría .NET Core en el Visual C#)

 

Paso 3. Selecciona API en el tipo de plantilla (la versión que usaremos es ASP .NET Core 2.0)

 

Paso 4. Una vez creado el proyecto, da botón derecho sobre el nombre del proyecto y selecciona Administrar paquetes Nuget. Vamos a agregar paquetes requeridos para manejar Entity Framework Core con SQL Server en nuestro backend.

 

Paso 5. El primer paquete que agregaremos es Microsoft.Entity.FrameworkCore.SqlServer. Importante: usaremos la versión 2.0.2:

 

Paso 6. El siguiente paquete es Microsoft.EntityFrameworkCore.Tools (también versión 2.0.2)

 

Paso 7. Siguiente paquete: Microsoft.EntityFrameworkCore.Design versión 2.0.2:

 

Paso 8. Ahora instalamos Microsoft.EntityFrameworkCore.SqlServer.Design, la última versión disponible.

 

Paso 9. Finalmente, instalamos AutoMapper.

 

Paso 10. El siguiente paso es hacer el scaffolding (generación de código) a partir de la base de datos. El resultado serán clases que modelan las tablas incluidas en el almacenamiento. Accede al menú Herramientas > Administrador de paquetes Nuget > Consola del Administrador de paquetes:

 

Paso 11. A continuación, escribe el siguiente comando en la consola:

Scaffold-DbContext “Server=TuServidor;Database=StoreDB;User ID=TuUsuario;Password=TuContraseña” Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models

 

Como puedes ver, las clases fueron creadas en la carpeta Models. Cada clase tiene como propiedades los atributos de las tablas correspondientes.

 

Paso 12. Otra de las clases creadas es una clase de contexto, StoreDBContext, la cual contiene la cadena de conexión al servidor. Sin embargo, éste no es el lugar indicado para dicha cadena, por lo que vamos a sacarla de ahí y utilizar Dependency Injection. El primer paso es eliminar el código del método OnConfiguring…

 

Paso 13. …y reemplazarlo por el siguiente código:

(básicamente es el constructor de la clase llamando al constructor de la clase base pero pasando como parámetro DbContextOptions)

 

Paso 14. Ahora agrega una cadena de conexión en el archivo appsettings.json:

“ConnectionStrings”: {
     “StoreDBContext”: “Server=TuServidor;Database=StoreDB;User ID=TuUsuario;Password=TuContraseña”
},

 

Paso 15. Agrega una nueva carpeta llamada DTOs.

 

Paso 16. En dicha carpeta, agrega una clase.

 

El nombre de la clase es CustomerDTO (representando el modelo Customer)

 

Paso 17. Repite el paso anterior para tener los DTOs (Data Transfer Objects) del resto de los modelos generados previamente.

 

Paso 18. Cada DTO tendrá la misma estructura (propiedades) que el modelo que representa. Sin embargo, los elementos ICollection se convierten en List y si una clase utiliza objetos de otro modelo, se reemplaza por su respectivo DTO. A continuación tienes el código de cada DTO:

 

CustomerDTO.cs: (CustomerOrder era un objeto ICollection<CustomerOrder> pero ahora se convierte en List<CustomerOrderDTO))

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StoreWebApi.DTOs
{
public class CustomerDTO
{
public int Id { get; set; }
//public string Nombre { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public List<CustomerOrderDTO> CustomerOrder { get; set; }
}
}
view raw CustomerDTO.cs hosted with ❤ by GitHub

 

CustomerOrderDTO.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StoreWebApi.DTOs
{
public class CustomerOrderDTO
{
public int Id { get; set; }
public int CustomerId { get; set; }
public DateTime Date { get; set; }
public int OrderStatusId { get; set; }
public decimal Amount { get; set; }
public CustomerDTO Customer { get; set; }
public OrderStatusDTO OrderStatus { get; set; }
public List<OrderDetailDTO> OrderDetail { get; set; }
}
}

 

EmployeeDTO.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StoreWebApi.DTOs
{
public class EmployeeDTO
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
}
view raw EmployeeDTO.cs hosted with ❤ by GitHub

 

OrderDetailDTO.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StoreWebApi.DTOs
{
public class OrderDetailDTO
{
public int CustomerOrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal Amount { get; set; }
public CustomerOrderDTO CustomerOrder { get; set; }
public ProductDTO Product { get; set; }
}
}
view raw OrderDetailDTO.cs hosted with ❤ by GitHub

 

OrderStatusDTO.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StoreWebApi.DTOs
{
public class OrderStatusDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<CustomerOrderDTO> CustomerOrder { get; set; }
}
}
view raw OrderStatusDTO.cs hosted with ❤ by GitHub

 

ProductDTO.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StoreWebApi.DTOs
{
public class ProductDTO
{
public int Id { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public List<OrderDetailDTO> OrderDetail { get; set; }
}
}
view raw ProductDTO.cs hosted with ❤ by GitHub

 

Paso 19. A continuación crea la clase AutoMapperConfiguration (la cual mapeará los modelos a los DTOs; además vamos a ignorar las propiedades de navegación a fin de que los JSON generados por los controladores no tengan referencias circulares) en la misma carpeta DTOs con el siguiente código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using StoreWebApi.Models;
namespace StoreWebApi.DTOs
{
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDTO>()
.ForMember(x => x.CustomerOrder, o => o.Ignore())
//.ForMember(x => x.Nombre, o => o.MapFrom(s => s.FirstName))
.ReverseMap();
cfg.CreateMap<CustomerOrder, CustomerOrderDTO>()
.ForMember(x => x.OrderDetail, o => o.Ignore())
.ReverseMap();
cfg.CreateMap<Employee, EmployeeDTO>()
.ReverseMap();
cfg.CreateMap<OrderDetail, OrderDetailDTO>()
.ReverseMap();
cfg.CreateMap<OrderStatus, OrderStatusDTO>()
.ForMember(x => x.CustomerOrder, o => o.Ignore())
.ReverseMap();
cfg.CreateMap<Product, ProductDTO>()
.ForMember(x => x.OrderDetail, o => o.Ignore())
.ReverseMap();
});
}
}

 

Paso 20. Modifica la clase Startup.cs para incluir la configuración de AutoMapper, la eliminación de referencias circulares por parte de la serialización Json y la inyección de dependencias que mencionamos para el contexto.

 

La clase Startup queda con el siguiente código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
namespace StoreWebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
DTOs.AutoMapperConfiguration.Configure();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(options =>
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddDbContext<Models.StoreDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString(StoreDBContext)));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

 

Paso 21: Ahora vamos a agregar los controladores, que son los métodos expuestos por el Web Api para realizar operaciones CRUD sobre las tablas de la base de datos. Sobre la carpeta Controllers, botón derecho y selecciona Agregar > Controlador

 

Paso 22: Selecciona Controlador de API con acciones que usan Entity Framework.

 

Paso 23: En clase de modelo selecciona Customer (de la carpeta Models), luego en clase de contexto elige StoreDBContext. El nombre del controlador será CustomersController.

 

Paso 24: Repite este proceso para generar los controladores del resto de los modelos:

 

Como puedes ver, cada controlador contiene métodos para acceder a la información (Get), insertar datos (Post), modificar un registro (Put) y eliminar un elemento (Delete). Vamos a modificar cada controlador para que funcione con los DTOs, así como agregar y modificar algunos métodos. A continuación se muestra el código final de cada controlador:

 

CustomerOrdersController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using StoreWebApi.Models;
using AutoMapper;
using StoreWebApi.DTOs;
namespace StoreWebApi.Controllers
{
[Produces(application/json)]
[Route(api/CustomerOrders)]
public class CustomerOrdersController : Controller
{
private readonly StoreDBContext _context;
public CustomerOrdersController(StoreDBContext context)
{
_context = context;
}
// GET: api/CustomerOrders
[HttpGet]
public IEnumerable<CustomerOrderDTO> GetCustomerOrder()
{
return Mapper.Map<IEnumerable<CustomerOrderDTO>>(_context.CustomerOrder.OrderByDescending(x => x.Date));
}
// GET: api/CustomerOrders/Customer/5
[HttpGet(Customer/{customerId})]
public IEnumerable<CustomerOrderDTO> GetCustomer_CustomerOrder([FromRoute] int customerId)
{
return Mapper.Map<IEnumerable<CustomerOrderDTO>>(_context.CustomerOrder.Where(x => x.CustomerId == customerId).OrderByDescending(x => x.Date));
}
// GET: api/CustomerOrders/5
[HttpGet({id})]
public async Task<IActionResult> GetCustomerOrder([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customerOrder = await _context.CustomerOrder.SingleOrDefaultAsync(m => m.Id == id);
if (customerOrder == null)
{
return NotFound();
}
return Ok(Mapper.Map<CustomerOrderDTO>(customerOrder));
}
// PUT: api/CustomerOrders/5
[HttpPut({id})]
public async Task<IActionResult> PutCustomerOrder([FromRoute] int id, [FromBody] CustomerOrderDTO customerOrder)
{
customerOrder.OrderDetail = null;
customerOrder.OrderStatus = null;
customerOrder.Customer = null;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != customerOrder.Id)
{
return BadRequest();
}
_context.Entry(Mapper.Map<CustomerOrder>(customerOrder)).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerOrderExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/CustomerOrders
[HttpPost]
public async Task<IActionResult> PostCustomerOrder([FromBody] CustomerOrderDTO customerOrder)
{
customerOrder.OrderDetail = null;
customerOrder.OrderStatus = null;
customerOrder.Customer = null;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var co = Mapper.Map<CustomerOrder>(customerOrder);
_context.CustomerOrder.Add(co);
await _context.SaveChangesAsync();
customerOrder.Id = co.Id;
return CreatedAtAction(GetCustomerOrder, new { id = co.Id }, customerOrder);
}
// DELETE: api/CustomerOrders/5
[HttpDelete({id})]
public async Task<IActionResult> DeleteCustomerOrder([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customerOrder = await _context.CustomerOrder.SingleOrDefaultAsync(m => m.Id == id);
if (customerOrder == null)
{
return NotFound();
}
_context.CustomerOrder.Remove(customerOrder);
await _context.SaveChangesAsync();
return Ok(Mapper.Map<CustomerOrderDTO>(customerOrder));
}
private bool CustomerOrderExists(int id)
{
return _context.CustomerOrder.Any(e => e.Id == id);
}
}

 

CustomersController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using StoreWebApi.Models;
using StoreWebApi.DTOs;
using AutoMapper;
using System.Linq;
namespace StoreWebApi.Controllers
{
[Produces(application/json)]
[Route(api/Customers)]
public class CustomersController : Controller
{
private readonly StoreDBContext _context;
public CustomersController(StoreDBContext context)
{
_context = context;
}
// GET: api/Customers/Page/3/30
// [HttpGet(“Page/{pag}/{tam}”)]
public IEnumerable<CustomerDTO> GetCustomer([FromRoute] int pag, [FromRoute] int tam)
{
var model = _context.Customer.Skip(tam * pag 1).Take(tam).OrderBy(x => x.LastName).ThenBy(x => x.FirstName);
var dto = Mapper.Map<IEnumerable<CustomerDTO>>(model);
return dto;
}
//[HttpGet(“Page?pag={pag}&tam={tam}”)]
//FromQuery?
// GET: api/Customers/Page/3/30
[HttpGet()]
public IEnumerable<CustomerDTO> GetCustomer()
{
var model = _context.Customer.OrderBy(x => x.LastName).ThenBy(x => x.FirstName);
var dto = Mapper.Map<IEnumerable<CustomerDTO>>(model);
return dto;
}
/*
[HttpGet(“{first}/{last}/{username}”)]
public async Task<IActionResult> GetCustomer([FromRoute] string first, [FromRoute] string last, [FromRoute] string username)
{
}
*/
// GET: api/Customers/5
[HttpGet({id})]
public async Task<IActionResult> GetCustomer([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customer = await _context.Customer.SingleOrDefaultAsync(m => m.Id == id);
if (customer == null)
{
return NotFound();
}
return Ok(Mapper.Map<CustomerDTO>(customer));
}
// PUT: api/Customers/5
[HttpPut({id})]
public async Task<IActionResult> PutCustomer([FromRoute] int id, [FromBody] CustomerDTO customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != customer.Id)
{
return BadRequest();
}
_context.Entry(Mapper.Map<Customer>(customer)).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Customers
[HttpPost]
public async Task<IActionResult> PostCustomer([FromBody] CustomerDTO customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var map = Mapper.Map<Customer>(customer);
_context.Customer.Add(map);
await _context.SaveChangesAsync();
customer.Id = map.Id;
return CreatedAtAction(GetCustomer, new { id = map.Id }, customer);
}
//POST: api/Customers/Login
[HttpPost(Login)]
public async Task<IActionResult> Login([FromBody] dynamic credentials)
{
var username = (string)credentials[username];
var password = (string)credentials[password];
var customer = await _context.Customer.SingleOrDefaultAsync(m => m.UserName == username && m.Password == password);
if (customer == null)
{
return NotFound();
}
return Ok(Mapper.Map<CustomerDTO>(customer));
}
// DELETE: api/Customers/5
[HttpDelete({id})]
public async Task<IActionResult> DeleteCustomer([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customer = await _context.Customer.SingleOrDefaultAsync(m => m.Id == id);
if (customer == null)
{
return NotFound();
}
_context.Customer.Remove(customer);
await _context.SaveChangesAsync();
return Ok(Mapper.Map<CustomerDTO>(customer));
}
private bool CustomerExists(int id)
{
return _context.Customer.Any(e => e.Id == id);
}
}
}

 

EmployeesController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using StoreWebApi.Models;
using AutoMapper;
using StoreWebApi.DTOs;
using Newtonsoft.Json;
namespace StoreWebApi.Controllers
{
[Produces(application/json)]
[Route(api/Employees)]
public class EmployeesController : Controller
{
private readonly StoreDBContext _context;
public EmployeesController(StoreDBContext context)
{
_context = context;
}
// GET: api/Employees
[HttpGet]
public IEnumerable<EmployeeDTO> GetEmployee()
{
return Mapper.Map<IEnumerable<EmployeeDTO>>(_context.Employee.OrderBy(x => x.LastName).ThenBy(x => x.FirstName));
}
// GET: api/Employees/5
[HttpGet({id})]
public async Task<IActionResult> GetEmployee([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var employee = await _context.Employee.SingleOrDefaultAsync(m => m.Id == id);
if (employee == null)
{
return NotFound();
}
return Ok(Mapper.Map<EmployeeDTO>(employee));
}
[HttpPost(Login)]
public async Task<IActionResult> Login([FromBody] dynamic credentials)
{
var username = (string)credentials[username];
var password = (string)credentials[password];
var employee = await _context.Employee.SingleOrDefaultAsync(m => m.UserName == username && m.Password == password);
if (employee == null)
{
return NotFound();
}
return Ok(Mapper.Map<EmployeeDTO>(employee));
}
// PUT: api/Employees/5
[HttpPut({id})]
public async Task<IActionResult> PutEmployee([FromRoute] int id, [FromBody] EmployeeDTO employee)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != employee.Id)
{
return BadRequest();
}
_context.Entry(Mapper.Map<Employee>(employee)).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EmployeeExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Employees
[HttpPost]
public async Task<IActionResult> PostEmployee([FromBody] EmployeeDTO employee)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var e = Mapper.Map<Employee>(employee);
_context.Employee.Add(e);
await _context.SaveChangesAsync();
employee.Id = e.Id;
return CreatedAtAction(GetEmployee, new { id = e.Id }, employee);
}
// DELETE: api/Employees/5
[HttpDelete({id})]
public async Task<IActionResult> DeleteEmployee([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var employee = await _context.Employee.SingleOrDefaultAsync(m => m.Id == id);
if (employee == null)
{
return NotFound();
}
_context.Employee.Remove(employee);
await _context.SaveChangesAsync();
return Ok(Mapper.Map<EmployeeDTO>(employee));
}
private bool EmployeeExists(int id)
{
return _context.Employee.Any(e => e.Id == id);
}
}
}

 

OrderDetailsController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using StoreWebApi.Models;
using AutoMapper;
using StoreWebApi.DTOs;
namespace StoreWebApi.Controllers
{
[Produces(application/json)]
[Route(api/OrderDetails)]
public class OrderDetailsController : Controller
{
private readonly StoreDBContext _context;
public OrderDetailsController(StoreDBContext context)
{
_context = context;
}
// GET: api/OrderDetails
[HttpGet]
public IEnumerable<OrderDetail> GetOrderDetail()
{
return _context.OrderDetail;
}
// GET: api/OrderDetails/Order/5
[HttpGet(Order/{orderId})]
public async Task<IActionResult> GetOrder_OrderDetail([FromRoute] int orderId)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var orderDetail = _context.OrderDetail.Where(m => m.CustomerOrderId == orderId);
if (orderDetail == null)
{
return NotFound();
}
return Ok(Mapper.Map<IEnumerable<OrderDetailDTO>>(orderDetail));
}
// POST: api/OrderDetails
[HttpPost]
public async Task<IActionResult> PostOrderDetail([FromBody] List<OrderDetailDTO> orderDetail)
{
foreach (var item in orderDetail)
{
item.CustomerOrder = null;
item.Product = null;
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var od = Mapper.Map<IEnumerable<OrderDetail>>(orderDetail);
_context.OrderDetail.AddRange(od);
await _context.SaveChangesAsync();
return CreatedAtAction(GetOrderDetail, new { id = od.First().CustomerOrderId }, orderDetail);
}
// DELETE: api/OrderDetails/5
[HttpDelete({orderId})]
public async Task<IActionResult> DeleteOrderDetail([FromRoute] int orderId)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var orderDetail = _context.OrderDetail.Where(m => m.CustomerOrderId == orderId);
if (orderDetail == null)
{
return NotFound();
}
_context.OrderDetail.RemoveRange(orderDetail);
await _context.SaveChangesAsync();
return Ok(orderDetail);
}
private bool OrderDetailExists(int id)
{
return _context.OrderDetail.Any(e => e.CustomerOrderId == id);
}
}

 

OrderStatusesController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using StoreWebApi.Models;
using AutoMapper;
using StoreWebApi.DTOs;
namespace StoreWebApi.Controllers
{
[Produces(application/json)]
[Route(api/OrderStatuses)]
public class OrderStatusesController : Controller
{
private readonly StoreDBContext _context;
public OrderStatusesController(StoreDBContext context)
{
_context = context;
}
// GET: api/OrderStatuses
[HttpGet]
public IEnumerable<OrderStatusDTO> GetOrderStatus()
{
return Mapper.Map<IEnumerable<OrderStatusDTO>>(_context.OrderStatus.OrderBy(x => x.Name));
}
// GET: api/OrderStatuses/5
[HttpGet({id})]
public async Task<IActionResult> GetOrderStatus([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var orderStatus = await _context.OrderStatus.SingleOrDefaultAsync(m => m.Id == id);
if (orderStatus == null)
{
return NotFound();
}
return Ok(Mapper.Map<OrderStatusDTO>(orderStatus));
}
// PUT: api/OrderStatuses/5
[HttpPut({id})]
public async Task<IActionResult> PutOrderStatus([FromRoute] int id, [FromBody] OrderStatusDTO orderStatus)
{
orderStatus.CustomerOrder = null;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != orderStatus.Id)
{
return BadRequest();
}
_context.Entry(Mapper.Map<OrderStatus>(orderStatus)).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!OrderStatusExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/OrderStatuses
[HttpPost]
public async Task<IActionResult> PostOrderStatus([FromBody] OrderStatusDTO orderStatus)
{
orderStatus.CustomerOrder = null;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var os = Mapper.Map<OrderStatus>(orderStatus);
_context.OrderStatus.Add(os);
await _context.SaveChangesAsync();
orderStatus.Id = os.Id;
return CreatedAtAction(GetOrderStatus, new { id = os.Id }, orderStatus);
}
// DELETE: api/OrderStatuses/5
[HttpDelete({id})]
public async Task<IActionResult> DeleteOrderStatus([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var orderStatus = await _context.OrderStatus.SingleOrDefaultAsync(m => m.Id == id);
if (orderStatus == null)
{
return NotFound();
}
_context.OrderStatus.Remove(orderStatus);
await _context.SaveChangesAsync();
return Ok(Mapper.Map<OrderStatusDTO>(orderStatus));
}
private bool OrderStatusExists(int id)
{
return _context.OrderStatus.Any(e => e.Id == id);
}
}

 

ProductsController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using StoreWebApi.Models;
using AutoMapper;
using StoreWebApi.DTOs;
namespace StoreWebApi.Controllers
{
[Produces(application/json)]
[Route(api/Products)]
public class ProductsController : Controller
{
private readonly StoreDBContext _context;
public ProductsController(StoreDBContext context)
{
_context = context;
}
// GET: api/Products
[HttpGet]
public IEnumerable<ProductDTO> GetProduct()
{
return Mapper.Map<IEnumerable<ProductDTO>>(_context.Product.OrderBy(x => x.Name));
}
// GET: api/Products/5
[HttpGet({id})]
public async Task<IActionResult> GetProduct([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var product = await _context.Product.SingleOrDefaultAsync(m => m.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(Mapper.Map<ProductDTO>(product));
}
// PUT: api/Products/5
[HttpPut({id})]
public async Task<IActionResult> PutProduct([FromRoute] int id, [FromBody] ProductDTO product)
{
product.OrderDetail = null;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != product.Id)
{
return BadRequest();
}
_context.Entry(Mapper.Map<Product>(product)).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Products
[HttpPost]
public async Task<IActionResult> PostProduct([FromBody] ProductDTO product)
{
product.OrderDetail = null;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var p = Mapper.Map<Product>(product);
_context.Product.Add(p);
await _context.SaveChangesAsync();
product.Id = p.Id;
return CreatedAtAction(GetProduct, new { id = p.Id }, product);
}
// DELETE: api/Products/5
[HttpDelete({id})]
public async Task<IActionResult> DeleteProduct([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var product = await _context.Product.SingleOrDefaultAsync(m => m.Id == id);
if (product == null)
{
return NotFound();
}
_context.Product.Remove(product);
await _context.SaveChangesAsync();
return Ok(Mapper.Map<ProductDTO>(product));
}
private bool ProductExists(int id)
{
return _context.Product.Any(e => e.Id == id);
}
}
}

 

Paso 25: En el explorador de soluciones selecciona el archivo appsettings.json y en su propiedad Copiar en el directorio de salida elige Copiar siempre.

 

Paso 26: A continuación vamos a probar el web api construido hasta el momento. Da clic en el botón IIS Express para compilar y desplegar la aplicación

 

Paso 27: Cuando el navegador sea ejecutado, abre Postman y hagamos una prueba del método Login (que pertenece al controlador Customers)

  • Selecciona POST
  • Revisa el puerto asignado cuando se lanzó la página web para conocer el puerto de tu aplicación (también lo puedes revisar en las propiedades del proyecto).
  • Coloca la url http://localhost:puerto/api/customers/Login
  • Elige Body
  • El tipo raw
  • El formato JSON (application/json)

 

Cuando das clic en el botón Send, observa que la consulta se realiza correctamente (200 OK) y el registro específico es devuelto

 

Paso 28. Otra prueba. Una consulta de los status de las órdenes con el método GET y el controlador OrderStatuses:

 

Paso 29. Detén la aplicación. Ahora vamos a proceder a publicar el proyecto en IIS. Para ello hay que realizar algunos ajustes. En primer lugar modifica el archivo Program.cs agregando código en el método BuildWebHost:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace StoreWebApi
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseIISIntegration()
.UseKestrel()
.UseContentRoot(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location))
.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostingEnvironment env = hostingContext.HostingEnvironment;
config.SetBasePath(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location));
config.AddJsonFile(appsettings.json, optional: true, reloadOnChange: true)
.AddJsonFile($”appsettings.{env.EnvironmentName}.json, optional: true, reloadOnChange: true);
})
.UseStartup<Startup>()
.Build();
}
}
view raw Program.cs hosted with ❤ by GitHub

 

Paso 30. Ahora accede a las propiedades del proyecto (doble clic)

 

Paso 31. En la categoría Depurar modifica el valor de la variable de entorno ASPNETCORE_ENVIRONMENT a production (todo en minúsculas)

 

Paso 32. Ahora accede a la dirección https://www.microsoft.com/net/download/dotnet-core/2.0 y descarga el Runtime & Hosting Bundle (en este caso estamos descargando la versión 2.0.9 del Runtime)

 

Paso 33. Instálalo y después reinicia tu equipo.

 

 

Paso 34. Ahora procedemos a publicar el Web Api. Da clic derecho sobre el proyecto y elige la opción Publicar.

 

Paso 35. Selecciona el destino IIS, FTP, etc. y elige Publicar nuevamente.

 

Paso 36. Elige Sistema de archivos como método de publicación y selecciona una ruta de destino de publicación. Luego clic en Siguiente.

 

Paso 37. Expande la opción Base de datos y checa la opción Usar esta cadena de conexión en tiempo de ejecución. Da clic en Guardar.

 

Paso 38. En ocasiones cuando aparece esta pantalla la opción de usar la cadena de conexión no se guarda, por lo que puedes regresar dando clic en el botón Configuración y volverla a marcar. Una vez que asegures que la cadena de conexión está guardada da clic en el botón Publicar.

 

Paso 39. Al recibir el mensaje de que la aplicación web se publicó con éxito, accede a la ruta de destino seleccionada y observa que en efecto los archivos del proyecto se encuentran ahí. Ahora abre una ventana del Símbolo de sistema (cmd) y ejecuta el siguiente comando

 

dotnet “Ruta\StoreWebApi.dll”

 

Si la ejecución es correcta, verás que el proyecto está en ejecución en un puerto específico (5000):

 

Paso 40: Finalmente, accede desde Postman y verifica el correcto funcionamiento, por ejemplo puedes agregar un nuevo producto.

 

¡Y eso es todo de momento! En la siguiente entrega vamos a meterle seguridad (tokens), hacer accesible el proyecto desde un cliente (por ejemplo una app móvil) y más cosas.

 

¿Qué tal te pareció esta práctica?  Si tienes alguna duda con la implementación de este proyecto, déjame un comentario para ayudarte 🙂

 

Recuerda que también puedes apoyarte de la sesión en vivo que realizamos en YouTube:

 

Espero que esta publicación haya sido de utilidad para tí. No olvides compartirla con tus amigos si fue interesante o si crees que puede servirles.

 

Sin más por el momento, me despido. ¡Nos vemos en la próxima entrega!

 

¡Gracias por tu visita!

Luis

 

Share

Katen Doe

Katen Doe

Hello, I’m a content writer who is fascinated by content fashion, celebrity and lifestyle. She helps clients bring the right content to the right people.

Press ESC to close