sábado, 29 de mayo de 2021

Comunicación en Tiempo Real - SignalR NetCore(Backend) con Ionic-vue(Frontend) - Parte II

 En el articulo anterior construimos el backend con netcore 5 y configuramos signalr ver artículo 

Ahora construiremos el frontend y veremos como configurar signalr en nuestro cliente para poder mostrar información en tiempo real.

Lo primero que aremos será instalar el  ionic


npm install -g @ ionic / cli
Creamos nuestra aplicación

ionic start product-signalr tabs --type vue
Una vez creado nuestro proyecto instalamos el paquete que nos servirá para establecer la comunicación con signalr

npm install @ aspnet / signalr
Dentro de la carpeta src creamos un archivo api-endpoint.ts que solo tendrá una constante que representara la uri base de nuestra api para poder consumir los diferentes endpoints de la api

const url =  "http://localhost:55802";
export default url;
Dentro de la carpeta views Modificamos el archivo Tabs.vue

<template>
  <ion-page>
    <ion-tabs>
      <ion-tab-bar slot="bottom">
        <ion-tab-button tab="tab1" href="/tabs/tab1">
          <ion-icon :icon="homeOutline" />
        </ion-tab-button>
          
        <ion-tab-button tab="tab2" href="/tabs/tab2">
          <ion-icon :icon="addCircleOutline" />
        </ion-tab-button>
        
        <ion-tab-button tab="tab3" href="/tabs/tab3">
          <ion-icon :icon="informationCircleOutline" />
        </ion-tab-button>
      </ion-tab-bar>
    </ion-tabs>
  </ion-page>
</template>

<script lang="ts">
import { IonTabBar, IonTabButton, IonTabs, IonIcon, IonPage } from '@ionic/vue';
import { addCircleOutline, informationCircleOutline, homeOutline } from 'ionicons/icons';


export default {
  name: 'Tabs',
  components: {IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
  setup() {
    return {
      addCircleOutline, 
      informationCircleOutline, 
      homeOutline,
    }
  }
}
</script>
Ahora modificamos Tab1.vue que será la vista donde cargaremos todos los productos que nos devuelve la petición Get de nuestra api, pero también estableceremos la comunicación en tiempo real con signalr para que siempre que se agregue un nuevo producto los datos se actualicen en tiempo real y nos muestre una notificación con los datos agregados.

<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Home</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content :fullscreen="true" class="ion-padding">
      <ion-list>
        <ion-list-header lines="inset">
          <ion-label>Products</ion-label>
        </ion-list-header>
        <div v-for="item in dataApi" :key="item.id">
          <ion-item>
            <ion-label>
              <h2>{{ item.name }}</h2>
              <h3>{{ item.category }}</h3>
            </ion-label>
          </ion-item>
        </div>
      </ion-list>
    </ion-content>
  </ion-page>
</template>

<script lang="ts">
import {
  IonPage,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonList,
  IonListHeader,
  IonLabel,
  IonItem,
  alertController 
} from "@ionic/vue";
import { ref, onMounted } from "vue";
import { HubConnectionBuilder, LogLevel } from "@aspnet/signalr";
import api from "../api-endpoint";
export default {
  name: "Tab1",
  components: {
    IonHeader,
    IonToolbar,
    IonTitle,
    IonContent,
    IonPage,
    IonList,
    IonListHeader,
    IonLabel,
    IonItem,
  },

  setup() {
    const dataApi = ref([]);
    //method get data from api
    const getData = async () => {
      try {
        const response = await fetch(`${api}/api/product`);
        dataApi.value = await response.json();
        console.log(dataApi.value);
      } catch (error) {
        console.log(error);
      }
    };
    onMounted(async() => {
      getData();

      //connection hub backend
      const connection = new HubConnectionBuilder()
        .withUrl(`${api}/product-hub`)
        .build();

      //show alert add product
      connection.on("NewProduct",async(product: {name: string; category: string; price: number}) => {
          const alert = await alertController
            .create({
              cssClass: 'my-custom-class',
              header: 'New Product',
              subHeader: product.name,
              message: JSON.stringify(product,null,'\t'),
              buttons: ['OK'],
            });
          await alert.present();

          getData();
      });
      connection.start();
    });
    return {
      dataApi,
      getData,
    };
  },
};
</script>
También modificamos Tab2.vue que será la vista donde agregaremos un nuevo producto

<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Add Product</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content :fullscreen="true" class="ion-padding">
      <ion-row class="ion-justify-content-center">
        <ion-col size="12" size-xs="12" size-sm="6" size-md="6" size-lg="6">
          <form @submit.prevent="onSubmit">
            <ion-item>
              <ion-label position="floating">Name</ion-label>
              <ion-input v-model="name"></ion-input>
            </ion-item>
            <ion-item>
              <ion-label position="floating">Category</ion-label>
              <ion-input v-model="category"></ion-input>
            </ion-item>
            <ion-item>
              <ion-label position="floating">Price</ion-label>
              <ion-input type="number" v-model="price"></ion-input>
            </ion-item>
            <div class="ion-text-center ion-padding-top">
              <ion-button type="submit" color="success" shape="round"
                >Add
              </ion-button>
            </div>
          </form>
        </ion-col>
      </ion-row>
    </ion-content>
  </ion-page>
</template>

<script lang="ts">
import {
  IonPage,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonRow,
  IonCol,
  IonItem,
  IonLabel,
  IonInput,
  IonButton,
  loadingController,
} from "@ionic/vue";
import { ref } from "vue";
import api from "../api-endpoint";
export default {
  name: "Tab2",
  components: {
    IonHeader,
    IonToolbar,
    IonTitle,
    IonContent,
    IonRow,
    IonCol,
    IonPage,
    IonItem,
    IonLabel,
    IonInput,
    IonButton,
  },

  setup() {
    const name = ref("");
    const category = ref("");
    const price = ref("");
    const onSubmit = async () => {
      
      const loading = await loadingController.create({
        cssClass: "my-custom-class",
        message: "Add...",
      });
      try {
        await loading.present();
  
        console.log(name.value);
          //post data api
          const requestOptions = {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ name: name.value, category: category.value, price: price.value })
          };
          const response = await fetch(`${api}/api/product`, requestOptions);
          const data = await response.json();
          console.log(data);
          name.value = "";
          category.value =  "";
          price.value = "";
      } catch (error) {
        console.log(error);
      }finally{
        await loading.dismiss();
      }

    };
    return {
      name,
      category,
      price,
      onSubmit,
    };
  },
};
</script>
Si ejecutamos nuestra aplicación obtendremos el siguiente resultado

Para poder observar la comunicación en tiempo real debemos ejecutar la aplicación en 2 pestañas diferentes del navegador en una pestaña agreguemos un producto y en la otra pestaña veremos como automáticamente después de agregar un producto nos aparece una notificación que un nuevo producto se agrego.

Para ver la aplicación funcionando realice el deploy del backend en heroku y el deploy del frontend en firebase https://product-signalr-ionic.web.app


puedes descargar el código desde este repositorio

si quieren donarme para una café lo pueden hacer aqui.

Hasta la próxima.

Saludos desde El Salvador...

martes, 25 de mayo de 2021

Comunicación en Tiempo Real - SignalR NetCore(Backend) con Ionic-vue(Frontend) - Parte I

En esta oportunidad estaremos aprendiendo como poder crear aplicaciones que se puedan comunicar en tiempo real desde el backend y el fronted.

Para este ejemplo Desarrollaremos una Api usando Net Core 5, para la persistencia de datos estaremos usando una Base de datos SQLite y del lado del Frontend estaremos usando Ionic-vue para consumir la api y poder actualizar la información en tiempo real usando  SignalR.

Esta será la primera parte en la cual estaremos realizando el backend.

Lo primero que aremos será crear un nuevo proyecto en visual studio tipo ASP.NET CORE Web Api al cual nombrare Products.Api



 Elegimos la plataforma .NET 5.0


Lo siguiente que aremos será instalar los siguientes paquetes Nuget para poder usar EF con SQLite


dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
Crearemos nuestra clase modelo que representa nuestra tabla en  sqlite. Así que creamos una carpeta Models y dentro de esta creemos una clase llama Product


    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public Decimal Price { get; set; }
    }
Siempre dentro de la carpeta Models creemos nuestra clase context que nos servirá para poder generar nuestra db


    public class ProductContext : DbContext
    {
        public DbSet<Product> Products { get; set; }

        public ProductContext(DbContextOptions<ProductContext> options) : base(options)
        {

        }
    }
Dentro del archivo appsettings.json crearemos un ConnectionStrings e indicamos el nombre que tendrá nuestro db


  "ConnectionStrings": {
    "AppConnection": "DataSource=products.db;"
  },
Registramos el contexto en la clase Startup en el método ConfigureServices.


           services.AddDbContext<ProductContext>(options =>
                options.UseSqlite(Configuration.GetConnectionString("AppConnection")));
Creamos la migración inicial(deben tener instalado el cli de netcore dotnet tool install --global dotnet-ef)

Generamos la db


Configuremos SignalR crea una carpeta llamada Hubs y dentro crea una clase llamada ProductHub


    public class ProductHub : Hub
    {
        public async Task SendMyEvent()
        {
            await Clients.All.SendAsync("MyEvent");
        }
    }
Nuevamente en la clase Startup en el método ConfigureServices agregamos el servicio de SignalR 

services.AddSignalR();
Siempre en la clase Startup pero en el método Configure agregamos la configuración de SignalR, y también aprovechemos y agregamos la configuración de los cors.

            // global cors policy
            app.UseCors(x => x
                .AllowAnyMethod()
                .AllowAnyHeader()
                .SetIsOriginAllowed(origin => true) // allow any origin
                .AllowCredentials()); // allow credentials

            app.UseAuthorization();

            //endpoint hub signalR
            app.UseEndpoints(endpoints => endpoints.MapHub<ProductHub>("/product-hub"));
Creamos un controlador Product 

    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private readonly ProductContext _context;
        private readonly IHubContext<ProductHub> _hubContext;

        public ProductController(ProductContext context, IHubContext<ProductHub> hubContext)
        {
            _context = context;
            _hubContext = hubContext;
        }

        [HttpGet]
        public async Task<ActionResult<List<Product>>> Get()
        {

            return await _context.Products.ToListAsync();
        }
        [HttpPost]
        public async Task<ActionResult> Post(Product product)
        {
            _context.Products.Add(product);
            await _context.SaveChangesAsync();
            await _hubContext.Clients.All.SendAsync("NewProduct", product);

            return Ok(product);
        }
    }
Como podrán notar es un controlador simple que solo cuenta con 2 peticiones una petición Get que obtendrá todos los productos y otra petición Post que agregara productos y dentro de esta hacemos uso de la clase Hub que creamos anteriormente y haciendo uso del método SendAsync que nos proporciona la librería de SignalR le pasamos el nombre del método(NewProduct) que usaremos en el frontend para obtener la información en tiempo real del producto agregado(product)

Nuestra api con signalr esta lista


puedes descargar el código desde este repositorio

En el próximo articulo estaremos creando el frontend y consumiendo la api.


si quieren donarme para una café lo pueden hacer aqui.

Hasta la próxima.

Saludos desde El Salvador...