Generador de Servicios Web Con DDD (Domain Driven Design)
Bueno, como muchos que leen este blog sigo estudiando la generación de modelos basados en MetaDatos, ya que en mis libros solo escribo el diseño mínimo de una generación de base de datos, pero si bien es cierto el modelo es extensible a lo que nosotros queramos, basado en esta premisa he realizado un programa que genera los códigos fuentes de un patrón de modelo DDD, lo cual me permite diseñar servicios web en un click con C# en este caso, en la ultima versión ya genera código para Java, basado en servicios REST con Sprint y PHP con mi propio modelo de generación de servicios, como el código es complejo para explicar, dejare los vídeos del funcionamiento del programa
USO DEL PROGRAMA WebServiceGenerator
USO DE LOS ARCHIVOS WebServicesGenerator
Actualmente estoy trabajando en un Trasladador de Bases de datos, esto es transportar el esquema de base de datos de cualquier base de datos a cualquier base de datos, próximamente les pondré los vídeos de uso para que también los veas.
Si quieres leer mis libros puedes hacerlo de las siguientes ligas:
Análisis, Diseño y Extensión de Meta-Datos Orientado al Desarrollo de Software: C# y SQLServer
Análisis, Diseño y Extensión de Meta-Datos Orientado al Desarrollo de Software: PHP y MySQL
Próximamente…
El 20 de Febrero de 2019, mi segundo libro:
Análisis y Diseño del Desarrollo de Bases de Datos con Meta-Datos – MySQL PHP

WebHooks ¿Qué son? ¿Como hago Uno? (Parte 3)
Y bien ya estamos en la tercera parte de nuestra entrega sobre WebHooks hasta el momento, en las anteriores entregas hemos generado nuestro código principal para un manager que contiene varios webhooks, esta tercera parte nos permitirá hacer uso del WebHook como tal.
A continuación se presentan los Archivos que se van a generar dentro del Proyecto:
- WHService
- Application
- AppWebHook.cs (Crear)
- Contract
- Data
- WH
- GetRequest.cs (Crear)
- GetResponse.cs (Crear)
- PostRequest.cs (Crear)
- PostResponse.cs (Crear)
- WH
- Service
- IWebHookService.cs (Crear)
- Data
- Service
- WebHookService.cs (Crear)
- Web.Config (Modificar)
- Application
Comenzamos por mostrar los códigos.
AppWebHook.cs
public class AppWebHook : IDisposable
{
public GetResponse GetData(string Application, string ApiKey, string Token, string Resource, GetRequest request)
{
GetResponse response = new GetResponse();
Domain.Application a = ApplicationRepository.GetApplicationByName(Application);
Message m = MessageRepository.GetMessagesByResource(Resource);
if (m == null)
{
throw new Exception("El Recurso no Existe");
}
if (a == null)
{
throw new Exception("La Aplicación no Existe");
}
if (a.ApiKey != ApiKey)
{
throw new Exception("La Llave Secreta no Coincide");
}
if (a.CveApplication != m.CveApplication)
{
throw new Exception("El recurso no pertenece a esta aplicación");
}
Data d = null;
if (request.Id > 0)
{
d = DataRepository.GetDataById(request.Id, a.CveApplication);
}
else
{
d = DataRepository.GetLastDatabyApplication(a.ApplicationName);
}
response.Data = d.Info;
return response;
}
public PostResponse PostData(string Application, string ApiKey, string Token, string Resource, PostRequest request)
{
PostResponse response = new PostResponse();
Domain.Application a = ApplicationRepository.GetApplicationByName(Application);
Message m = MessageRepository.GetMessagesByResource(Resource);
if (m == null)
{
throw new Exception("El Recurso no Existe");
}
if (a == null)
{
throw new Exception("La Aplicación no Existe");
}
if (a.ApiKey != ApiKey)
{
throw new Exception("La Llave Secreta no Coincide");
}
if (a.CveApplication != m.CveApplication)
{
throw new Exception("El recurso no pertenece a esta aplicación");
}
string mess = request.Message;
switch (request.Type)
{
case "JSON":
Result o = CustomCompiler.ExecuteData(m.RequestData, mess);
break;
default: //Marcaremos todo Como String
break;
}
Data d = new Data();
d.CveApplication = a.CveApplication;
d.CveMessage = m.CveMessage;
d.DateInsert = DateTime.Now;
d.IsRead = false;
d.Info = mess;
DataRepository.Add(d);
return response;
}
public void Dispose()
{
}
}
GetRequest.cs
publicclassGetRequest
{
public string Resource { get; set; }
public int Id { get; set; }
}
GetResponse.cs
publicclassGetResponse
{
public string Data { get; set; }
}
PostRequest.cs
publicclassPostRequest
{
public string Type { get; set; }
public string Message { get; set; }
}
PostResponse.cs
publicclassPostResponse
{
}
IWebHookService.cs
[ServiceContract]
public interface IWebHookService : IDisposable
{
[OperationContract]
[WebInvoke(UriTemplate = "{Resource}/{id=0}/", Method = "GET", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json),
Description("Obtiene un Recurso Generado")]
GetResponse GetData(string Resource, string id);
[OperationContract]
[WebInvoke(UriTemplate = "{Resource}/", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json),
Description("Recurso Generado")]
PostResponse PostData(string Resource, PostRequest request);
}
WebHookService.cs
private readonly AppWebHook appwh;
public WebHookService()
{
appwh = new AppWebHook();
}
public GetResponse GetData(string Resource, string id)
{
GetResponse response = new GetResponse();
try
{
WebHeaderCollection headers = ValidateHeaders();
GetRequest request = new GetRequest();
request.Resource = Resource;
request.Id = string.IsNullOrEmpty(id) ? 0 : Convert.ToInt32(id);
response = appwh.GetData(headers.Get("Application"), headers.Get("ApiKey"), headers.Get("Token"), Resource, request);
}
catch (Exception ex)
{
throw new WebFaultException(ex.Message, HttpStatusCode.BadRequest);
}
return response;
}
public PostResponse PostData(string Resource, PostRequest request)
{
PostResponse response = new PostResponse();
try
{
WebHeaderCollection headers = ValidateHeaders();
appwh.PostData(headers.Get("Application"), headers.Get("ApiKey"), headers.Get("Token"), Resource, request);
}
catch (Exception ex)
{
throw new WebFaultException(ex.Message, HttpStatusCode.BadRequest);
}
return response;
}
private static WebHeaderCollection ValidateHeaders()
{
if (WebOperationContext.Current != null)
{
WebHeaderCollection headers = WebOperationContext.Current.IncomingRequest.Headers;
if (string.IsNullOrEmpty(headers.Get("Content-Type")))
{
throw new Exception("Error al obtener Encabezados");
}
else if (string.IsNullOrEmpty(headers.Get("Application")))
{
throw new Exception("Error al obtener Encabezados");
}
else if (string.IsNullOrEmpty(headers.Get("ApiKey")))
{
throw new Exception("Error al obtener Encabezados");
}
else if (string.IsNullOrEmpty(headers.Get("Token")))
{
throw new Exception("Error al obtener Encabezados");
}
return headers;
}
else
{
throw new Exception("Error al obtener Encabezados");
}
}
}
Web.Config
En este caso, agregamos la configuración que nos lleva a exponer el Api
<service behaviorConfiguration="BehaviorConfigurationService" name="WHService.Service.WebHookService"> <endpoint address="" binding="basicHttpBinding" name="WHEndPoint" contract="WHService.Contract.Service.IWebHookService"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> <endpoint address="/api/" binding="customBinding" bindingConfiguration="RawReceiveCapable" behaviorConfiguration="RestServiceEndPointBehaviors" contract="WHService.Contract.Service.IWebHookService"/> </service>
Vamos a mostrar, las ejecuciones con un proyecto de SOAPUI y consumir nuestro servicio como un servicio REST, vamos a probarlo:
En la imagen anterior podemos ver como hemos asignado los recursos al SOAPUI y realizaremos la operación Hello en los métodos GET y POST, a continuación vemos el método que usaremos para insertar
Como podemos ver el metodo get recibe los Headers ApiKey, Application, Token, Content-Type, estos encabezados nos permitirán buscar y hacer validaciones dentro del sistema, cabe mencionar que el metodo Token, debería llevar un Digestivo con las llaves que creamos, esto para poder verificar que la persona que esta agregando información a nuestro WebHook es alguien que tiene la Llave, por eso es que cuando se crea una aplicación se dan llaves de Cifrado y el ApiKey, con esto, el sistema ya tiene los datos dentro de la Base de datos.
A continuación veremos el metodo GET
Como podemos ver nuestros datos tal cual han sido agregados al sistema. finalmente se tienen que hacer más acciones de seguridad, pero como siempre los invito a implementar y re implementar sus propias mejoras, esto es una idea, para que creen ideas más grandes.
(Descarga)
WebHooks ¿Qué son? ¿Como hago Uno? (Parte 2)
Bien, en la anterior entrega, vimos como crear el servicio para nuestro WebHook, en esta ocasión vamos a crear el Manager con el cual podremos Agregar y Editar tanto Aplicaciones y Mensajes. Vamos a comenzar Agregando un nuevo Proyecto a nuestra solución WHService, y lo llamaremos WHManager, hago un paracentesis en esta parte dado que me la llevaré rápido con el MVC, si quieren saber como integrar bootstrap a MVC puedes pasarte por esta liga en ella podrás ver a profundidad como utilizo el MVC y WebApi.
Comenzamos agregando un proyecto de MVC al cual nombraremos WHManager como en la siguiente Imagen:
Luego de poner el nombre y dar click en aceptar vamos a seleccionar Empty Proyect, y nos muestra una solución base como la siguiente:
Apartir de Aqui vamos a crear y modificar Archivos en las diferentes secciones:
- App_Start
- WebApiConfig.cs (Modificar)
- Controllers
- HomeController.cs (Crear)
- css (Copiar)
- Fonts (Copiar)
- img (Copiar)
- js (Copiar)
- Models
- Result.cs (Crear)
- service
- ApplicationController.cs (Crear)
- ResourceController.cs (Crear)
- Views
- Home
- AddApplication.cshtml (Crear)
- AddResource.cshtml (Crear)
- Index.cshtml (Crear)
- Shared
- _Layout.cshtml (Crear)
- Home
Haciendo los cambios y creaciones de archivo podremos hacer las pruebas correspondientes de nuestro Administrador de WebHook, así que vamos a dar una vista rápida de los archivos que vamos a copiar para estructurar el proyecto y después veremos los códigos de los archivos que vamos a crear.
Para comenzar descargaremos un template de bootstrap que ya se me está haciendo costumbre de aquí. A continuación muestro los archivos que encontramos una vez descargado nuestro template:
A continuación vamos a mover el contenido de font-awesome dentro de ella encontraremos la carpeta css y fonts, de las cuales sacaremos los contenidos y moveremos lo del css a la carpeta raiz css y lo de la carpeta fonts a la carpeta fonts, quedando de la siguiente manera en nuestra solución:
Después de esto agregaremos a la solución las carpetas, para no hacer más largo este post, me dedicaré a la muestra de los archivos que se deben crear, como ya dije antes si quieren saber más de como se usa el MVC pueden hacerlo de esta liga.
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "service/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Como podrás darte cuenta agregamos 2 lineas de configuración para poder trabajar exclusivamente en JSON.
HomeController.cs
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
string SourceView = "Index";
if (this.Request.Params["Source"] != null)
{
SourceView = this.Request.Params["Source"];
}
return View(SourceView);
}
}
En el código anterior podemos ver nuestro controller Home, el cual nos permite acceder a todas las vistas que serán utilizados más adelante.
Result.cs
public class Result
{
public bool success { get; set; }
public string AdditionalData { get; set; }
}
Esta clase, nos permite los retornos que usaremos en los servicios del WebApi
ApplicationController.cs
public class ApplicationController : ApiController
{
public GetApplicationsResponse get()
{
GetApplicationsResponse response = new GetApplicationsResponse();
GetApplicationRequest request = new GetApplicationRequest();
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.GetApplications(request);
return response;
}
public GetApplicationResponse get(string ApplicationName)
{
GetApplicationResponse response = new GetApplicationResponse();
GetApplicationRequest request = new GetApplicationRequest();
request.ApplicationName = ApplicationName;
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.GetApplication(request);
return response;
}
[HttpPost]
public Result post(AddAplicationRequest request)
{
Result result = new Result();
AddApplicationResponse response = new AddApplicationResponse();
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.AddApplication(request);
result.success = true;
result.AdditionalData = JsonConvert.SerializeObject(response);
return result;
}
[HttpPut]
public Result put(UpdateApplicationRequest request)
{
Result result = new Result();
UpdateApplicationResponse response = new UpdateApplicationResponse();
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.UpdateApplication(request);
result.success = true;
result.AdditionalData = JsonConvert.SerializeObject(response);
return result;
}
[HttpDelete]
public Result Delete(int CveApplication)
{
Result result = new Result();
DeleteApplicationResponse response = new DeleteApplicationResponse();
DeleteApplicationRequest request = new DeleteApplicationRequest();
request.CveApplication = CveApplication;
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.DeleteApplication(request);
result.success = true;
result.AdditionalData = JsonConvert.SerializeObject(response);
return result;
}
}
Como podemos ver el codigo anterior es parte del servicio del WebApi, que nos permite crear servicios GET y POST, PUT que nos retornan directamente JSON, en este caso es para el servicio de aplicaciones
ResourceController.cs
public class ResourceController : ApiController
{
public GetMessagesResponse get(string ApplicationName)
{
GetMessagesResponse response = new GetMessagesResponse();
GetMessageRequest request = new GetMessageRequest();
request.ApplicationName = ApplicationName;
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.GetMessages(request);
return response;
}
[HttpPost]
public Result post(AddMessageRequest request)
{
Result result = new Result();
AddMessageResponse response = new AddMessageResponse();
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.AddMessage(request);
result.success = true;
result.AdditionalData = JsonConvert.SerializeObject(response);
return result;
}
[HttpPut]
public Result put(UpdateMessageRequest request)
{
Result result = new Result();
UpdateMessageResponse response = new UpdateMessageResponse();
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.UpdateMessage(request);
result.success = true;
result.AdditionalData = JsonConvert.SerializeObject(response);
return result;
}
[HttpDelete]
public Result Delete(DeleteMessageRequest request)
{
Result result = new Result();
DeleteMessageResponse response = new DeleteMessageResponse();
WebHookManagerClient servicio = new WebHookManagerClient();
response = servicio.DeleteMessage(request);
result.success = true;
result.AdditionalData = JsonConvert.SerializeObject(response);
return result;
}
}
Mismo caso pero para la sección de Recursos o mensajes. aquí van a encontrar un detalle, necesitan las clases del servicio esto lo podemos hacer agregando una referencia como vemos en la siguiente imagen:
Despues de esto nos mostrará una ventana como la siguiente:
En mi caso se la dirección por que anteriormente copie la dirección en la que me compila el servicio, que se realizo en la parte 1
A continuación vamos a presentar las vistas
AddApplication.cshtml
@{
ViewBag.Title = "Application";
}
<script src="~/js/DataList/Application.js"></script>
<script src="~/js/Controls/CustomControls.js"></script>
<div class="container-fluid">
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">
@ViewBag.Title <small>Add/Edit Applications</small>
</h1>
<ol class="breadcrumb">
<li class="active">
<i class="fa fa-dashboard"></i> @ViewBag.Title
</li>
</ol>
</div>
</div>
<div class="row">
<div class="panel-body">
<ul class="nav nav-tabs">
<li class="active"><a href="#add" data-toggle="tab">Agregar Aplicación</a></li>
<li><a href="#edit" data-toggle="tab">Editar Aplicación</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active" id="add">
<div class="col-lg-12">
<br />
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Nombre Aplicación:</label>
<input id="tbxiAppName" type="text" class="form-control input-sm">
</div>
<div class="col-lg-12">
<button type="button" class="btn btn-sm" data-dismiss="modal" id="addApp">Agregar</button>
</div>
</div>
</div>
<div class="tab-pane fade in" id="edit">
<br />
<div class="col-lg-12">
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Seleccionar Aplicación:</label>
<div id="controlApp">
<select class="form-control input-sm" id="AppsInput">
<option>Seleccione una Opcion</option>
</select>
</div>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Nombre Aplicación:</label>
<input id="tbxAppName" type="text" class="form-control input-sm">
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Llave de Api:</label>
<input id="tbxAppKey" type="text" class="form-control input-sm copyApiKey">
<button type="button" class="btn btn-sm" id="copyApiKey">Copiar LLave de Api</button>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Llave Publica:</label>
<input id="tbxPublicKey" type="text" class="form-control input-sm copyPublicKey">
<button type="button" class="btn btn-sm" id="copyPublicKey">Copiar LLave Publica</button>
</div>
<div class="col-lg-12">
<button type="button" class="btn btn-sm" data-dismiss="modal" id="editApp">Editar</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var applications = [];
applications = getApplications();
$('#controlApp').html(DropDownForm("Apps", parseApplications(applications), ""));
$('#addApp').on('click', function () {
request = {};
request.ApplicationName = $('#tbxiAppName').val();
addApplication(request);
applications = getApplications();
$('#controlApp').html(DropDownForm("Apps", parseApplications(applications), ""));
$('#AppsInput').on('change', function () {
SetData(applications, $('#AppsInput').val());
});
});
$('#editApp').on('click', function () {
request = {};
request.ApplicationName = $('#tbxAppName').val();
request.CveApplication = $('#AppsInput').val();
udpApplication(request);
});
$('#AppsInput').on('change', function () {
SetData(applications, $('#AppsInput').val());
});
function parseApplications(data) {
var NewData = [];
NewData.push({ "id": 0, "name": "Selecciona una Opcion" });
$.each(data, function (key, value) {
NewData.push({ "id": value.CveApplication, "name": value.ApplicationName });
});
return NewData;
}
function SetData(data, valueSelect) {
$.each(data, function (key, value) {
if (parseInt(valueSelect) == value.CveApplication) {
$('#tbxAppName').val(value.ApplicationName);
$('#tbxAppKey').val(value.ApiKey);
$('#tbxAppKey').prop("disabled", true);
$('#tbxPublicKey').val(value.PublicKey);
$('#tbxPublicKey').prop("disabled", true);
}
});
}
$('#copyApiKey').on('click', function () {
$('#tbxAppKey').prop("disabled", false);
copy('.copyApiKey');
$('#tbxAppKey').prop("disabled", true);
});
$('#copyPublicKey').on('click', function () {
$('#tbxPublicKey').prop("disabled", false);
copy('.copyPublicKey');
$('#tbxPublicKey').prop("disabled", true);
});
function copy(id) {
var copyTextarea = document.querySelector(id);
copyTextarea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
}
</script>
AddResource.cshtml
@{
ViewBag.Title = "Resource";
}
<script src="~/js/DataList/Application.js"></script>
<script src="~/js/DataList/Resource.js"></script>
<script src="~/js/Controls/CustomControls.js"></script>
<div class="container-fluid">
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">
@ViewBag.Title <small>Add/Edit Resources</small>
</h1>
<ol class="breadcrumb">
<li class="active">
<i class="fa fa-dashboard"></i> @ViewBag.Title
</li>
</ol>
</div>
</div>
<div class="row">
<div class="panel-body">
<ul class="nav nav-tabs">
<li class="active"><a href="#add" data-toggle="tab">Agregar Recurso</a></li>
<li><a href="#edit" data-toggle="tab">Editar Recurso</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active" id="add">
<div class="col-lg-12">
<br />
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Seleccionar Aplicación:</label>
<div id="controlApp">
<select class="form-control input-sm" id="AppsInput">
<option>Seleccione una Opcion</option>
</select>
</div>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Nombre de Mensaje:</label>
<input id="tbxMessName" type="text" class="form-control input-sm">
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Datos de Entrada:</label>
<textarea id="tbxRequest" type="text" class="form-control input-sm" style="height:200px;"></textarea>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Datos de Salida:</label>
<textarea id="tbxResponse" type="text" class="form-control input-sm" style="height:200px;"></textarea>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Nombre Recurso:</label>
<input id="tbxResourceName" type="text" class="form-control input-sm">
</div>
<div class="col-lg-12">
<button type="button" class="btn btn-sm" data-dismiss="modal" id="addMess">Agregar</button>
</div>
</div>
</div>
<div class="tab-pane fade in" id="edit">
<br />
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Seleccionar Aplicación:</label>
<div id="controlIApp">
<select class="form-control input-sm" id="IAppsInput">
<option>Seleccione una Opcion</option>
</select>
</div>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Seleccionar Mensaje:</label>
<div id="controlMess">
<select class="form-control input-sm" id="IMessInput">
<option>Seleccione una Opcion</option>
</select>
</div>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Nombre de Mensaje:</label>
<input id="tbxIMessName" type="text" class="form-control input-sm">
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Nombre Recurso:</label>
<input id="tbxIResourceName" type="text" class="form-control input-sm">
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Datos de Entrada:</label>
<textarea id="tbxIRequest" type="text" class="form-control input-sm" style="height:200px;"></textarea>
</div>
<div class="form-group col-lg-6">
<label class="col-md-5 control-label">Datos de Salida:</label>
<textarea id="tbxIResponse" type="text" class="form-control input-sm" style="height:200px;"></textarea>
</div>
<div class="col-lg-12">
<button type="button" class="btn btn-sm" data-dismiss="modal" id="EditMess">Editar</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var applications = [];
var messages = [];
applications = getApplications();
$('#controlApp').html(DropDownForm("Apps", parseApplications(applications), ""));
$('#controlIApp').html(DropDownForm("IApps", parseApplications(applications), ""));
$('#IAppsInput').on('change', function () {
messages = getResources($('#IAppsInput option:selected').text());
$('#controlMess').html(DropDownForm("IMess", parseMessages(messages), ""))
$('#IMessInput').on('change', function () {
SetData(messages, $('#IMessInput').val());
});
});
$('#addMess').on('click', function () {
request = {};
request.CveApplication = $('#AppsInput').val();
request.MessageName = $('#tbxMessName').val();
request.ResourceName = $('#tbxResourceName').val();
request.RequestData = $('#tbxRequest').val();
request.ResponseData = $('#tbxResponse').val();
addResource(request);
clearFormAddMess();
});
$('#EditMess').on('click', function () {
request = {};
request.CveApplication = $('#IAppsInput').val();
request.CveMessage = $('#IMessInput').val();
request.MessageName = $('#tbxIMessName').val();
request.ResourceName = $('#tbxIResourceName').val();
request.RequestData = $('#tbxIRequest').val();
request.ResponseData = $('#tbxIResponse').val();
udpResource(request);
});
function clearFormAddMess() {
$('#AppsInput').val('0');
$('#tbxMessName').val('');
$('#tbxResourceName').val('');
$('#tbxRequest').val('');
$('#tbxResponse').val('');
}
function SetData(data, valueSelect) {
$.each(data, function (key, value) {
if (parseInt(valueSelect) == value.CveMessage) {
$('#tbxIMessName').val(value.MessageName);
$('#tbxIResourceName').val(value.ResourceName);
$('#tbxIRequest').val(value.RequestData);
$('#tbxIResponse').val(value.ResponseData);
}
});
}
function parseMessages(data) {
var NewData = [];
NewData.push({ "id": 0, "name": "Selecciona una Opcion" });
$.each(data, function (key, value) {
NewData.push({ "id": value.CveMessage, "name": value.MessageName });
});
return NewData;
}
function parseApplications(data) {
var NewData = [];
NewData.push({ "id": 0, "name": "Selecciona una Opcion" });
$.each(data, function (key, value) {
NewData.push({ "id": value.CveApplication, "name": value.ApplicationName });
});
return NewData;
}
</script>
_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>@ViewBag.Title</title>
<link rel="stylesheet" href="~/css/bootstrap.css" type="text/css" />
<link rel="stylesheet" href="~/css/sb-admin.css" type="text/css" />
<link rel="stylesheet" href="~/css/font-awesome.css" type="text/css" />
<script src="~/js/jquery.js"></script>
<script src="~/js/bootstrap.js"></script>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div id="wrapper">
<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="index.html">SB Admin</a>
</div>
<!-- Top Menu Items -->
<ul class="nav navbar-right top-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-envelope"></i> <b class="caret"></b></a>
<ul class="dropdown-menu message-dropdown">
<li class="message-preview">
<a href="#">
<div class="media">
<span class="pull-left">
<img class="media-object" src="http://placehold.it/50x50" alt="">
</span>
<div class="media-body">
<h5 class="media-heading">
<strong>John Smith</strong>
</h5>
<p class="small text-muted"><i class="fa fa-clock-o"></i> Yesterday at 4:32 PM</p>
<p>Lorem ipsum dolor sit amet, consectetur...</p>
</div>
</div>
</a>
</li>
<li class="message-preview">
<a href="#">
<div class="media">
<span class="pull-left">
<img class="media-object" src="http://placehold.it/50x50" alt="">
</span>
<div class="media-body">
<h5 class="media-heading">
<strong>John Smith</strong>
</h5>
<p class="small text-muted"><i class="fa fa-clock-o"></i> Yesterday at 4:32 PM</p>
<p>Lorem ipsum dolor sit amet, consectetur...</p>
</div>
</div>
</a>
</li>
<li class="message-preview">
<a href="#">
<div class="media">
<span class="pull-left">
<img class="media-object" src="http://placehold.it/50x50" alt="">
</span>
<div class="media-body">
<h5 class="media-heading">
<strong>John Smith</strong>
</h5>
<p class="small text-muted"><i class="fa fa-clock-o"></i> Yesterday at 4:32 PM</p>
<p>Lorem ipsum dolor sit amet, consectetur...</p>
</div>
</div>
</a>
</li>
<li class="message-footer">
<a href="#">Read All New Messages</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-bell"></i> <b class="caret"></b></a>
<ul class="dropdown-menu alert-dropdown">
<li>
<a href="#">Alert Name <span class="label label-default">Alert Badge</span></a>
</li>
<li>
<a href="#">Alert Name <span class="label label-primary">Alert Badge</span></a>
</li>
<li>
<a href="#">Alert Name <span class="label label-success">Alert Badge</span></a>
</li>
<li>
<a href="#">Alert Name <span class="label label-info">Alert Badge</span></a>
</li>
<li>
<a href="#">Alert Name <span class="label label-warning">Alert Badge</span></a>
</li>
<li>
<a href="#">Alert Name <span class="label label-danger">Alert Badge</span></a>
</li>
<li class="divider"></li>
<li>
<a href="#">View All</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-user"></i> John Smith <b class="caret"></b></a>
<ul class="dropdown-menu">
<li>
<a href="#"><i class="fa fa-fw fa-user"></i> Profile</a>
</li>
<li>
<a href="#"><i class="fa fa-fw fa-envelope"></i> Inbox</a>
</li>
<li>
<a href="#"><i class="fa fa-fw fa-gear"></i> Settings</a>
</li>
<li class="divider"></li>
<li>
<a href="#"><i class="fa fa-fw fa-power-off"></i> Log Out</a>
</li>
</ul>
</li>
</ul>
<!-- Sidebar Menu Items - These collapse to the responsive navigation menu on small screens -->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav side-nav">
<li class="active">
<a href="@Url.Content("~/Home")"><i class="fa fa-fw fa-dashboard"></i> Dashboard</a>
</li>
<li>
<a href="@Url.Content("~/Home?Source=AddApplication")"><i class="fa fa-fw fa-bar-chart-o"></i> Agregar Aplicación</a>
</li>
<li>
<a href="@Url.Content("~/Home?Source=AddResource")"><i class="fa fa-fw fa-table"></i> Agregar Recurso</a>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</nav>
<div id="page-wrapper">
@RenderBody()
</div>
<!-- /#page-wrapper -->
</div>
<a data-toggle="modal" href="#modalDefault" style="display: none;" id="WindowModal">Modal - Default</a>
<div class="modal fade" id="modalDefault" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="windowTitle"></h4>
</div>
<div class="modal-body" id="windowMessage">
</div>
<div class="modal-footer">
<button type="button" style="display: none;" class="btn btn-sm" data-dismiss="modal" id="messageclose">Aceptar</button>
<button type="button" class="btn btn-sm" data-dismiss="modal">Cerrar</button>
</div>
</div>
</div>
</div>
<!-- /#wrapper -->
</body>
</html>
Luego del monton de codigo que se ha escrito vamos a ver como quedaron nuestras vistas, ya funcionales, si seguimos todos los pasos del código anterior:
En la imagen anterior podemos ver como quedo nuestra pagina en la pestaña de Agregar Aplicación.
En la imagen anterior vemos como al seleccionar una aplicación, nos muestra el Nombre de la Aplicación, nos da una Llave de Api y una Llave Publica para cifrar algún dato ya veremos como lo usaremos en el WebHook en la Parte 3 de este articulo, veamos como se ven las secciones de Recursos.
Esta parte, es la más divertida e interesante, dado que creamos un mensaje que se ancla a una aplicación, este mensaje tiene Información de entrada e Información de Salida, aquí quiero despejar un poco esta información, la información de entrada y de salida no es otra cosa que una clase en C# que va a permitir validar la estructura del JSON que sea enviado al WebHook, así como la información de Salida, como podemos ver en la siguiente imagen de la pagina de edición:
Con esto tenemos el Manager Finalizado para efectos de jugar con nuestro WebHook, con esto se finaliza la segunda parte de esta entrega les dejo la liga de descarga y nos vemos en la próxima entrega donde crearemos el WebHook la parte que se va a dedicar a recibir datos y a obtenerlos para conectar otras aplicaciones.
(Download)
WebHooks ¿Qué son? ¿Como hago Uno? (Parte 1)
YDesde hace algun tiempo vengo viendo que se agregan nuevas terminologías al diccionario informatico, desde hace algun tiempo la palabra WebHook ha tomado popularidad, por ahí en wikipedia dice que este termino fue inventado apartir de la programación Hook o funcionalidad Hook (En mis tiempos se llamaban Callbacks), si, si sabes JavaScript Ajax o Jquery Asincrono, si, has usado programación Hook, la diferencia aqui radica en que la llamada se hará hacia un WebService, en mi caso, me toco conectarme a uno para intercambio de información entre transacciones bancarias, lo cual es por demás interesante; Pero… Quiero hacer algo generico, que me soporte cualquier tipo de información y yo despues haga lo que quiera con ella, imaginate un repositorio de BigData con diferentes informaciónes en un mismo usuario, donde puedas programar tus entradas y salidas, para un webhook esto seria divertido y el truco está en como crear una configuración
Para este ejemplo crearemos lo siguiente:
- Diseño de Base de Datos
- WCF Configurado como REST (Este servicio contendrá)
- Metodo Generador de Llaves Publica y Privada
- Metodo Generador de ApiKey
- Metodo Al que se enviará la Información
- Metodo de Creación de Clases de Trasnporte
- Metodo de Asignación del Tipo de Mensaje
- Metodo de Obtención de Mensaje
- Pagina de Configuración Tipo MVC (Este contendrá)
- Pagina de Generación de ApiKey y Llave Publica
- Configuración para crear Objetos y Reservar el Tipo de Objeto dentro del WebHook
- Asignación de Tipo de Mensaje
- Ver Lista de Mensajes Enviados
Comenzaremos definiendo el Diseño de Base de Datos que queda como en la siguiente imagen:
Como podemos ver en la imagen anterior el diseño no es nada complicado, aqui jugaremos con pedasos de codigo que ya han sido expuestos con anterioridad y algunos nuevos que crearemos gracias a algunas investigaciones que se han realizado a lo largo de estos meses.
Continuaremos creando una solución nueva a la cual llamaremos WHService, ya saben abrimos nuestro Visual Studio como en la siguiente imagen:
Y una vez creado, crearemos el siguiente arbol de archivos:
Los lectores que llevan tiempo visitando este blog y ha seguido los modos de construcción del diseño orientado a dominio, ya estarán familiarizados con este modelo, comenzaremos poniendo las clases de la Carpeta Domain con las siguientes clases:
- Application.cs
- Data.cs
- Result.cs
- Message.cs
Contenido de Application.cs
public class Application
{
public int CveApplication { get; set; }
public string ApplicationName { get; set; }
public string ApiKey { get; set; }
public string PrivateKey { get; set; }
public string PublicKey { get; set; }
}
Contenido de Data.cs
public class Data
{
public int CveData { get; set; }
public int CveApplication { get; set; }
public int CveMessage { get; set; }
public string Info { get; set; }
public DateTime DateInsert { get; set; }
public bool IsRead { get; set; }
}
Contenido de Result.cs
public class Result
{
public bool Success { get; set; }
public string Erros { get; set; }
public int ErrorNumber { get; set; }
}
Codigo de Message.cs
public class Message
{
public int CveMessage { get; set; }
public string MessageName { get; set; }
public string ResourceName { get; set; }
public string RequestData { get; set; }
public string ResponseData { get; set; }
public int CveApplication { get; set; }
}
Después de haber realizado la creación de las clases de dominio, pasaremos a crear las clases de Acceso a Datos que se encuentran en la carpeta DataAccess
- ApplicationRepository.cs
- DataRepository.cs
- MessageRepository.cs
A continuación el Código de cada una:
Coddigo de ApplicationRepository.cs
public class ApplicationRepository
{
public static List<Domain.Application> GetApplications()
{
List<Domain.Application> ass = new List<Domain.Application>();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT [CveApplication],[ApplicationName],[ApiKey],[PrivateKey],[PublicKey]
FROM [Application]";
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
ass.Add(PopulateData(r));
}
}
}
}
return ass;
}
public static Domain.Application GetApplicationByName(string ApplicationName)
{
Domain.Application a = null;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT [CveApplication],[ApplicationName],[ApiKey],[PrivateKey],[PublicKey]
FROM [Application]
WHERE ApplicationName=@ApplicationName";
cmd.Parameters.AddWithValue("@ApplicationName", ApplicationName);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
a = PopulateData(r);
}
}
}
}
return a;
}
public static Domain.Application GetApplicationById(int CveApplication)
{
Domain.Application a = null;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT [CveApplication],[ApplicationName],[ApiKey],[PrivateKey],[PublicKey]
FROM [Application]
WHERE CveApplication=@CveApplication";
cmd.Parameters.AddWithValue("@CveApplication", CveApplication);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
a = PopulateData(r);
}
}
}
}
return a;
}
public static Domain.Application PopulateData(IDataReader r)
{
Domain.Application a = new Domain.Application();
a.CveApplication = Convert.ToInt32(r["CveApplication"]);
a.ApplicationName = Convert.ToString(r["ApplicationName"]);
a.ApiKey = Convert.ToString(r["ApiKey"]);
a.PrivateKey = Convert.ToString(r["PrivateKey"]);
a.PublicKey = Convert.ToString(r["PublicKey"]);
return a;
}
public static void Add(Domain.Application a)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"INSERT INTO [dbo].[Application] ([ApplicationName],[ApiKey],[PrivateKey],[PublicKey])
VALUES (@ApplicationName,@ApiKey,@PrivateKey,@PublicKey);
SELECT SCOPE_IDENTITY() AS CveApplication";
cmd.Parameters.AddWithValue("@ApplicationName", a.ApplicationName);
cmd.Parameters.AddWithValue("@ApiKey", a.ApiKey);
cmd.Parameters.AddWithValue("@PrivateKey", a.PrivateKey);
cmd.Parameters.AddWithValue("@PublicKey", a.PublicKey);
a.CveApplication = Convert.ToInt32(cmd.ExecuteScalar());
}
}
}
public static void Update(Domain.Application a)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"UPDATE [dbo].[Application]
SET [ApplicationName] = @ApplicationName
,[ApiKey] = @ApiKey
,[PrivateKey] = @PrivateKey
,[PublicKey] = @PublicKey
WHERE CveApplication=@CveApplication";
cmd.Parameters.AddWithValue("@ApplicationName", a.ApplicationName);
cmd.Parameters.AddWithValue("@ApiKey", a.ApiKey);
cmd.Parameters.AddWithValue("@PrivateKey", a.PrivateKey);
cmd.Parameters.AddWithValue("@PublicKey", a.PublicKey);
cmd.Parameters.AddWithValue("@CveApplication", a.CveApplication);
cmd.ExecuteNonQuery();
}
}
}
public static long Delete(int a)
{
long RecordId = 0;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"DELETE FROM [dbo].[Application] WHERE CveApplication=@CveApplication";
cmd.Parameters.AddWithValue("@CveApplication", a);
cmd.ExecuteNonQuery();
}
}
return RecordId;
}
}
Código de Data.cs
public class DataRepository
{
public static Domain.Data GetLastDatabyApplication(string ApplicationName)
{
Domain.Data ds = new Domain.Data();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT TOP 1 d.[CveData],d.[CveApplication],d.[CveMessage],d.[Info],d.[DateInsert],d.[IsRead]
FROM [Data] d
JOIN [Application] a ON a.CveApplication=d.CveApplication
JOIN [Message] m ON m.CveMessage = d.CveMessage
WHERE a.ApplicationName=@ApplicationName
ORDER BY DateInsert";
cmd.Parameters.AddWithValue("@ApplicationName", ApplicationName);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
ds = Populate(r);
}
}
}
}
return ds;
}
public static List<Domain.Data> GetDataByApplicationName(string ApplicationName)
{
List<Domain.Data> ds = new List<Domain.Data>();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT d.[CveData],d.[CveApplication],d.[CveMessage],d.[Info],d.[DateInsert],d.[IsRead]
FROM [Data] d
JOIN [Application] a ON a.CveApplication=d.CveApplication
JOIN [Message] m ON m.CveMessage = d.CveMessage
WHERE a.ApplicationName=@ApplicationName
ORDER BY DateInsert";
cmd.Parameters.AddWithValue("@ApplicationName", ApplicationName);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
ds.Add(Populate(r));
}
}
}
}
return ds;
}
public static Domain.Data GetDataById(int CveData)
{
Domain.Data ds = new Domain.Data();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT d.[CveData],d.[CveApplication],d.[CveMessage],d.[Info],d.[DateInsert],d.[IsRead]
FROM [Data] d
JOIN [Application] a ON a.CveApplication=d.CveApplication
JOIN [Message] m ON m.CveMessage = d.CveMessage
WHERE d.CveData=@CveData";
cmd.Parameters.AddWithValue("@CveData", CveData);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
ds = Populate(r);
}
}
}
}
return ds;
}
public static Domain.Data Populate(IDataReader r)
{
Domain.Data d = new Domain.Data();
d.CveApplication = Convert.ToInt32(r["CveApplication"]);
d.CveData = Convert.ToInt32(r["CveData"]);
d.CveMessage = Convert.ToInt32(r["CveMessage"]);
d.DateInsert = Convert.ToDateTime(r["DateInsert"]);
d.Info = Convert.ToString(r["Info"]);
d.IsRead = Convert.ToBoolean(r["IsRead"]);
return d;
}
public static void Add(Domain.Data d)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"INSERT INTO [dbo].[Data] ([CveApplication],[CveMessage],[Info],[DateInsert],[IsRead])
VALUES (@CveApplication,@CveMessage,@Info,@DateInsert,@IsRead);
SELECT SCOPE_IDENTITY() AS CveData";
cmd.Parameters.AddWithValue("@CveApplication", d.CveApplication);
cmd.Parameters.AddWithValue("@CveMessage", d.CveMessage);
cmd.Parameters.AddWithValue("@Info", d.Info);
cmd.Parameters.AddWithValue("@DateInsert", d.DateInsert);
cmd.Parameters.AddWithValue("@IsRead", d.IsRead);
d.CveData = Convert.ToInt32(cmd.ExecuteScalar());
}
}
}
public static void Update(Domain.Data d)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"UPDATE [dbo].[Data]
SET [CveApplication] = @CveApplication
,[CveMessage] = @CveMessage
,[Info] = @Info
,[DateInsert] = @DateInsert
,[IsRead] = @IsRead
WHERE CveData=@CveData";
cmd.Parameters.AddWithValue("@CveApplication", d.CveApplication);
cmd.Parameters.AddWithValue("@CveMessage", d.CveMessage);
cmd.Parameters.AddWithValue("@Info", d.Info);
cmd.Parameters.AddWithValue("@DateInsert", d.DateInsert);
cmd.Parameters.AddWithValue("@IsRead", d.IsRead);
cmd.Parameters.AddWithValue("@CveData", d.CveData);
cmd.ExecuteNonQuery();
}
}
}
public static long Delete(int d)
{
long RecordId = 0;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"DELETE FROM [dbo].[Data] WHERE CveData=@CveData";
cmd.Parameters.AddWithValue("@CveData", d);
cmd.ExecuteNonQuery();
}
}
return RecordId;
}
}
Codigo de MessageRepository.cs
public static class MessageRepository
{
public static List<Domain.Message> GetMessagesByApplicationName(string ApplicationName)
{
List<Domain.Message> ms = new List<Domain.Message>();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT m.[CveMessage],m.[MessageName],m.[ResourceName],m.[RequestData],m.[ResponseData],m.[CveApplication]
FROM [Message] m
JOIN [Application] a ON a.CveApplication=m.CveApplication
WHERE a.ApplicationName=@ApplicationName";
cmd.Parameters.AddWithValue("@AUTHORIZATION_ID", ApplicationName);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
ms.Add(Populate(r));
}
}
}
}
return ms;
}
public static Domain.Message GetMessagesById(int CveMessage)
{
Domain.Message m = null;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT m.[CveMessage],m.[MessageName],m.[ResourceName],m.[RequestData],m.[ResponseData],m.[CveApplication]
FROM [Message] m WHERE CveMessage=@CveMessage";
cmd.Parameters.AddWithValue("@CveMessage", CveMessage);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
m = Populate(r);
}
}
}
}
return m;
}
public static Domain.Message GetMessagesByResource(string ResourceName)
{
Domain.Message m = null;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"SELECT m.[CveMessage],m.[MessageName],m.[ResourceName],m.[RequestData],m.[ResponseData],m.[CveApplication]
FROM [Message] m WHERE ResourceName=@ResourceName";
cmd.Parameters.AddWithValue("@ResourceName", ResourceName);
using (IDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
m = Populate(r);
}
}
}
}
return m;
}
public static Domain.Message Populate(IDataReader r)
{
Domain.Message m = new Domain.Message();
m.CveApplication = Convert.ToInt32(r["CveApplication"]);
m.CveMessage = Convert.ToInt32(r["CveMessage"]);
m.MessageName = Convert.ToString(r["MessageName"]);
m.RequestData = Convert.ToString(r["RequestData"]);
m.ResourceName = Convert.ToString(r["ResourceName"]);
m.ResponseData = Convert.ToString(r["ResponseData"]);
return m;
}
public static void Add(Domain.Message m)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"INSERT INTO [dbo].[Message] ([MessageName],[ResourceName],[RequestData],[ResponseData],[CveApplication])
VALUES (@MessageName,@ResourceName,@RequestData,@ResponseData,@CveApplication);
SELECT SCOPE_IDENTITY() AS RECORD_ID";
cmd.Parameters.AddWithValue("@MessageName", m.MessageName);
cmd.Parameters.AddWithValue("@ResourceName", m.ResourceName);
cmd.Parameters.AddWithValue("@RequestData", m.RequestData);
cmd.Parameters.AddWithValue("@ResponseData", m.ResponseData);
cmd.Parameters.AddWithValue("@CveApplication", m.CveApplication);
m.CveMessage = Convert.ToInt32(cmd.ExecuteScalar());
}
}
}
public static void Update(Domain.Message m)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"UPDATE [dbo].[Message]
SET [MessageName] = @MessageName
,[ResourceName] = @ResourceName
,[RequestData] = @RequestData
,[ResponseData] = @ResponseData
,[CveApplication] = @CveApplication
WHERE CveMessage=@CveMessage";
cmd.Parameters.AddWithValue("@MessageName", m.MessageName);
cmd.Parameters.AddWithValue("@ResourceName", m.ResourceName);
cmd.Parameters.AddWithValue("@RequestData", m.RequestData);
cmd.Parameters.AddWithValue("@ResponseData", m.ResponseData);
cmd.Parameters.AddWithValue("@CveApplication", m.CveApplication);
cmd.Parameters.AddWithValue("@CveMessage", m.CveMessage);
cmd.ExecuteNonQuery();
}
}
}
public static long Delete(int m)
{
long RecordId = 0;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Context"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = @"DELETE FROM [dbo].[Message] WHERE CveMessage=@CveMessage";
cmd.Parameters.AddWithValue("@CveMessage", m);
cmd.ExecuteNonQuery();
}
}
return RecordId;
}
}
Una vez realizado esta sección comenzaremos con la parte interesante que es la creación de nuestra aplicación, como les dije antes este será un webhook en el que podremos agregar cualquier tipo de dato, lo cierto es que vamos a crear un manager para poder registrar nuestros webhooks y nuestros tipos de mensajes independientemente de que estos se manejen como json del tipo string, lo correcto es que validemos esta porción de código, lo cual nos llevará al mundo oscuro del CodeDom, pero veamos que tan divertido nos sale esto, para estos efectos se supone que tendremos llaves publicas y privadas que se generarán a partir de una aplicación, asi como una secret key que no es otra cosa que un bonito guid asi que comencemos en la carpeta Helpers dentro de la Carpeta Application, crearemos los siguinetes archivos
CryptoHelper.cs y CustomCompiler.cs
public partial class CryptoHelper
{
public RSACryptoServiceProvider RSAService { get; set; }
public CryptoHelper()
{
this.RSAService = new RSACryptoServiceProvider(2048);
}
public byte[] CreatePublicKey()
{
string xmlPublicKey = this.RSAService.ToXmlString(false);
return Encoding.ASCII.GetBytes(xmlPublicKey);
}
public byte[] CreatePrivateKey()
{
string xmlPrivateKey = this.RSAService.ToXmlString(true);
return Encoding.ASCII.GetBytes(xmlPrivateKey);
}
public static byte[] EncryptText(string text, string xmlPublicKey)
{
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048);
RSA.FromXmlString(xmlPublicKey);
byte[] encryptedData = RSA.Encrypt(Encoding.ASCII.GetBytes(text), false);
return encryptedData;
}
public static byte[] DecryptText(string encrypttext, string xmlPrivateKey)
{
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048);
RSA.FromXmlString(xmlPrivateKey);
byte[] decryptData = RSA.Decrypt(Convert.FromBase64String(encrypttext), false);
return decryptData;
}
}
Como podemos ver CryptoHelper es una clase que esta dedicada a crear las llaves publicas y privadas, además tiene los metodos de cifrado y descifrado, estas que ocuparan la parte de creación de llaves para el Manager y previamente para cifrar y des cifrar en el webhook.
Veamos el código de CustomCompiler.cs
public static class CustomCompiler
{
public static Result EvalCode(string sCSCode)
{
Result r = new Result();
CodeDomProvider c = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("system.dll");
cp.ReferencedAssemblies.Add("system.xml.dll");
cp.ReferencedAssemblies.Add("system.data.dll");
cp.ReferencedAssemblies.Add("system.windows.forms.dll");
cp.ReferencedAssemblies.Add("system.drawing.dll");
cp.CompilerOptions = "/t:library";
cp.GenerateInMemory = true;
StringBuilder sb = new StringBuilder(""); //Texto del codigo
sb.Append("using System;\n");
sb.Append("using System.Xml;\n");
sb.Append("using System.Data;\n");
sb.Append("using System.Data.SqlClient;\n");
sb.Append("using System.Windows.Forms;\n");
sb.Append("using System.Drawing;\n");
sb.Append("namespace CustomSpace{ \n");
sb.Append(sCSCode);
sb.Append("}\n");
CompilerResults cr = c.CompileAssemblyFromSource(cp, sb.ToString()); //icc.CompileAssemblyFromSource(cp, sb.ToString());
if (cr.Errors.Count > 0)
{
r.Erros = cr.Errors[0].ErrorText;
r.ErrorNumber = cr.Errors.Count;
r.Success = false;
return r;
}
r.Success = true;
return r;
}
public static Result ExecuteData(string sCSCode)
{
Result r = new Result();
CodeDomProvider c = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("system.dll");
cp.ReferencedAssemblies.Add("system.xml.dll");
cp.ReferencedAssemblies.Add("system.data.dll");
cp.ReferencedAssemblies.Add("system.windows.forms.dll");
cp.ReferencedAssemblies.Add("system.drawing.dll");
cp.CompilerOptions = "/t:library";
cp.GenerateInMemory = true;
StringBuilder sb = new StringBuilder(""); //Texto del codigo
sb.Append("using System;\n");
sb.Append("using System.Xml;\n");
sb.Append("using System.Data;\n");
sb.Append("using System.Data.SqlClient;\n");
sb.Append("using System.Windows.Forms;\n");
sb.Append("using System.Drawing;\n");
sb.Append("namespace CustomSpace{ \n");
sb.Append(sCSCode);
sb.Append("}\n");
CompilerResults cr = c.CompileAssemblyFromSource(cp, sb.ToString()); //icc.CompileAssemblyFromSource(cp, sb.ToString());
if (cr.Errors.Count > 0)
{
r.Erros = cr.Errors[0].ErrorText;
r.ErrorNumber = cr.Errors.Count;
r.Success = false;
return r;
}
System.Reflection.Assembly a = cr.CompiledAssembly;
object o = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");
Type t = o.GetType();
#region Preparación de Implementación y Validación del Codigo
//Generar Region despues
#endregion
r.Success = true;
return r;
}
}
Estas clases son las principales, CustomCompiler permite crear y validar código en tiempo real, aun falta la región de como lo haremos pero creo que veremos esa opción en el próximo capitulo, por ahora terminaremos el servicio del Manager, así que a continuación crearemos el archivo AppManager.cs que será el encargado de generar los métodos que se expondrán en el servicio:
public class AppManager : IDisposable
{
private readonly IMapper _mapper;
public AppManager()
{
this._mapper = AppProfile.ConfigMaps();
}
#region Application
public GetApplicationsResponse GetApplications(GetApplicationRequest request)
{
GetApplicationsResponse response = new GetApplicationsResponse();
List<Domain.Application> a = ApplicationRepository.GetApplications();
response.Applications = this._mapper.Map<List<Domain.Application>, List<GetApplicationResponse>>(a);
return response;
}
public GetApplicationResponse GetApplication(GetApplicationRequest request)
{
GetApplicationResponse response = new GetApplicationResponse();
Domain.Application a = ApplicationRepository.GetApplicationByName(request.ApplicationName);
response = this._mapper.Map<Domain.Application, GetApplicationResponse>(a);
return response;
}
public AddApplicationResponse AddApplication(AddAplicationRequest request)
{
AddApplicationResponse response = new AddApplicationResponse();
Domain.Application a = new Domain.Application();
a.ApplicationName = request.ApplicationName;
a.ApiKey = Guid.NewGuid().ToString();
CryptoHelper cipher = new CryptoHelper();
a.PrivateKey = Convert.ToBase64String(cipher.CreatePrivateKey());
a.PublicKey = Convert.ToBase64String(cipher.CreatePublicKey());
ApplicationRepository.Add(a);
return response;
}
public UpdateApplicationResponse UpdateApplication(UpdateApplicationRequest request)
{
UpdateApplicationResponse response = new UpdateApplicationResponse();
Domain.Application a = ApplicationRepository.GetApplicationById(request.CveApplication);
a.ApplicationName = request.ApplicationName;
ApplicationRepository.Update(a);
return response;
}
public DeleteApplicationResponse DeleteApplication(DeleteApplicationRequest request)
{
DeleteApplicationResponse response = new DeleteApplicationResponse();
ApplicationRepository.Delete(request.CveApplication);
return response;
}
#endregion
#region Message
public GetMessagesResponse GetMessages(GetMessageRequest request)
{
GetMessagesResponse response = new GetMessagesResponse();
List<Domain.Message> m = MessageRepository.GetMessagesByApplicationName(request.ApplicationName);
response.Messages = this._mapper.Map<List<Domain.Message>, List<GetMessageResponse>>(m);
return response;
}
public GetMessageResponse GetMessage(GetMessageRequest request)
{
GetMessageResponse response = new GetMessageResponse();
Domain.Message m = MessageRepository.GetMessagesById(request.CveMessage);
response = this._mapper.Map<Domain.Message, GetMessageResponse>(m);
return response;
}
public AddMessageResponse AddMessage(AddMessageRequest request)
{
AddMessageResponse response = new AddMessageResponse();
Domain.Message m = new Domain.Message();
m.CveApplication = request.CveApplication;
m.MessageName = request.MessageName;
CustomCompiler.EvalCode(request.RequestData);
CustomCompiler.EvalCode(request.ResponseData);
m.RequestData = request.RequestData;
m.ResponseData = request.ResponseData;
m.ResourceName = request.ResourceName;
MessageRepository.Add(m);
return response;
}
public UpdateMessageResponse UpdateMessage(UpdateMessageRequest request)
{
UpdateMessageResponse response = new UpdateMessageResponse();
Domain.Message m = new Domain.Message();
m.CveApplication = request.CveApplication;
m.CveMessage = request.CveMessage;
m.MessageName = request.MessageName;
CustomCompiler.EvalCode(request.RequestData);
CustomCompiler.EvalCode(request.ResponseData);
m.RequestData = request.RequestData;
m.ResponseData = request.ResponseData;
m.ResourceName = request.ResourceName;
MessageRepository.Update(m);
return response;
}
public DeleteMessageResponse DeleteMessage(DeleteMessageRequest request)
{
DeleteMessageResponse response = new DeleteMessageResponse();
MessageRepository.Delete(request.CveMessage);
return response;
}
#endregion
#region Data
public GetDatasResponse GetDatas(GetDataRequest request)
{
GetDatasResponse response = new GetDatasResponse();
List<Domain.Data> d = DataRepository.GetDataByApplicationName(request.ApplicationName);
response.Datas = this._mapper.Map<List<Domain.Data>, List<GetDataResponse>>(d);
return response;
}
public GetDataResponse GetData(GetDataRequest request)
{
GetDataResponse response = new GetDataResponse();
Domain.Data d = DataRepository.GetDataById(request.CveData);
response = this._mapper.Map<Domain.Data, GetDataResponse>(d);
return response;
}
#endregion
public void Dispose()
{
}
}
Existen dos métodos que vale la pena mencionar, como lo son AddApplication donde podemos ver la creación del GUID y las Llaves Publica y Privada, y AddMessage en el cual request.RequestData y request.ResponseData contendrán clases de entrada y salida, (Si ya se complique de más la situación pero es divertido) que servirán para validar la información, OJO esto es un ejemplo, no recomiendo usar esto para exponer en sistemas dado que esto es permitir al usuario ejecutar cualquier código dentro de nuestro programa, apartir de esto crearemos los Accesos a los Repositorios o Contratos, lo cual es muy pero muy largo así que mejor dejo el ejemplo para que lo puedan descargar y pasaré a la ejecución del Servicio Manager veamos como queda:
Vamos a ver como quedan los Métodos AddApplication y GetApplication:
Y despues de insertado nuestro registro lo podemos obtener con el mismo nombre:
Con esto terminamos por hoy, a continuación les muestro el enlace para que puedan ver el proyecto completo
En la próxima parte, crearemos el la parte visual para jugar con nuestro sistema y terminar de crear la validación de código, ya verán que será divertido.
Datos en linea con WebSockets y C#
Actualmente me encuentro desarrollando un Dashboard y un sistema que trabaja con flujos de trabajo en tiempo real, lo cual me parecio un problema dado que tenia que hacer llamadas todo el tiempo al BackEnd para poder obtener los datos, generalmente usas un setInterval con llamada a Ajax asincrono, pero me daba vueltas la cabeza de la «talacha» que tendría que hacer.
Por lo tanto, me he visto obligado a hacer una pequeña investigación que maneja WebSockets, al utlizar MVC5 mi primera idea fue usar SignalR, pero luego recordé que todo lo que hago tiene que ser portable de alguna manera a otros lenguajes y se me paso, así que adiós SignalR, por lo tanto regresemos al origen de todo los Sockets, estos los puedo programar como me convenga en cualquier lenguaje, ya sea C#, Pyton, Java, C, lo cual me permite portar el servidor a donde lo necesite
Al comenzar esta tarea decidí hacer una consola, ya que después podré levantar un Servicio con el resultado, para esto tengo que crear un Socket – Asyncrono, el cuál me permitirá obtener los datos.
Para el caso del cliente, como su base es HTML en si mismo es portable a cualquier lenguaje web ya sea PHP, C# con MVC o JAVA con Groovy
Que debe hacer el programa Servidor:
- Crear un Programa de Consola
- Crear un SocketServer
- Habilitar la Autenticación del SocketCliente
- Obtener Datos de una Base de Datos
- Enviar los datos cada determinado tiempo
Vale la pena mencionar la siguiente pagina https://developer.mozilla.org/es/docs/WebSockets-840092-dup/Escribiendo_servidores_con_WebSocket que es muy importante para comprender el concepto e implementación de los sockets server
Despues de haber definido lo que hará el programa servidor, haremos manos a la obra.
Abriremos nuestro Visual Studio y pondremos el nombre de la solución como WebSocketServer como vemos en la siguiente imagen:
Luego vamos a ir definiendo el codigo del socket, al ser un socket asincrono, es necesario utilizar solamente los metodos BeginAccept y BeginReceive, lo cual nos permitirá hacerlo completamente asincrono.
Vamos a definir las siguientes Variables:
static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B13"; static List<Socket> lstSocket = new List<Socket>(); static Socket serverSocket; static byte[] b = new byte[1024];
En el codigo de Main Crearemos el Listen de nuestro Socket:
static void Main(string[] args)
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 9090));
serverSocket.Listen(128);
serverSocket.BeginAccept(null, 0, OnAccept, null);
Console.ReadKey();
}
Con el codigo anterior, estamos poniendo a escuchar el socket debemos mirar la seccion OnAccept que es el metodo al que hará referencia y que crearemos para continuar con la programación del Socket como veremos en el siguiente codigo:
private static void OnAccept(IAsyncResult result)
{
try
{
Socket client = null;
if (serverSocket != null && serverSocket.IsBound)
{
client = serverSocket.EndAccept(result);
}
if (client != null)
{
/* Handshaking and managing ClientSocket */
if (b == null) { b = new byte[1024]; }
client.BeginReceive(b, 0, b.Length, SocketFlags.None, OnReceive, client);
serverSocket.BeginAccept(null, 0, OnAccept, null);
}
}
catch (SocketException exception)
{
serverSocket.BeginAccept(null, 0, OnAccept, null);
}
}
En el codigo anterior hacemos tambien una llamada OnReceives en el cual tenemos el codigo principal de la información, como veremos a continuación:
</pre>
private static void OnReceive(IAsyncResult AR)
{
byte[] ResponseBytes = null;
Console.WriteLine("Recieving");
Socket client = (Socket)AR.AsyncState;
int dataLen = client.EndReceive(AR);
byte[] dataBuff = new byte[dataLen];
Array.Copy(b, dataBuff, dataLen);
string szReceived = Encoding.ASCII.GetString(dataBuff, 0, dataBuff.Length);
if (!lstSocket.Contains(client))
{
Console.WriteLine(szReceived);
lstSocket.Add(client);
#region ParseHeaders
var headers = new Dictionary<string, string="">();</string,>
string[] lines = szReceived.Split('\n');
foreach (string line in lines)
{
var tokens = line.Split(new char[] { ':' }, 2);
if (!string.IsNullOrWhiteSpace(line) && tokens.Length > 1)
{
headers[tokens[0]] = tokens[1].Trim();
}
}
string Key = headers["Sec-WebSocket-Key"];
#endregion
#region CreateResponseHeaders
string ResponseKey = AcceptKey(ref Key);
var response =
"HTTP/1.1 101 WebSocket Protocol Handshake" + Environment.NewLine +
"Upgrade: WebSocket" + Environment.NewLine +
"Connection: Upgrade" + Environment.NewLine +
"Sec-WebSocket-Origin: " + headers["Origin"] + Environment.NewLine +
"Sec-WebSocket-Accept: " + ResponseKey + Environment.NewLine +
"Sec-WebSocket-Location: ws://localhost:8080/websession" + Environment.NewLine +
Environment.NewLine;
#endregion
ResponseBytes = Encoding.ASCII.GetBytes(response);
client.Send(ResponseBytes);
}
else
{
Console.Write(szReceived + " = ");
#region DecodeMessage
string Path = DecodeMessage(dataBuff);
#endregion
#region CreateTimerToSendInfo
Timer t = new Timer();
t.Enabled = true;
t.Interval = 5000;
t.Start();
t.Elapsed += (sender, args) => ExecuteQuery(sender, client, Path);
#endregion
Console.WriteLine(Path);
string Data = DataInfo(Path);
ResponseBytes = EncodeMessageToSend(Data);
client.Send(ResponseBytes);
}
if (b != null) { b = new byte[1024]; }
try
{
client.BeginReceive(b, 0, b.Length, SocketFlags.None, OnReceive, client);
}
catch (Exception ex)
{
Console.WriteLine("Error");
}
}
<pre>
Lo más relevante que podemos encontrar el en metodo OnRecieve es que revisamos que el socket se encuentre en una lista de Sockets, esto nos permite identificar cada conexión, esto nos da dos opciones cuando nos envian Headers o Datos, los Headers siempre se mandarán en la primera conexión, todo lo demás para nosotros siempre serán Datos o Parametros o Consultas… o Algo es un Socket, siempre va a ser texto.
Headers
Lo primero es identificar las etiquetas de #region y #endregion que son las marcas que están puestas en el codigo para poder identificarlo y analizarlo parte por parte lo primero es la sección de ParseHeaders al ser un WebServer obviamente es necesario obtener los tokens y ponerlos en una lista que podamos utilizar ya que los encabezados son necesarios para obtener una única información que nos manda el socket cliente, y es Sec-WebSocket-Key para retornar la información al socket cliente como podemos ver en las etiquetas CreateResponseHeaders finalmente retornamos los encabezados de respuesta al Socket con el metodo client.Send. A todo este proceso se le llama Handshake.
Información
En el contenido del else como podemos ver se encuentran las regiones DecodeMessage que en la cual esta el método que decodificara el mensaje que nos envié el WebSocketCliente y la región CreateTimerToSendInfo la cual crea un pequeño timer que nos permite enviar la información cada cierto tiempo.
La información estará definida por el método DataInfo, que prácticamente contiene un Swith con una palabra mágica para saber que datos se van a enviar, después la información retornada en string será enviada al codificador del mensaje, para finalmente enviar la respuesta de la petición por primera vez.
Podemos ver que dentro del sistema se utilizan los siguientes Metodos:
- AcceptKey
- DecodeMessage
- ExecuteQuery
- EncodeMessageToSend
- DataInfo
Los cuales se mostrarán a continuación:
AcceptKey es utilizado para generar la nueva llave apartir de la que el clientre nos envio:
private static string AcceptKey(ref string key)
{
string longKey = key + guid;
SHA1 sha1 = SHA1CryptoServiceProvider.Create();
byte[] hashBytes = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(longKey));
return Convert.ToBase64String(hashBytes);
}
ExcuteQuery es el Metodo que será utilizado como delegado para la propiedad Elapsed:
public static void ExecuteQuery(object sender, Socket client, string Path)
{
string Data = DataInfo(Path);
byte[] ResponseBytes = EncodeMessageToSend(Data);
if (client.Connected)
{
client.Send(ResponseBytes);
}
else
{
client.Disconnect(false);
lstSocket.Remove(client);
}
}
DataInfo es el metodo que nos permitirá obtener informaciónes segun haya llamadas a la base de datos, como puedes ver en el metodo, no hay conexión a la base pero esa implementación la pueden hacer ustedes ya a su gusto:
public static string DataInfo(string Path)
{
string json = string.Empty;
Random rnd = new Random();
switch (Path)
{
case "DATOS1":
json = "[{\"Key\":"+ rnd.Next(0, 100) +",\"Value\":\"" + Guid.NewGuid() + "\"},{\"Key\":"+rnd.Next(0, 100)+",\"Value\":\"" + Guid.NewGuid() + "\"}]";
break;
case "DATOS2":
json = "[{\"KeyData\":"+ rnd.Next(0, 100) +",\"ValueData\":\"" + Guid.NewGuid() + "\"},{\"KeyData\":"+rnd.Next(0, 100)+",\"ValueData\":\"" + Guid.NewGuid() + "\"}]";
break;
}
return json;
}
DecodeMessage es un metodo un tanto complicado, dado que se debe seguir la implmentación mencionada en la pagina dada al inicio de este post Especificamente Aqui.
private static String DecodeMessage(Byte[] bytes)
{
String incomingData = String.Empty;
Byte secondByte = bytes[1];
Int32 dataLength = secondByte & 127;
Int32 indexFirstMask = 2;
if (dataLength == 126)
indexFirstMask = 4;
else if (dataLength == 127)
indexFirstMask = 10;
IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4);
Int32 indexFirstDataByte = indexFirstMask + 4;
Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
{
decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
}
return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
}
EncodeMessageToSend hace practicamente lo mismo pero de manera inversa:
private static Byte[] EncodeMessageToSend(String message)
{
Byte[] response;
Byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
Byte[] frame = new Byte[10];
Int32 indexStartRawData = -1;
Int32 length = bytesRaw.Length;
frame[0] = (Byte)129;
if (length <= 125)
{
frame[1] = (Byte)length;
indexStartRawData = 2;
}
else if (length >= 126 && length <= 65535)
{
frame[1] = (Byte)126;
frame[2] = (Byte)((length >> 8) & 255);
frame[3] = (Byte)(length & 255);
indexStartRawData = 4;
}
else
{
frame[1] = (Byte)127;
frame[2] = (Byte)((length >> 56) & 255);
frame[3] = (Byte)((length >> 48) & 255);
frame[4] = (Byte)((length >> 40) & 255);
frame[5] = (Byte)((length >> 32) & 255);
frame[6] = (Byte)((length >> 24) & 255);
frame[7] = (Byte)((length >> 16) & 255);
frame[8] = (Byte)((length >> 8) & 255);
frame[9] = (Byte)(length & 255);
indexStartRawData = 10;
}
response = new Byte[indexStartRawData + length];
Int32 i, reponseIdx = 0;
//Add the frame bytes to the reponse
for (i = 0; i < indexStartRawData; i++)
{
response[reponseIdx] = frame[i];
reponseIdx++;
}
//Add the data bytes to the response
for (i = 0; i < length; i++)
{
response[reponseIdx] = bytesRaw[i];
reponseIdx++;
}
return response;
}
Con estos metodos terminamos nuestro socket server ahora vamos a compilar nuestro servidor, vamos a ejecutarlo y nos mostrará una pantalla negra, y dejemoslo esperando ahí. y crearemos un cliente creando nuestro html con el siguiente codigo:
</pre>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js">
<style>
#Data1 {
float:left;
}
#Data2 {
float:left;
background:#fc0;
}
</style>
<body>
<div id="Data1"></div>
<div id="Data2"></div>
</body>
<script>
$(document).ready(function () {
var uri = "ws://localhost:9090/api/test";
//Initialize socket
websocket = new WebSocket(uri);
//Open socket and send message
websocket.onopen = function () {
$('#messages').prepend('
<div>Connected to server.</div>
');
websocket.send("DATOS1");
websocket.send("DATOS2");
};
//Socket error handler
websocket.onerror = function (event) {
alert("Error de Conexión");
};
//Socket message handler
websocket.onmessage = function (event) {
if (event.data != "[]") {
var parse = JSON.parse(event.data)
$.each(parse, function (i, data) {
if (data.KeyData == undefined) {
$('#Data1').prepend('
<div>Key:' + data.Key + ' Value: ' + data.Value + '</div>
');
}
else {
$('#Data2').prepend('
<div>Key:' + data.KeyData + ' Value: ' + data.ValueData + '</div>
');
}
});
}
};
});
</script>
<pre>
Una vez creado lo abriremos con el navegador de chrome y veremos el siguiente resultado:

Un Resultado interesante ¿No?, con esto ya no tenemos que hacer llamadas recursivas es el server el que se encarga de enviarnos la información y la pagina solo se dedica a parsear, imaginen las cosas que podrían hacerse.
Descarga(Pendiente)
Manejo de Idiomas en Web con jquery.i18n.properties-1.0.9.js
Ahhh, los idiomas, un gracioso dolor de cabeza, después de tomar un desarrollo en el cual hacia todo por controllers, una mezcla de miedo, pero esta no es la razón del post, veremos una librería que medio algo de tranquilidad en la tempestad de esta travesía…
jquery.i18n.properties es una librería la cual permite el manejo de idioma con javascript y jquery si quieren ver el proyecto original pueden descargarlo de esta aquí.
En cuanto a mi, bueno como ya saben me gusta controlar algo las cosas que uso, así que me metí a echarle un ojo e hice un par de cambios y debo decir que no muy substanciales, manos a la obra, primero haremos un html con el siguiente código
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://fcgexamples.000webhostapp.com/i18n/jquery.i18n.custom.js"></script>
<input type="button" id="btEsp" value="Español">
<input type="button" id="btIng" value="English">
<fieldset>
<legend lang="app.test.legend.title"></legend>
<label lang="app.test.input.text.User.label"></label>:<input name="username" lang="app.test.input.text.User.placeholder" ><br>
<label lang="app.test.input.text.Password.label"></label>:<input name="password" lang="app.test.input.text.Password.placeholder"><br>
<input type="button" value="" lang="app.test.input.Button.label"><br>
<span lang="app.test.Description.Text1"></span>
</fieldset>
<script>
var lang = 'es';
$('#btEsp').on('click', function() {
lang = 'es';
regionalData();
});
$('#btIng').on('click', function() {
lang = 'en';
regionalData();
});
$(document).ready(function() {
regionalData();
});
function regionalData() {
$.i18n.properties({
name: 'messages',
path: '/i18n/lng/',
mode: 'both',
language: lang,
});
}
</script>
Como podemos ver tenemos un atributo «lang» que nos servirá para definir las etiquetas que manejaran el idioma un ejemplo de esto es lang=»app.test.input.Button.label» a partir de esto usamos parte de la librería i18n, a la cual le he realizado algunos cambios, uno de ellos es que cambie el idioma de manera automática podemos ver el ejemplo funcionado aquí.
Los archivos archivos de idioma se encuentran en la carpeta lng y cuentan con la siguiente nomenclatura messages_{idioma}.txt en el caso de ingles sería messages_en.txt en el caso de español sería messages_es.txt
La información interna de los archivos contiene lo siguiente para messages_es.txt
app.test.input.text.User.placeholder=Usuario app.test.input.text.User.label=Usuario app.test.input.text.Password.placeholder=Contraseña app.test.input.text.Password.label=Contraseña app.test.input.Button.label=Enviar app.test.legend.title=Iniciar session app.test.Description.Text1=Este es un ejemplo de Formulario
La información interna de los archivos contiene lo siguiente para messages_en.txt
app.test.input.text.User.placeholder=User app.test.input.text.User.label=User Name app.test.input.text.Password.placeholder=Password app.test.input.text.Password.label=Password app.test.input.Button.label=Send app.test.legend.title=Login app.test.Description.Text1=This is a From Example
POCO, POJO, POPO y otras chunches… Ahhh Y MetaData tambien…
POCO es un acrónimo de Plain Old CLR Object
POJO es un acrónimo de Plain Old Java Object
Ambos acrónimos son utilizados para definir clases simples que pueden ser utilizados por frameworks para ser llenados, como muchas personas que me siguen en este blog, se darán cuenta que son muy utilizados en mis post para crear clases de paso o clases de llenado de datos que a su vez generan listas, hoy me he encontrado con algo que me da flojera a ciencia cierta y es actualizar una clase junto con todos los cambios que estas representan en las diferentes capas que utilizo en el trabajo, por lo que hoy dedique mi tiempo a realizar una herramienta que me permite crear estas clases de «dominio» o «entidad» de manera rapida utilizando la MetaData de la base de datos, veamos el código para armarla y luego podremos descargarlo.
Crearemos una solución de Windows Forms para este caso, y agregaremos los siguientes elementos
- cbDatabaseManager: Combo que contendrá las bases de datos para conectarse
- Server: TextBox que nos permitirá escribir el servidor a conectarnos
- Port: TextBox que nos permitirá cambiar de puerto si es necesario
- User: TextBox que nos permitirá escribir el Usuario
- Password: TextBox que nos permitirá escribir el Password
- Connect: Boton que generará la conexión
- DirectoryPath: Path donde serán guardadas nuestras clases
- button1: (Ya comenzamos con problemas de flojera) Boton que nos permitirá seleccionar el Path donde guardaremos nuestras clases
- cbDataBases: Combo que se llenará con las bases de datos una vez estemos conectados
- lstbTables: ListBox que nos permitirá mostrar las tablas y seleccionar más de una tabla para su generación en clase
- cbLanguage: Combo que nos dirá en que lenguaje serán creados nuestros ¿POCO’s?, ¿POJO’s? lo que ustedes quieran siguien siendo clases simples y dependen de la sintaxis del lenguaje
- Console: TextBox Multilinea que nos dira que esta pasando en nuestro proceso
- button2: Boton que nos permitirá Ejecutar la generación de clases
- fbdDirectory: Control de FileBoxDialog que nos permitirá en conjunto con el Boton1 seleccionar nuestro directorio
Una vez realizado nuestro formulario debe quedar más o menos así:
Bien una vez creado nuestro formulario creemos las clases para utilizarlo, crearemos una carpeta en la raíz de nuestro proyecto llamada Entity, en el cual crearemos las siguientes clases:
Catalog
publicclassCatalog
{
public string Key { get; set; }
public string Value { get; set; }
}
La clase anteriormente mostrada es generica, se utiliza para llenar catalogos tanto estaticos como dinamicos esta se utilizará para los combos y los listbox
Connector
public class Connector
{
public string DataBaseManager { get; set; }
public string Server { get; set; }
public int Port { get; set; }
public string Catalog { get; set; }
public string User { get; set; }
public string Password { get; set; }
public string SourceConnectorMaster { get {
StringBuilder conn = new StringBuilder();
conn.Append("Data Source = ")
.Append(Server)
.Append("; User ID = ")
.Append(User)
.Append("; Password = ")
.Append(Password);
return conn.ToString();
} }
public string SourceConnectorComplete
{
get
{
StringBuilder conn = new StringBuilder();
conn.Append("Data Source = ")
.Append(Server)
.Append("; Initial Catalog = ")
.Append(Catalog)
.Append("; User ID = ")
.Append(User)
.Append("; Password = ")
.Append(Password);
return conn.ToString();
}
}
}
La clase connector contendrá la información de conexión a la base de datos así como los datos que recopilemos de la entrada del formulario además de tener 2 propiedades más SourceConnectorMaster y SourceConnectorComplete, las cuales definen las cadenas de conexion que se turilizarán en este caso es .NET, más adelante con un poco de imaginación podemos agregar más cadenas de conexión de diferentes Administradores de Bases de Datos.
MetaInfo
public class MetaInfo
{
public string Table { get; set; }
public string FieldName { get; set; }
public string Type { get; set; }
public bool ISNullable { get; set; }
public int MaxChar { get; set; }
}
La clase MetaInfo contendrá la información de la Metadata de la base de datos Información de Tablas, Tamaños, Nombres de Columna, Tipos de Dato, etc…
CacheManager
public static class CacheManager
{
public static List<Catalog> DataBaseManager;
public static List<Catalog> Language;
public static void init()
{
DataBaseManager = new List<Catalog>() {
new Catalog() { Key="1", Value="SQLServer" }
};
Language = new List<Catalog>()
{
new Catalog() { Key="1", Value="C#" },
new Catalog() { Key="2", Value="Java"}
};
}
}
La clase CacheManager es una clase que se inicializará al abrir nuestro formulario y por lo tanto estatica, la cual contiene las listas estaticas dentro de nuestro programa, se utiliza para llenar los combos de Lenguaje y DataBaseManager, esto nos permite ir mostrando mientras crecemos la implementación, Estamos dieñando un programita, pero recordemos que si trabajamos en diferentes lenguajes seria un error crear una herramienta solo para uno de los lenguajes que utilizamos y peor aun si ligamos los lenguajes a bases de datos podria decirse que nativas o de paridad emmm… que quiero decir con esto, que si usas C# probablemente estes usando .NET o en su defecto si usas PHP, seguramente estas usando MySQL por esto es que voy a hacer el programa de tal manera que pueda crecer a mis necesidades.
Hora de preparar las conexiones a base de datos en este caso por prisa, solo utilizare, net; pero no obstante dejaré el proyecto preparado para crecer, así que crearé una carpeta DataAccess y dentro de ella una interface IData y una clase SQLServer, las cuales me permitirán trabajar y extender mi trabajo despues, veamos el contenido:
IData
public interface IData
{
List<Catalog> GetTables(string Connector);
List<Catalog> GetDataBases(string Connector);
List<MetaInfo> GetMetaInfo(string Connector);
}
Esta clase podemos ver que solo tiene las funciones que voy a utilizar en las demás clases esto me permitirá usar un poco de polimorfismo y algunos factorys para vivir mi experiencia un poco más ordenada (Si, a mi me va bien así…)
SQLServer
public class SQLServer : IData
{
public List GetMetaInfo(string Connector)
{
List<MetaInfo> metaInfo = new List<MetaInfo>();
SqlConnection conn = new SqlConnection(Connector);
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
#region Query
cmd.CommandText = @"SELECT
C.TABLE_CATALOG,C.TABLE_NAME,C.COLUMN_NAME,C.IS_NULLABLE,C.DATA_TYPE,C.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES A ON C.TABLE_NAME=A.TABLE_NAME
WHERE A.TABLE_NAME NOT IN('SYSSCHEMAARTICLES','SYSPUBLICATIONS','MSREPL_ERRORS','TRANSACTIONS','MSMERGE_HISTORY','MSMERGE_AGENT_PARAMETERS','MSMERGE_REPLINFO',
'SYSSUBSCRIPTIONS','SYSMERGEARTICLES','VW_TRAMITES_CONCEPTOS','SYSARTICLEUPDATES','MSPUB_IDENTITY_RANGE','SYSTRANSCHEMAS','SYSMERGESCHEMAARTICLES','SYSMERGEPARTITIONINFOVIEW',
'SYSMERGEPARTITIONINFO','MSMERGE_SUPPORTABILITY_SETTINGS','MSMERGE_SESSIONS','MSMERGE_PAST_PARTITION_MAPPINGS','MSMERGE_GENERATION_PARTITION_MAPPINGS','SYSMERGESUBSCRIPTIONS',
'MSDYNAMICSNAPSHOTJOBS','MSDYNAMICSNAPSHOTVIEWS','MSMERGE_AGENT_PARAMETERS','MSMERGE_ARTICLEHISTORY','MSMERGE_ALTSYNCPARTNERS','MSMERGE_CONTENTS','MSMERGE_CURRENT_PARTITION_MAPPINGS',
'MSMERGE_DYNAMIC_SNAPSHOTS','MSMERGE_ERRORLINEAGE','MSMERGE_GENHISTORY','MSMERGE_IDENTITY_RANGE','MSMERGE_LOG_FILES','MSMERGE_METADATAACTION_REQUEST','MSMERGE_PARTITION_GROUPS',
'MSMERGE_SETTINGSHISTORY','MSMERGE_TOMBSTONE','MSPEER_CONFLICTDETECTIONCONFIGREQUEST','MSPEER_LSNS','MSPEER_ORIGINATORID_HISTORY','MSPEER_REQUEST','MSPEER_TOPOLOGYREQUEST',
'SYSARTICLECOLUMNS','SYSARTICLES','SYSEXTENDEDARTICLESVIEW','SYSMERGEEXTENDEDARTICLESVIEW','SYSMERGEPUBLICATIONS','SYSMERGESCHEMACHANGE'
)
AND A.TABLE_TYPE NOT IN('VIEW')
ORDER BY A.TABLE_NAME";
#endregion
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandTimeout = 0;
using (SqlDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
MetaInfo mi = new MetaInfo();
mi.Table = Convert.ToString(r["TABLE_NAME"]);
mi.FieldName = Convert.ToString(r["COLUMN_NAME"]);
mi.Type = Convert.ToString(r["DATA_TYPE"]);
mi.ISNullable = Convert.ToString(r["IS_NULLABLE"]) == "YES" ? true : false;
metaInfo.Add(mi);
}
}
}
conn.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return metaInfo;
}
public List<Catalog> GetDataBases(string Connector)
{
List<Catalog> catalogs = new List();
Catalog c = new Catalog();
c.Key = "0";
c.Value = "Select one";
catalogs.Add(c);
SqlConnection conn = new SqlConnection(Connector);
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
#region Query
cmd.CommandText = @"SELECT name, database_id FROM sys.databases";
#endregion
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandTimeout = 0;
using (SqlDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
c = new Catalog();
c.Key = Convert.ToString(r["database_id"]);
c.Value = Convert.ToString(r["name"]);
catalogs.Add(c);
}
}
}
conn.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return catalogs;
}
public List GetTables(string Connector)
{
List<Catalog> catalogs = new List<Catalog>();
SqlConnection conn = new SqlConnection(Connector);
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
#region Query
cmd.CommandText = @"SELECT * FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME";
#endregion
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandTimeout = 0;
using (SqlDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
Catalog c = new Catalog();
c.Key = Convert.ToString(r["TABLE_NAME"]);
c.Value = Convert.ToString(r["TABLE_NAME"]);
catalogs.Add(c);
}
}
}
conn.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return catalogs;
}
}
Como podemos ver la clase SQLServer hereda de IData, esto me permite implementar los mismos metodos en todas mis clases ahora solo veamos que es lo que debemos hacer en cuanto a los metodos, creo que si eres desarrollador en .NET lo importante no es tanto el código si no más bien los querys veamos la información y expliquemoslos de poco en poco:
Metodo GetDataBases
SELECT name, database_id FROM sys.databases
Con este query obtenemos la información de todas las bases de datos en la instancia
Metodo GetTables
SELECT * FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME
Este query a su vez nos devuelve las tablas que estan dentro de una base de datos seleccionada
Metodo MetaInfo
SELECT
C.TABLE_CATALOG,C.TABLE_NAME,C.COLUMN_NAME,C.IS_NULLABLE,C.DATA_TYPE,C.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES A ON C.TABLE_NAME=A.TABLE_NAME
WHERE A.TABLE_NAME NOT IN('SYSSCHEMAARTICLES','SYSPUBLICATIONS','MSREPL_ERRORS','TRANSACTIONS','MSMERGE_HISTORY','MSMERGE_AGENT_PARAMETERS','MSMERGE_REPLINFO',
'SYSSUBSCRIPTIONS','SYSMERGEARTICLES','VW_TRAMITES_CONCEPTOS','SYSARTICLEUPDATES','MSPUB_IDENTITY_RANGE','SYSTRANSCHEMAS','SYSMERGESCHEMAARTICLES','SYSMERGEPARTITIONINFOVIEW',
'SYSMERGEPARTITIONINFO','MSMERGE_SUPPORTABILITY_SETTINGS','MSMERGE_SESSIONS','MSMERGE_PAST_PARTITION_MAPPINGS','MSMERGE_GENERATION_PARTITION_MAPPINGS','SYSMERGESUBSCRIPTIONS',
'MSDYNAMICSNAPSHOTJOBS','MSDYNAMICSNAPSHOTVIEWS','MSMERGE_AGENT_PARAMETERS','MSMERGE_ARTICLEHISTORY','MSMERGE_ALTSYNCPARTNERS','MSMERGE_CONTENTS','MSMERGE_CURRENT_PARTITION_MAPPINGS',
'MSMERGE_DYNAMIC_SNAPSHOTS','MSMERGE_ERRORLINEAGE','MSMERGE_GENHISTORY','MSMERGE_IDENTITY_RANGE','MSMERGE_LOG_FILES','MSMERGE_METADATAACTION_REQUEST','MSMERGE_PARTITION_GROUPS',
'MSMERGE_SETTINGSHISTORY','MSMERGE_TOMBSTONE','MSPEER_CONFLICTDETECTIONCONFIGREQUEST','MSPEER_LSNS','MSPEER_ORIGINATORID_HISTORY','MSPEER_REQUEST','MSPEER_TOPOLOGYREQUEST',
'SYSARTICLECOLUMNS','SYSARTICLES','SYSEXTENDEDARTICLESVIEW','SYSMERGEEXTENDEDARTICLESVIEW','SYSMERGEPUBLICATIONS','SYSMERGESCHEMACHANGE'
)
AND A.TABLE_TYPE NOT IN('VIEW')
ORDER BY A.TABLE_NAME
Y este nos da la información de tablas agrupadas por columnas con sus respectivas informaciónes por cada registro.
Una vez realizado esto tenemos la mitad de nuestro codigo para jugar, en realidad la otra mitad esta en la generación de los archivos, para hacerlo con más velocidad solo crearemos el de generacion de clases en C#; en la carpeta Entity crearemos otra carpeta llamada Servicio en la cual crearemos IFileWriter, CSharpFile y FileWriterFactory. Y veremos como nos queda:
IFileWriter
public interface IFileWriter
{
void GenerateFile(string Table, List meta);
void Save(string Path);
}
Nuevamente definimos una interfaz que nos apoyará en el camino de la extención de nuestro código.
CSharpFile
public class CSharpFile : IFileWriter
{
private string Data;
private string NameFile;
public void GenerateFile(string Table, List<MetaInfo> meta)
{
this.NameFile = Table + ".cs";
StringBuilder str = new StringBuilder();
List<MetaInfo> TableInfo = meta.Where(MI => MI.Table == Table).ToList();
str.Append("public class ").Append(Table).Append(" {").Append(Environment.NewLine);
foreach (MetaInfo FieldInfo in TableInfo)
{
str.Append("\tpublic ");
switch (FieldInfo.Type)
{
case "nchar":
str.Append("string").Append(" ");
break;
case "varchar":
str.Append("string").Append(" ");
break;
case "text":
str.Append("string").Append(" ");
break;
case "int":
str.Append("int").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
case "bigint":
str.Append("long").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
case "smallint":
str.Append("short").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
case "tinyint":
str.Append("short").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
case "datetime":
str.Append("DateTime").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
case "money":
str.Append("decimal").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
case "bit":
str.Append("bool").Append(FieldInfo.ISNullable ? "?" : "").Append(" ");
break;
}
str.Append(FieldInfo.FieldName).Append(" { get;set; }").Append(Environment.NewLine);
}
str.Append(" } ");
Data = str.ToString();
}
public void Save(string Path)
{
string FullPath = System.IO.Path.Combine(Path, NameFile);
System.IO.StreamWriter file = new System.IO.StreamWriter(FullPath);
file.WriteLine(Data);
file.Close();
}
}
Este archivo implementa los metodos de IFileWriter y nos permitirá crear una seccion que se dedicará solamente al codigo de .NET, no creo que tenga que explicar gran cosa, simplemente se crea un string que contiene la clase parseada en base a una serie de patrones
FileWriterFactory
public static class FileWriterFactory
{
public static IFileWriter GenerateWriter(string Language)
{
IFileWriter writer = null;
switch (Language)
{
case "C#":
writer = new CSharpFile();
break;
case "Java":
//...
break;
case "PHP":
//...
break;
}
return writer;
}
}
Un factory que contiene un switch con la información de que tipo de lenguaje usaremos.
Una vez realizado es hora de darle funcionalidad a nuestro From inicial.
Agregaremos las siguientes lineas que definirán nuestro «repositorio» de datos (Repository) y nuestro conector
Connectorconnector = newConnector(); IData Repository;
Después agregaremos la funcion Onload del formulario, que nos permitirá ejecutar operaciones al momento de la carga del form
private void Form1_Load(object sender, EventArgs e)
{
CacheManager.init();
cbLanguage.DisplayMember = cbDatabaseManager.DisplayMember = "Value";
cbLanguage.ValueMember = cbDatabaseManager.ValueMember = "Key";
cbLanguage.DataSource = CacheManager.Language;
cbDatabaseManager.DataSource = CacheManager.DataBaseManager;
}
Con estas operaciones llenamos los combos iniciales que son cbLanguaje y cbDatabaseManager.
private void Connect_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(Server.Text) && string.IsNullOrEmpty(User.Text) && string.IsNullOrEmpty(Password.Text))
{
MessageBox.Show("The Server, User and Password can't be blank");
}
Catalog databaseManager = (Catalog)cbDatabaseManager.SelectedItem;
connector.Server = Server.Text;
connector.User = User.Text;
connector.Password = Password.Text;
switch (databaseManager.Value)
{
case "SQLServer":
Repository = new SQLServer();
break;
}
List<Catalog> DataBases = Repository.GetDataBases(connector.SourceConnectorMaster);
cbDataBases.DisplayMember = "Value";
cbDataBases.ValueMember = "Key";
cbDataBases.DataSource = DataBases;
}
Con el código anteriror damos funcionalidad al boton connect podemos checar nuestra conección y crear los repositorios.
privatevoid cbDataBases_SelectedIndexChanged(object sender, EventArgs e)
{
if (cbDataBases.SelectedItem != null)
{
Catalog dataBaseSelected = (Catalog)cbDataBases.SelectedItem;
if (dataBaseSelected.Key != "0")
{
connector.Catalog = Convert.ToString(dataBaseSelected.Value);
List<Catalog> Tables = Repository.GetTables(connector.SourceConnectorComplete);
lstbTables.ValueMember = "Key";
lstbTables.DisplayMember = "Value";
lstbTables.DataSource = Tables;
}
}
}
Despues agregamos el evento SelectedIndexChanged de cbDataBases que nos permite que cuando se seleccione un nuevo item del combo automaticamente nos muestre las tablas de la base de datos que se selecciono y los despliega en lstbTables
private void button1_Click(object sender, EventArgs e)
{
fbdDirectory.ShowDialog();
DirectoryPath.Text = fbdDirectory.SelectedPath;
}
Que nos da la funcionalidad para seleccionar carpeta.
private void button2_Click(object sender, EventArgs e)
{
Catalog languaje = (Catalog)cbLanguage.SelectedItem;
IFileWriter writer = FileWriterFactory.GenerateWriter(languaje.Value);
if (writer == null)
{
MessageBox.Show("must be select a language");
return;
}
List<MetaInfo> data = Repository.GetMetaInfo(connector.SourceConnectorComplete);
foreach(Catalog item in lstbTables.SelectedItems) {
writer.GenerateFile(item.Value, data);
writer.Save(DirectoryPath.Text);
Console.Text += Console.Text+ "Se ha Generado el archivo " + item.Value + " en "+ DirectoryPath.Text + Environment.NewLine;
}
}
y finalmente el botton2 que nos permitira generar el código…
Es interesante como siguiendo ciertos patrones de programación podemos generar código de manera automatica… y evitarnos la tediosa talacha.





















