[Event] NetConfCo 2018 |.Net Core Multiplataforma. ¿Sera Verdad?

Esta vez viajamos con amigos  compañeros de trabajo para participar en la NetConfCO Medellín, Colombia. Fuimos recibidos con una gran calidez por toda la gente de Colombia y no alcanzas la gracias para describir los excelentes momentos que pasamos.

Estuvimos viendo:

Todos comentan que .Net Core es OpenSource y multiplataforma. ¿Puedo trabajar 100% en linux como en windows? ¿Quién usa linux con .Net core? ¿Que beneficios tengo al trabajar en Linux? ¿Que necesito para empezar?. En esta charla te vamos a contar cómo podemos trabajar con .Net Core sin la necesidad de tener Windows. Además, crearemos un entorno con todo lo necesarios para llevar adelante cualquier proyecto .Net Core sea en Windows o en Linux. También, veremos cómo algunas personas de un equipo pueden trabajar en Linux y otras Windows al mismo tiempo sin conflictos.

Les dejo los links de la presentación, videos y webcast:

Presentación

Video

Video Entrevista

Nota WebCast

WebCast

[HowTo] VS Code | Crear una WebApi con Asp.Net Core en Linux

Como comente en post anterior Microsoft está entrando fuertemente al mundo OpenSource. Por esta razon hoy podemos disfrutar de Asp.Net Core en la plataforma Linux, en mi caso KUbuntu, junto a algunas herramientas muy poderosas com VS Code.

Objetivo

El objetivo de esta publicación tiene un tutorial de como debemos preparar nuestro linux para hacer uso de Asp.Net junto a una aplicacion del tipo WebApi que usaremos en post futuros.

Audiencia

Este documento está dirigido a personas que conocen muy poco o nada sobre el tema o personas que desarrollan tareas de consultoría de desarrollo o que simplemente están interesados en leer e investigar sobre la tecnología alcanzada por esta publicación.

Desarrollo

Para empezar lo que haremos es instalar Net Core es nuestro linux. Lo haré sobre mi distribución preferida que es KUbuntu. Primero necesitamos instalar las llaves de producto y actualizar el listado de paquetes en nuestra distribución linux. Abriremos una consola y ejecutamos los siguiente comandos estos 4 comandos en este orden:

curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-zesty-prod zesty main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-get update

 

Ya ejecutado todo estamos listos para instalar:

sudo apt-get install dotnet-sdk-2.0.0

Una vez que instalamos todo y configurado estamos listso. Vamos a crear nuestra primera aplicación en linux. Crearemos una carpeta y ejecutaremos el comando dotnet que nos ayuda a crear la plantilla:

mkdir MvcMyApp
cd MvcMyApp
dotnet new mvc

Ahora podemos abrir desde VS Code que instalamos en un post anterior. Con Solo escribir “code .” se abrirar el VS Code en la carpeta donde estamos parados. Ahora necesitaremos instalar VS Code C# extensión. Lo haremos desde el icono de VS Code, lo buscaremos y lo instalaremos.

Luego de instalarlo recargamos la ventana y comenzará a instalarse algunos componentes necesarios que nos permitirá usar la depuración en VS Code.

Ahora solo nos queda presionar F5 y guala!

 

Una WebApi para ejemplos

Lo que haremos ahora es crear una WebApi Rest que la usaremo en futuros ejemplos en un el tutorial de TypeScript. Vamos a necesitar tener instalado:

  • DB Browser for Sqlite (podemos instalarlos desde el gestor de software que viene con KUbuntu.
  • Postman (podemos descargarlo desde aqui https://www.getpostman.com/)

Empezaremos con crear una carpeta DemoApi y dentro ejecutaremos los comandos de creación y lo abriremos, solo hay que seguir los siguientes comando en orden:

mkdir DemoService
cd DemoService
dotnet new webapi
code .

Nuevamente nos aparecerán los warning a los que debemos darle yes.

Crearemos la base de datos con Sql DB Browser dentro de una carpeta data. La base de Datos la llamaremos DBDemoServices y corremos el script que dejare en el github dentro de la carpeta data se llama base.txt. Este script creará 2 tablas e insertara datos para hacer el demo.

En nuestro servicio usaremos Entity Framework Core como herramienta de acceso a datos. No ayudará con nuestra base de datos Sqlite. Agregaremos las siguientes lineas en nuestro archivo DemoService.csproj:

<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0"/>

Desde la consola ejecutaremos dotnet restore para actualizar nuestros paquetes:

Comenzaremos creando nuestros modelos y nuestro DBContext. Crearemos 3 archivos, Customer.cs, CustomerType y DemoServiceContext.cs. Todos dentro de una carpeta Models con la siguiente clases dentro:

Customer.cs

using System;
using System.Collections.Generic;

namespace DemoService.Models
{
public partial class Customers
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string SurName { get; set; }
public string EmailAddress { get; set; }
public int? CustomerTypeId { get; set; }
public string Notes { get; set; }

public virtual CustomersTypes CustomerType { get; set; }
}
}

CustomerType.cs

using System;
using System.Collections.Generic;

namespace DemoService.Models
{
public partial class CustomersTypes
{

public CustomersTypes()
{
Customers = new HashSet<Customers>();
}

public int CustomerTypeId { get; set; }
public string Description { get; set; }

public virtual ICollection<Customers> Customers { get; set; }

}

}

DemoServiceContext.Typecs

using Microsoft.EntityFrameworkCore;
using DemoService.Models;

namespace DemoService.Models
{
public class DemoDBContext : DbContext
{
public DemoDBContext(DbContextOptions<DemoDBContext> options)
: base(options)
{
}
public virtual DbSet<Customers> Customers { get; set; }
public virtual DbSet<CustomersTypes> CustomersTypes{ get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Filename=BDemoSevice.db;");
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customers>(entity =>
{
entity.HasKey(e => e.CustomerId)
.HasName("PK_Customers");

entity.Property(e => e.EmailAddress).HasColumnType("varchar(150)");

entity.Property(e => e.FirstName).HasColumnType("varchar(50)");

entity.Property(e => e.MiddleName).HasColumnType("varchar(50)");

entity.Property(e => e.Notes).HasColumnType("varchar(5000)");

entity.Property(e => e.SurName).HasColumnType("varchar(50)");

entity.HasOne(d => d.CustomerType)
.WithMany(p => p.Customers)
.HasForeignKey(d => d.CustomerTypeId)
.HasConstraintName("FK_Customers_CustomersTypes");
});

modelBuilder.Entity<CustomersTypes>(entity =>
{
entity.HasKey(e => e.CustomerTypeId)
.HasName("PK_CustomersTypes");

entity.Property(e => e.CustomerTypeId).ValueGeneratedNever();

entity.Property(e => e.Description).HasColumnType("varchar(50)");
});
}

}
}

Debemos agregar en nuestro archivo Startup.cs la inyección de nuestra base de datos.

public void ConfigureServices(IServiceCollection services){
services.AddMvc();
services.AddDbContext<DemoDBContext>();
}

Ahora que tenemos todo configurado crearemos dentro de la carpeta Controllers un archivo llamado customers.cs donde tendremos nuestra api:

CustomersControllers.cs

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using DemoService.Models;
using System.Linq;

namespace DemoService.Controllers
{
[Route("api/[controller]")]
public class CustomersController : Controller
{
private readonly DemoDBContext _context;

public CustomersController(DemoDBContext context)
{
_context = context;
}

[HttpGet]
public IEnumerable<Customers> GetAll()
{
return _context.Customers.ToList();
}

}
}

Para comprobar que funciona correctamente ejecutaremos la aplicación presionando F5 y lo invocamos con el Postman:

Bien está funcionando correctamente. Agregamos los métodos de crear, editar y eliminar y también agregaremos el CustomersTypesController.cs y un modelo que será ModeCustomerPage.cs que será para devolver páginas de Customers.

Models/ViewModelPage.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DemoService.Models;

namespace DemoService.Models
{
public class ViewModelPage
{
public int CountPages { get; set; }
public int CountCustomers { get; set; }
public int ActualPage { get; set; }
public IList<Customers> customers { get; set; }
}
}

CustomersControllers.cs

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using DemoService.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace DemoService.Controllers
{
[Route("api/[controller]")]
public class CustomersController : Controller
{
private readonly DemoDBContext _context;

public CustomersController(DemoDBContext context)
{
_context = context;
}

[HttpGet]
public IEnumerable<Customers> GetCustomers()
{
return _context.Customers;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetCustomers([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var customers = await _context.Customers.SingleOrDefaultAsync(m => m.CustomerId == id);

if (customers == null)
{
return NotFound();
}

return Ok(customers);
}

[HttpGet]
[Route("{numberPage}/{perPage}")]
public ViewModelPage GetCustomerPaging(int numberPage, int perPage)
{

int _customersCount = GetCustomers().Count();
int _countPages = (_customersCount / perPage) + ( _customersCount % perPage == 0 ? 0 : 1);

var page = new ViewModelPage();
page.ActualPage = numberPage;
page.CountCustomers = GetCustomers().Count();
page.CountPages = _countPages;
page.customers = GetCustomers().Skip(numberPage * perPage).Take(perPage).ToList();

return page;
}

[HttpPut("{id}")]
public async Task<IActionResult> PutCustomers([FromRoute] int id, [FromBody] Customers customers)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (id != customers.CustomerId)
{
return BadRequest();
}

_context.Entry(customers).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomersExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return NoContent();
}

[HttpPost]
public  IActionResult PostCustomers([FromBody] Customers customers)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

_context.Customers.Add(customers);
_context.SaveChanges();

return CreatedAtAction("GetCustomers", new { id = customers.CustomerId }, customers);
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCustomers([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var customers = await _context.Customers.SingleOrDefaultAsync(m => m.CustomerId == id);
if (customers == null)
{
return NotFound();
}

_context.Customers.Remove(customers);
await _context.SaveChangesAsync();

return Ok(customers);
}

private bool CustomersExists(int id)
{
return _context.Customers.Any(e => e.CustomerId == id);
}

}
}

CustomersTypesControllers.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 DemoService.Models;

namespace DemoService.Controllers
{

[Route("api/[controller]")]
public class CustomersTypesController : Controller
{
private readonly DemoDBContext _context;

public CustomersTypesController(DemoDBContext context)
{
_context = context;
}

[HttpGet]
public IEnumerable<CustomersTypes> GetCustomersTypes()
{
return _context.CustomersTypes;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetCustomersTypes([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var customersTypes = await _context.CustomersTypes.SingleOrDefaultAsync(m => m.CustomerTypeId == id);

if (customersTypes == null)
{
return NotFound();
}

return Ok(customersTypes);
}

[HttpPut("{id}")]
public async Task<IActionResult> PutCustomersTypes([FromRoute] int id, [FromBody] CustomersTypes customersTypes)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (id != customersTypes.CustomerTypeId)
{
return BadRequest();
}

_context.Entry(customersTypes).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomersTypesExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return NoContent();
}

[HttpPost]
public async Task<IActionResult> PostCustomersTypes([FromBody] CustomersTypes customersTypes)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

_context.CustomersTypes.Add(customersTypes);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (CustomersTypesExists(customersTypes.CustomerTypeId))
{
return new StatusCodeResult(StatusCodes.Status409Conflict);
}
else
{
throw;
}
}

return CreatedAtAction("GetCustomersTypes", new { id = customersTypes.CustomerTypeId }, customersTypes);
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCustomersTypes([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var customersTypes = await _context.CustomersTypes.SingleOrDefaultAsync(m => m.CustomerTypeId == id);
if (customersTypes == null)
{
return NotFound();
}

_context.CustomersTypes.Remove(customersTypes);
await _context.SaveChangesAsync();

return Ok(customersTypes);
}

private bool CustomersTypesExists(int id)
{
return _context.CustomersTypes.Any(e => e.CustomerTypeId == id);
}
}
}

Listo nuestra aplicación está completa ahora probaremos un post con el postman.

Conclusión

Hemos instalado y configurado .Net Core y Asp.Net Core en nuestro linux. También hicimos una pequeña base de datos y nos conectamos a ella por medio de WebApi para tener servicio Rest. Estos servicios rest los usaremos en otros post para algunos ejemplo. Pueden descargar el código complete de GitHub desde esta url https://github.com/withoutdebugger/DemoService

 

 

[Article] Azure | Integrando Azure Search en un Website

En en el artículos anterior (ir al articulo anterior) estuvimos viendo qué es Azure Search y como podemos configurarlo para comenzar a utilizarlo. En este artículo veremos cómo integrarlo en una Aplicación Web por medio del SDK que nos brindan.

Objetivo

En esta entrega estaremos integrando Azure Search en una Aplicación Web. Veremos sus características y funcionalidades plasmadas en un WebSite. El código del proyecto podrán descargo desde GitHub desde esta dirección

https://github.com/withoutdebugger/DemoAzureSearchWODv1

Audiencia

Este documento está dirigido a personas que conocen muy poco o nada sobre el tema o personas que desarrollan tareas de consultoría de desarrollo o que simplemente están interesados en leer e investigar sobre la tecnología alcanzada por esta publicación.

Desarrollo

Antes que nada necesitaremos el SDK. El SDK posee un conjunto de librerías de cliente las cuales las utilizaremos para manejar los índices, los datasource, indexadores, subir documentos, ejecutar queries y todo lo que necesitemos.

Las librerías principales son:

  • Azure.Search
  • Search.Models

Estas librerías tienen definidas algunas clases principales como Index, Field y Document como también algunas operaciones como Indexes.Create o Documents.Search asociadas a SearchServiceClient y SerachIndeCliente. Están agrupadas de una forma lógica para una mejor compresión.

Una vez listo el SDK en nuestra aplicación necesitaremos los ApiKey que Azure. Esta llave nos dará acceso al servicio y a base de búsqueda. La guardaremos en nuestra configuración de connections strings.

Para obtenerlos debemos ingresar al portal de Azure. Seleccionamos nuestro servicio de búsqueda -> opción claves:

Estas claves la usaremos dentro de nuestro archivo appsettings.json para tenerlas disponibles y consultarlas en el momento de consumir el servicio.

{
"AzureSearch": {
"SearchServiceName": "{nombre del servicio}",
"SearchServiceAdminApiKey": "{apikey}",
"SearchServiceQueryApiKey": "{apikey} "
},

...

}

Nuestra aplicación es una aplicacion ASP.Net Core MVC junto a una Web APi que será consumida por medio de JQuery. No voy a entrar en detalle de la aplicación MVC pero si en el servicio de búsqueda.

Tendremos el Home Controller que solamente nos devolverá la página inicial donde se encontrarán nuestros métodos en JQuery que consumirán los servicios de búsqueda.

Crearemos una Web API que se llamara Search. Será un controller llamado SearchController. Este recibirá en su constructor la clase IConfiguration para tener disponible el servicio de Azure Search.

{
searchServiceName = _configuration["AzureSearch:SearchServiceName"];
queryApiKey = _configuration["AzureSearch:SearchServiceQueryApiKey"];
indexClient = new SearchIndexClient(searchServiceName, "realestate-us-sample", new SearchCredentials(queryApiKey));
}

Nuestro método principal Get será el encargado de recibir las consultas desde el cliente. En esta primera versión filtra por texto, facets, tag y manejara la paginación. También tendremos algunos métodos privados que nos ayudaran a armar la consulta final.

Modelo Hotel

Este modelo es en el cual se transformara el resultado de Azure Search en nuestra aplicación.

public partial class Hotel
{
[System.ComponentModel.DataAnnotations.Key]
public string listingId { get; set; }
public string description { get; set; }
public string description_es { get; set; }
public string number { get; set; }
public string street { get; set; }
public string city { get; set; }
public string countryCode { get; set; }
public string postCode { get; set; }
public string thumbnail { get; set; }
public string status { get; set; }
public string[] tags { get; set; }
}

Get Method

El método principal recibirá los parámetros de consulta. Dentro podemos ver que tenemos los métodos privados CreateFilterFacets, CreateFilterTags, CreateParameter.

[HttpGet]
public DocumentSearchResul<Hotel> Get(string searchtext = "", string cityFacet = "", string typeFacet = "", string tags = "", string orderby = "", string derbydirection "desc", int actualPage = 1)
{
//Filter
string filter = CreateFilter(cityFacet, typeFacet);

//Filter Tags
IList<ScoringParameter> filterTags = CreatedFilterTags(tags);

//Parameters
SearchParameters sp = CreateParameter(10, filter, orderBy, filterTags, actualPage);

//Search
var result = indexClient.Documents.Search<Hotel>(searchtext, sp);
return result;
}

CreateFilterFacets

 Este método es el encargado de crear los filtros para los facets de City y Type. Como podemos ver en el método, el resultado un string que acompañará a la consulta. Azure Index utiliza una estructura OData formar las consultas. Por ejemplo, en el string el comando podemos ver el comando eq que significa igual. Podemos ver la lista completa de comando desde este link:

https://docs.microsoft.com/en-us/rest/api/searchservice/odata-expression-syntax-for-azure-search

private string CreateFilterFacets(string cityFacet, string typeFacet) {

string filter = "";

if (cityFacet != null)
{
filter += filter != string.Empty ? " and " : "";
filter += "city eq '" + cityFacet + "'";
}

if (typeFacet != null)
{
filter += filter != string.Empty ? " and " : "";
filter += "type eq '" + typeFacet + "'";
}

return filter;
}

CreateFilterTags

Este método es el encargado de crear una lista de Tags para filtrar. A diferencia del anterior, los tags deben filtrarse por medio de ScoringParameter que hace referencia a un nivel de acierto.

private IList<ScoringParameter> CreateFilterTags(string tags) {

IList<ScoringParameter> _parameterTags = new List<ScoringParameter>();

if (tags == null) { return _parameterTags; }

string[] _listTags = tags.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);

if (_listTags.Count() > 0)
{
_parameterTags.Add(new ScoringParameter("tags", _listTags));
}

return _parameterTags;
}

CreateParameter

Antes de la ejecución final de obtención de resultados debemos armar un SearchParamters. Desde este manejaremos la configuración principal de la consulta como el modo de búsqueda, cuántos tomar por medio de top, el filtro de text, el cálculo para la página, los campos que usaremos, si incluimos el total y los facets que filtraremos. Por último también se agregara tags correspondientes al ScoringParameters.

private SearchParameters CreateParameter(int top, string filter, IList<string> orderby, IList<ScoringParameter> filterTag, int currentPage)
{

SearchParameters searchParameters =
new SearchParameters() {
SearchMode = SearchMode.Any,
Top = 10,
Filter = filter,
Skip = currentPage - 1,
Select = new Lis<String>() { "listingId", "description", "description_es", "number", "street", "city", "countryCode", "postCode", "thumbnail", "status", "tags" },
// Add count
IncludeTotalResultCount = true,
//// Add facets
Facets = new List<String>() { "city", "type", "status", "tags" },
};

searchParameters.OrderBy = orderby;
searchParameters.ScoringParameters = filterTag;

return searchParameters;
}

Ejecución de resultado

Una vez que tenemos todo los objetos armados para nuestra consulta se llamará al siguiente método perteneciente al cliente de Azure Search.

var result = indexClient.Documents.Search<Hotel>(searchtext, sp);

indexClient es del tipo SearchIndexClient el cual es el encargado de realizar finalmente la consulta en nuestro servicio de Azure Search. Podemos ver que le enviaremos nuestra clase Hotel y el se encargara de hacer la transformación.

La aplicación

Finalmente nuestra aplicación se verá visualizará de la siguiente forma luego de una búsqueda:

Podemos filtrar la búsqueda por medio de la selección en cada una de las secciones City, Type y Tags para ajustar nuestros resultados. El encargado de ajustar la búsqueda sera los métodos creados en JQuery.

 

Conclusión

Azure Search es una excelente herramienta para realizar potentes búsquedas. También nos brinda un SDK muy potente para que podamos realizar consultas muy rápidamente y poder así integrar rápidamente en nuestras aplicaciones

Les dejos la aplicación de ejemplo en GitHub para que puedan consultarla:

https://github.com/withoutdebugger/DemoAzureSearchWODv1

También les dejo el link del demo:

http://wodazuresearch.azurewebsites.net/