ASP.net

[ASP.NET] Subiendo archivos a un blob storage con RadCloudUpload

Posted on Actualizado enn

Hola a todos, actualmente es muy común que en las aplicaciones Web el cargue de archivos se haga a un storage en la nube, y generalmente es necesario crear todo el código para implementar dicha funcionalidad, sin embargo Telerik nos hace la vida más sencilla ya que en su gama de controles ahora disponemos de RadCloudUpload, control que en esencia nos permite realizar la subida de un archivo a un storage en la nube (Azure, Amazon S3 y Telerik Backend Services).

Pues bien, su implementación es bastante sencilla y la vamos a realizarm para cargar archivos a un blob storage de Microsoft Azure, una vez añadimos el control, como es costumbre tenemos algunas opciones para comenzar su parametrización como:

  • Tamaño del archivo
  • Proveedor del storage
  • Extensiones permitidas
  • Posibilidad de subir múltiples archivos
  • Skin

cloudupload1

Como hemos seleccionado en el proveedor Azure, se habilita un wizard para ingresar los datos del storage:

cloudupload3

No olvides agregar por Nuget el paquete Windows Azure Storage:

cloudupload2

Y listo, ya es solo probar, una vista previa del control funcionando:

cloudupload4

Si luego requieren cambiar los datos del storage, todos los datos se han replicado en el web.config:

<telerik.web.ui>
	<radCloudUpload>
	  <storageProviders>
		<add name="Azure" type="Telerik.Web.UI.AzureProvider" 
			 accountKey="681o+9PweN+2k....." 
			 accountName="demomvcsignalr" 
			 blobContainer="democloudupload" 
			 ensureContainer="true" 
			 uncommitedFilesExpirationPeriod="1" 
			 defaultEndpointsProtocol="" />
	  </storageProviders>
	</radCloudUpload>
</telerik.web.ui>

Saludos!

[ASP.NET] Integrando ASP.NET con Telerik ASP.NET AJAX

Posted on Actualizado enn

Hola a todos, muchos sabrán de mi gusto por los controles y herramientas de Telerik, así que quiero comenzar a compartir regularmente post sobre sus productos. En esta ocasión vamos a ver cómo es de sencillo integrar los controles ASP.NET AJAX en una aplicación nueva de tipo Web Forms, tarea que Telerik nos pone bastante fácil (con un wizard :)).

Una vez ya tienes instalados los controles, y te recomiendo utilices el Telerik Control Panel (luego hablamos hablaremos de él), tenemos un template que nos va a llevar paso a paso:

Primero seleccionamos el template Telerik Web Application:

telerik 1

Luego se abre un wizard en donde podemos configurar algunas partes importantes del proyecto, iniciando con el tema a utilizar y si se desea agregar una referencia de los assemblies a la solución:

telerik 2

En el siguiente paso es posible establecer algunas opciones, como por ejemplo si usar CDN entre otras:

telerik 3

Luego si queremos tener soporte para jQuery y templates:

telerik 4

Y finalmente si queremos usar Telerik Data Access, el ORM de Telerik (que la verdad va bastante bien):

telerik 5

Y listo, ya solo queda usar y aprovechar la potencia de los controles!

Saludos!

[ASP.NET Web API] Web API IX – Consumiendo un servicio externo, CORS

Posted on Actualizado enn

Hola a todos, hoy vamos a retomar la serie de post sobre Web API, para hablar específicamente el como podemos consumir dichos servicios pero de un dominio diferente, lo primero que vamos a realizar es desplegar lo que se ha venido trabajando, y lo he hecho ayudandome de Azure y en un par de clicks ya esta listo: http://testwebapi.azurewebsites.net, ahora el siguiente paso es crear un cliente para consumir dicho servicio (que ya todos conocemos), para este caso he replicado lo que hemos venido trabajando, la diferencia es que en el archivo person.js al realizar el llamado al servicio ahora es necesario utilizar el dominio del sitio:

http://testwebapi.azurewebsites.net/api/person/...

Bueno, hasta el momento todo parece listo, entonces si probamos vamos a obtener el siguiente error:

error domain

Revisando el error, podemos deducir que el problema se da porque estamos realizando la petición entre dominios diferentes, y es acá donde iniciamos a hablar de CORS!

CORS que quiere decir algo como intercambio de recursos entre dominios cruzados (Cross-Origin Resource Sharing, si mucho mejor la definición en inglés) lo que hace es definir un modelo para poder acceder a recursos de diferentes dominios, en ese caso tanto el cliente como el servidor digamos que trabajan de la mano usando encabezados HTTP.

Cuando estamos usando el método GET para la petición, en la cabecera del request se envía la propiedad Origin con el dominio desde el cual estamos realizando la petición, luego el servicio Web API válida si el dominio que realiza la petición es permitido, en caso afirmativo en la cabecera de la respuesta en la propiedad Access-Control-Allow-Origin se retorna el mismo dominio que realizo la petición o un * para permitir todos los dominios, si la respuesta no cumple esa condición el browser elimina la respuesta:

request 1

Y ahora la solución, nos vamos al método Get del servicio y en el header de la respuesta le agregamos la propiedad Access-Control-Allow-Origin con el valor *, o bien si se tiene un listado de dominios permitidos allí hacer la validación y retornar el dominio específico en lugar del *, por lo tanto el código quedaría:

public HttpResponseMessage GetPerson()
{
	var data = db.Person.AsEnumerable();

	var httpResponseMessage = Request.CreateResponse<IEnumerable<Person>>(HttpStatusCode.OK, data);
	httpResponseMessage.Headers.Add("Access-Control-Allow-Origin","*");

	httpResponseMessage.Headers.CacheControl = new CacheControlHeaderValue()
	{ 
		MaxAge = TimeSpan.FromMinutes(1)
	};

	return httpResponseMessage;
}

[ActionName("getbyid")]
public HttpResponseMessage GetPerson(Int32 id)
{
	var person = db.Person.Find(id);

	if (person == null)
	{
		var httpResponseMessage = Request.CreateResponse<Person>(HttpStatusCode.NotFound,person);
		httpResponseMessage.Headers.Add("Access-Control-Allow-Origin", "*");

		return httpResponseMessage;
	}
	else
	{
		var httpResponseMessage = Request.CreateResponse<Person>(HttpStatusCode.OK, person);
		httpResponseMessage.Headers.Add("Access-Control-Allow-Origin", "*");

		return httpResponseMessage;
	}
}

Ahora si probamos de nuevo la petición Get funciona correctamente:

response

Bueno, ya tenemos el GET, ahora vamos a trabajar con los demás verbos Http, por ejemplo si intentamos realizar una petición PUT obtenemos:

put

Revisando la información del request, tenemos dos cosas importantes, la primera es que en Request Method el valor es OPTIONS y la segunda que el Status Code es el 405 (Método no permitido), bueno y ahora? Lo que debemos hacer ahora es leer el encabezado de la petición y hacer algunas pequeñas adiciones como especificar que se permitan los verbos PUT y DELETE, permitir todos los dominios y aceptar en el header el atributo Content-Type, para este caso, vamos a crear un Message Handler con el nombre RequestMethodHandler:

public class RequestMethodHandler : DelegatingHandler
{
	protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
	{
		if (request.Headers.Contains("Origin") && request.Method == HttpMethod.Options)
		{
			var response = new HttpResponseMessage(HttpStatusCode.OK);
			response.Headers.Add("Access-Control-Allow-Origin", "*");
			response.Headers.Add("Access-Control-Allow-Methods", "PUT, DELETE");
			response.Headers.Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
			return response;
		}

		return await base.SendAsync(request, cancellationToken);
	}
}

y no olviden llamarlo en WebApiConfig:

config.MessageHandlers.Add(new RequestMethodHandler());

Ahora replicamos los cambios que realizamos en el método GET del controlador para los demás métodos que tenemos allí (POST, UPDATE, DELETE), por lo que ahora el controlador quedaría:

public class PersonController : ApiController
{
	private PersonDBContext db = new PersonDBContext();

	/// <summary>
	/// Get all persons
	/// </summary>
	public HttpResponseMessage GetPerson()
	{
		var data = db.Person.AsEnumerable();

		var httpResponseMessage = Request.CreateResponse<IEnumerable<Person>>(HttpStatusCode.OK, data);
		httpResponseMessage.Headers.Add("Access-Control-Allow-Origin","*");

		return httpResponseMessage;
	}
	
	[ActionName("getbyid")]
	public HttpResponseMessage GetPerson(Int32 id)
	{
		var person = db.Person.Find(id);

		if (person == null)
		{
			var httpResponseMessage = Request.CreateResponse<Person>(HttpStatusCode.NotFound,person);
			httpResponseMessage.Headers.Add("Access-Control-Allow-Origin", "*");

			return httpResponseMessage;
		}
		else
		{
			var httpResponseMessage = Request.CreateResponse<Person>(HttpStatusCode.OK, person);
			httpResponseMessage.Headers.Add("Access-Control-Allow-Origin", "*");

			return httpResponseMessage;
		}
	}

	/// <summary>
	/// Get a person by an id
	/// </summary>
	/// <param name="id"></param>
	/// <returns></returns>
	[ActionName("getbyotherid")]
	public Person GetPersonByOtherId(Int32 id)
	{
		Person person = db.Person.Find(id);
		if (person == null)
		{
			throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
	}

		return person;
	}

	// PUT api/Person/5
	public HttpResponseMessage PutPerson(Int32 id, Person person)
	{
		HttpResponseMessage response;

		if (!ModelState.IsValid)
		{
			response = Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
		}
		else if (id != person.Id)
		{
			response = Request.CreateResponse(HttpStatusCode.BadRequest);
		}
		else 
		{
			db.Entry(person).State = EntityState.Modified;

			try
			{
				db.SaveChanges();
				response = Request.CreateResponse(HttpStatusCode.OK);
			}
			catch (DbUpdateConcurrencyException ex)
			{
				response = Request.CreateResponse(HttpStatusCode.NotFound, ex);
			}
		}
		
		response.Headers.Add("Access-Control-Allow-Origin", "*");
		return response;
	}

	// POST api/Person
	public HttpResponseMessage PostPerson(Person person)
	{
		if (ModelState.IsValid)
		{
			db.Person.Add(person);
			db.SaveChanges();

			HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, person);
			response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = person.Id }));
			response.Headers.Add("Access-Control-Allow-Origin", "*");
			return response;
		}
		else
		{
			return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
		}
	}

	// DELETE api/Person/5
	public HttpResponseMessage DeletePerson(Int32 id)
	{
		Person person = db.Person.Find(id);
		HttpResponseMessage response;

		if (person == null)
		{
			response = Request.CreateResponse(HttpStatusCode.NotFound);
		}
		else
		{
			db.Person.Remove(person);

			try
			{
				db.SaveChanges();
				response = Request.CreateResponse(HttpStatusCode.OK, person);
			}
			catch (DbUpdateConcurrencyException ex)
			{
				response = Request.CreateResponse(HttpStatusCode.NotFound);
				
			}
		}

		response.Headers.Add("Access-Control-Allow-Origin", "*");
		return response;
	}

	protected override void Dispose(bool disposing)
	{
		db.Dispose();
		base.Dispose(disposing);
	}
}

y si probamos de nuevo, podemos ver que ahora si todo nos funciona correctamente.

Espero este post les sirva bastante, saludos!

Descarga el ejemplo!

[ASP.NET Web API] Web API VIII – Trabajando con los ActionName

Posted on Actualizado enn

Hola a todos, volviendo con la serie sobre ASP.NET Web API, en esta oportunidad quiero mostrarles como podemos personalizar el nombre de las acciones y además poder tener dos o más métodos que trabajen con el mismo verbo HTTP y una misma firma.

Para personalizar el nombre de las acciones, es necesario decorar cada acción con el atributo ActionName y especificar el nombre que deseamos utilizar:

[ActionName("nombre_de_la_acción")]

Para nuestro ejemplo vamos a crear una nueva acción con el nombre GetPersonByOtherId(Int32 id), dicha acción obedece al verbo Http Get y tiene la misma firma que la acción GetPerson, adicionalmente decoramos las acciones con el atributo ActionName y le asignamos un nombre (si en este caso la lógica de cada acción es la misma):

[ActionName("getbyid")]
public Person GetPerson(Int32 id)
{
	Person person = db.Person.Find(id);
	if (person == null)
	{
	   throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
	}

	return person;
}

[ActionName("getbyotherid")]
public Person GetPersonByOtherId(Int32 id)
{
	Person person = db.Person.Find(id);
	if (person == null)
	{
		throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
	}

	return person;
}

Adicionalmente agregamos un nuevo elemento en nuestro HTML:

<input id="btnSearch2" type="button" value="Search by Other Id" data-bind="click:getPersonByOtherId" />

Ahora modificamos el ViewModel y agregamos la función getPersonById, así como en la función getPersonById hacemos un pequeño cambio a la url que se esta llamando, en este caso agregando el nombre de la acción:

...
self.getPersonById= function () {
	var url = '/api/person/getbyid/' + self.id();
	$.getJSON(url)
		.done(function (data) {
			self.name(data.Name);
			self.lastname(data.LastName);
			self.twitter(data.Twitter);
		})
		.fail(function (erro) {
			self.clearForm();
	});
},

self.getPersonByOtherId = function () {
	var url = '/api/person/getbyotherid/' + self.id();
	$.getJSON(url)
		.done(function (data) {
			self.name(data.Name);
			self.lastname(data.LastName);
			self.twitter(data.Twitter);
		})
		.fail(function (erro) {
			self.clearForm();
		});
},
...

Luego es necesario agregar una nueva ruta en la tabla de routing, en este caso en la clase WebApiConfig:

config.Routes.MapHttpRoute(
	name: "ApiByOtherId",
	routeTemplate: "api/{controller}/{action}/{id}",
	defaults: new { id = RouteParameter.Optional }
);

Y ahora si ejecutamos y probamos podemos ver como en efecto los ActionName funcionan correctamente:

ActionName

Espero el post les haya gustado, hasta el próximo!

Descarga el ejemplo!

[ASP.NET Web API] Web API VII – Message Handlers

Posted on Actualizado enn

Hola a todos, luego una pequeña ausencia, retomo el blog con un nuevo artículo sobre Web API, en este caso tratando el tema de los Message Handlers, un Message Handler se ejecuta antes que cualquier action filter además que son ejecutados para todos las acciones de los controladores, por lo anterior, un Message Handler es ideal para tener lógica centralizada que se deba ejecutar en cada request.

Como parte informativa, el primer Message Handler que se ejecuta es el HttpServer, y luego si se ejecutarán los que nosotros definamos.

Para crear un Message Handler personalizado, se debe crear una clase que herede de DelegatingHandler y sobrescribir el método SendAsync, generalmente los Message Handler son utilizados para temas como autenticación y autorización.

En nuestro caso, vamos a implementar un Message Handler que valide el dominio desde el cual están realizando la petición, para autorizarla o no, lo primero es crear una nueva clase a la que llamaremos ValidationHandler y como ya se comento dicha clase heredara de DelegatingHandler:

public class ValidationHandler : DelegatingHandler
{
	protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
	{
		var domain = "localhost";
		if (!string.Equals(request.RequestUri.Host, domain, StringComparison.OrdinalIgnoreCase))
			return request.CreateResponse(HttpStatusCode.Unauthorized);

		var response = await base.SendAsync(request, cancellationToken);
		return response;
	}
}

Allí, accedemos al valor del host que realiza la petición por medio del objeto request que es del tipo HttpRequestMessage, en caso que el host no sea válido se crea al vuelo la respuesta a la petición y se retorna un código 401 que indica no autorizado (HttpStatusCode.Unauthorized).

Finalmente para que el Message Handler entre en ejecución, se debe relacionar en el Application_Start, y recuerden que tenemos una clase llamada WebApiConfig donde se tiene configurado el routing para Web API, por lo tanto es un buen lugar para relacionarlo, recuerda tener presente el orden en que se añaden, ya que en ese mismo orden serán ejecutados:

public static void Register(HttpConfiguration config)
{
	...
	config.MessageHandlers.Add(new ValidationHandler());
}

no autorizado

Espero les haya gustado el post, ya nos seguiremos viendo por acá con otras entregas sobre Web API.

Descarga el ejemplo!

[ASP.NET Web API] Web API VI – Implementando Caché

Posted on Actualizado enn

Hola a todos, vuelvo con otro post sobre Web API, en esta ocasión quiero mostros como podemos implementar un sencillo sistema de caché para reducir las peticiones que se realizan a nuestro servicio y así poder mejorar el rendimiento del mismo, para el ejemplo implementaremos dicho sistema en la acción que retorna todo el listado de personas, recordando tenemos algo como:

public IEnumerable<Person> GetPerson()
{
	var data = db.Person.AsEnumerable();
	return data;
}

La acción anterior simplemente consulta la fuente de datos y los retorna, sin embargo este tipo de comportamientos puede traernos problemas futuros ya que constantemente se está consultando la fuente de datos para traer todos los registros existentes y si dicha fuente de datos es una base de datos se debe tener presente el abrir y cerrar conexiones, el delay entre que se lanza el query y el momento en que el servidor de base de datos responde… ya vemos por donde va todo no?

Pensando en el problema anterior, una solución es implementar un sistema de caché para reducir las peticiones a la base de datos y en algunos casos (como este) también disminuir los request al servicio. Lo primero es añadir un nuevo botón que realice la petición a la acción GetPerson() para ver su comportamiento:

<input type="button" id="btnGetAll" value="Get all" data-bind="click:getAll" />

Ahora probamos de nuevo la aplicación y para ver el comportamiento damos click varias veces en el botón creado anteriormente, con ayuda de firebug revisemos las peticiones:

firebug1

y el detalle de alguna de ellas:

no-cache

Resumiendo la prueba, cada vez que damos click en el botón se realiza una nueva llamada al servicio, y como se aprecia en el detalle de la petición, en el Header de la respuesta el Cache-Control tiene el valor no-cache, que como adivinan indica que no se está manejando.

Para aplicar caché, vamos a hacerle un refactoring a la acción GetPerson() teniendo en cuenta los siguientes puntos:

  • Se retornará HttpResponseMessage en lugar de IEnumerable<Person>.
  • Se definirá la caché en el header de la respuesta.

Con los dos puntos anteriores en mente, ahora la acción GetPerson() quedaría:

public HttpResponseMessage GetPerson()
{
	var data = db.Person.AsEnumerable();

	var httpResponseMessage = Request.CreateResponse<IEnumerable<Person>>(HttpStatusCode.OK, data);

	httpResponseMessage.Headers.CacheControl = new CacheControlHeaderValue()
	{ 
		MaxAge = TimeSpan.FromMinutes(1)
	};

	return httpResponseMessage;
}

Lo primero es obtener los datos, luego se crea el objeto HttpResponseMessage, se define el tipo de dato que se retornará, en este caso un IEnumerable<Person>, el código de la respuesta es 200 (HttpStatusCode.OK) y los datos; y ahora para especificar el uso y tiempo de vida de la cache, modificamos el valor de la propiedad CacheControl del header y definimos que su tiempo de vida será de 1 minuto.

Si ahora repetimos la prueba realizada, y damos click varias veces seguidas en el botón para obtener todos los datos, visualizamos que ya no se realizan tantas peticiones como clicks,  ya que la caché usada guarda los datos en JSON en el cliente y los utiliza cada vez que hacemos click, claro que una vez se complete 1 minuto, la siguiente petición SI se realizará normalmente y se volverá a cachear la respuesta, y para que no quede duda, revisemos de nuevo la información de la petición y su respuesta con firebug, la propiedad Cache-Control ahora tiene el valor max-age=60, es decir que usa caché con un duración de 60 segundos.

si-cache

Espero e post les haya gustado, nos vemos!

Saludos!

Descarga el ejemplo!

[ASP.NET Web API] Web API V – Filtros de acción

Posted on Actualizado enn

Hola a todos, de nuevo con otro post de la serie de ASP.NET Web API, esta vez vamos a revisar como podemos crear filtros de acción los cuales nos permiten ejecutar código antes y después de la ejecución de un acción en determinado controlador.

Para crear un filtro de acción, se debe crear una clase que herede de ActionFilterAttribute, y sobrescribir las funciones OnActionExecuting (ejecutada antes de la acción del controlador) y/o OnActionExecuted (ejecutada una vez la acción del controlador ha finalizado).

Para mostrar su uso, la idea es crear un filtro que permita tener un log de  las ejecuciones de la ejecuciones de las acciones en PersonController, pero antes de crear el filtro crearemos una clase singleton que se encargará de hacer el log, asi que primero definimos una sencilla interfaz:

interface ILogWriter
{
	void Log(string message);
}

Luego la clase que implementa dicha interfaz:

public class LogWriter : ILogWriter
{
	private static readonly Lazy instance = new Lazy(() => new LogWriter());

	private LogWriter() { }

	public static LogWriter Instance
	{
		get
		{
			return instance.Value;
		}
	}

	public void Log(string message)
	{
		Debug.WriteLine(message);
	}
}

Ahora que ya tenemos lista la clase, creamos una clase llamada LogFilterAttribute la cual debe heredar de ActionFilterAttribute y sobrescribimos los métodos nombrados anteriormente:

public class LogFilterAttribute : ActionFilterAttribute
{
	public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
	{
		//Get info using actionContext
		LogWriter.Instance.Log("Executed before at: " + System.DateTime.Now);
	}

	public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
	{
		//Get info using actionContext
		LogWriter.Instance.Log("Executed after at: " + System.DateTime.Now);
	}
}

En este caso solo enviamos una simple cadena de texto, pero es posible obtener información del request utilizando el objeto actionContext.

Por último, para que el filtro creado funcione, lo que hacemos es decorar el controlador Person con el atributo creado:

[LogFilter]
public class PersonController : ApiController
{
	...
}

Espero el post les sea de utilidad, nos vemos en el siguiente artículo de Web API!

Saludos!

Descarga el ejemplo!

[ASP.NET Web API] Web API IV – Implementando Knockoutjs

Posted on Actualizado enn

Hola a todos, en el post anterior vimos como podemos consumir nuestro servicio REST de Web API utilizando jQuery y AJAX (míralo acá), creamos un archivo JavaScript para mantener separado el contenido (HTML) del comportamiento de la página (JavaScript), sin embargo, con solo observar el archivo js creado, nos damos cuenta que dicha afirmación no es del todo cierta, puesto que tenemos una dependencia total a los elementos del DOM, y si en algún momento el HTML llega a cambiar o los id´s de los elementos vamos a tener problemas.

Para solucionar el problema anterior, una posible solución es implementar un framework JavaScript como Knockout, el cual permite trabajar el patrón MVVM (model-view-view model) en el cliente; en este post no voy a entrar en detalles técnicos de este framework (probablemente luego haga una serie sobre el tema) ni a discutir si es o no la mejor opción, solo comentare que a mi me gusta, me ayuda a trabajar mejor en el cliente y que cumple su objetivo, además si hasta inicias con JavaScript, Knockout te será sencillo de entender.

Luego de una pequeña introducción iniciemos (seguimos con el ejemplo del anterior post), lo primero es añadir Knockout, para ello vamos a hacer uso de Nuget:

knockout

Ahora en la clase BundleConfig (en la carpeta App_Start) en el método RegisterBundles añadimos un nuevo bundle para knockout:

bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
        "~/Scripts/knockout-{version}.js"));

Luego en el layout (Views/Shared/_Layout.cshtml) del sitio lo referenciamos al final:

@Scripts.Render("~/bundles/knockout")

Ahora que ya tenemos listo a Knockout, iniciamos con el refactoring de person.js, la idea es crear un ViewModel que permita trabajar de manera desconectada del DOM las operaciones CRUD implementadas, lo primero que haremos y para poder trabajar mejor con Knockout es agregar una referencia a knockout-2.3.0.debug.js (en el momento de escribir el post la versión que uso es la 2.3.0) en person.js para tener intellisense

/// <reference path="../knockout-2.3.0.debug.js" />

Luego definimos el modelo:

var PersonViewModel = function () {
    self = this;
    self.id = ko.observable();
    self.name= ko.observable();
    self.lastname= ko.observable();
    self.twitter= ko.observable();
    self.personList= ko.observableArray();
	.....
}

Y al definir el modelo ya tenemos varios cambios importantes:

  • Definición del objeto PersonViewModel
  • A self le asignamos this para evitar conflictos al acceder a las propiedades del objeto
  • Las propiedades se definen como observables, es decir si cambia la propiedad el objeto HTML asociado también lo hará, y viceversa.
  • La propiedad personList es observable y a la vez es un array.

Ahora comenzamos a añadir funciones a nuestro viewmodel:

Obtener todos los elementos

self.getAll = function () {
	$.getJSON('/api/person', function (data) {
		self.personList(data);
	});
}

En este caso de nuevo se realiza la petición con $.getJSON, la diferencia radica en que la respuesta se la seteamos como valor al array observable.

Obtener un elemento

self.getPersonById= function () {
	var url = '/api/person/' + self.id();
	$.getJSON(url)
		.done(function (data) {
			self.name(data.Name);
			self.lastname(data.LastName);
			self.twitter(data.Twitter);
		})
		.fail(function (erro) {
			self.clearForm();
	});
},

La funcionalidad básica permanece casi que igual, sin embargo los cambios que se observan son:

  • Se utiliza self.id() para obtener el valor de la propiedad id del objeto en lugar de consultar el DOM
  • Si la respuesta es correcta, se asignan los valores a las propiedades del objeto y no directamente a los elementos del DOM.

Eliminar elemento por id

self.deletePersonById= function () {
	var url = '/api/person/' + self.id();
	$.ajax({
		url: url,
		type: 'DELETE',
		contentType: "application/json;chartset=utf-8",
		statusCode: {
			200: function () {
				self.getAll();
				alert('Person with id= ' + self.id() + ' was deleted');
				self.clearForm();
			},
			404: function () {
				alert('Person with id= ' + self.id() + ' was not found');
			}
		}
	});
},

El cambio importante es que para acceder al id de la persona de utiliza self.id() en lugar de acceder al elemento del DOM para leer su valor.

Actualizar y crear un elemento

self.updatePerson= function () {
	var url = '/api/person/' + self.id();
	$.ajax({
		url: url,
		type: 'PUT',
		data: ko.toJSON(self),
		contentType: "application/json;chartset=utf-8",
		statusCode: {
			200: function () {
				self.getAll();
				alert('Person with id= ' + self.id() + ' was updated');
				self.clearForm();
			},
			404: function () {
				alert('Person with id= ' + self.id() + ' was not found');
				self.clearForm();
			},
			400: function () {
				self.clearForm();
				alert('Error');
			}
		}
	});
},

self.createPerson= function () {
	var url = '/api/person/';
	$.ajax({
		url: url,
		type: 'POST',
		data: ko.toJSON(self),
		contentType: "application/json;chartset=utf-8",
		statusCode: {
			201: function () {
				self.getAll();
				self.clearForm();
				alert('Person was created');
			},
			400: function () {
				self.clearForm();
				alert('Error');
			}
		}
	});
},

En la actualización y creación de un elemento se comparten los cambios (por eso no lo separe acá), las dos acciones en el lado del servidor reciben un objeto de tipo Person, lo cual en el post anterior hacíamos creando un nuevo objeto, asignándoles propiedades y finalmente serializándolo, sin embargo Knockout ofrece una forma más sencilla de realizar este proceso, simplemente con ko.toJSON(modelo) se realiza la serialización del objeto (cool no?).

Bueno, lo anterior todo muy bonito… pero… y como relacionamos ese modelo con nuestro HTML dicho proceso tiene dos pasos:

Paso 1: Llamar el viewmodel y enlazarlo

En este caso, una vez la página este cargada, creamos un nuevo objeto de tipo PersonViewModel, luego con ko.applyBindings(modelo) aplicamos los bindgins y finalmente hacemos el llamado a la función que obtiene todos los elementos:

$(document).on("ready", function () {
    var personvm = new PersonViewModel();
    ko.applyBindings(personvm);
    personvm.getAll();
})

Paso 2: Definir los bindings en el HTML

Llego el momento de modificar nuestra vista, el nuevo código es:

<div class="hero-unit">
    <h2>Get All</h2>
    <div>
        <table id="tblList" class="table table-bordered table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Last Name</th>
                    <th>Twitter</th>
                </tr>
            </thead>
            <tbody data-bind="foreach: personList">
                <tr>
                    <td data-bind="text: Name"></td>
                    <td data-bind="text: LastName"></td>
                    <td data-bind="text: Twitter"></td>
                </tr>
            </tbody>
        </table>
    </div>
    <h2>Get one</h2>
    <div>
        <ul>
            <li>
                <label>Id:</label>
                <input type="text" id="txtIdSearch" data-bind="value:id" />
                <input type="button" id="btnSearch" value="Search" data-bind="click:getPersonById" />
            </li>
            <li>
                <label>Name:</label>
                <input type="text" id="txtName" data-bind="value:name" />
            </li>
            <li>
                <label>Last name:</label>
                <input type="text" id="txtLastName" data-bind="value:lastname" />
            </li>
            <li>
                <label>Twitter:</label>
                <input type="text" id="txtTwitter" data-bind="value:twitter" />
            </li>
            <li>
                <input type="button" id="btnCreate" value="Create" data-bind="click:createPerson" />
                <input type="button" id="btnDelete" value="Delete" data-bind="click:deletePersonById" />
                <input type="button" id="btnUpdate" value="Update" data-bind="click:updatePerson" />
            </li>
        </ul>
    </div>
</div>
@section scripts
{
    <script type="text/javascript" src="@Url.Content("/Scripts/app/person.js")"></script>
}

El primer cambio se da en la definición del cuerpo de la tabla, en este caso haciendo uso de la propiedad data-bind le estamos asignamos la propiedad personList, y como usamos la función foreach entonces Knockout itera sobre cada elemento del array, luego en cada elemento td usando nuevamente la propiedad data-bind a la propiedad text le relacionamos la propiedad de la cual va a mostrar su valor.

Para los input, en el data-bind a la propiedad value le asignamos alguna de las propiedades del ViewModel, para los botones igualmente con data-bind al evento click le relacionamos una función del ViewModel.

El cambio más importante en esta sencilla implementación de Knockout, es que si revisamos de nuevo el archivo person.js NO se tiene ninguna dependencia ni referencia al DOM, lo cual ofrece una mejor estructura de nuestra aplicación y la puede hacer más mantenible y extensible.

Espero el post les haya gustado, y esperen la siguiente entrega de este serie sobre Web API!

Saludos!

Descarga el ejemplo!

[ASP.NET Web API] Web API III – Consumiendo el servicio

Posted on Actualizado enn

Y seguimos con un nuevo post sobre Web API, ya en los dos pasados revisamos como crear un servicio con ASP.NET Web API y además como crearlo para trabajar un CRUD completo, pero hasta ahora no hemos consumido el servicio, lo cual es el objetivo de este post.

El ejemplo que usaremos será el creado en el post anterior así que dale una mirada: Creando un CRUD

Primero vamos a modificar un poco el HTML de la vista Index del controlador Home, para el ejemplo todas las operaciones las vamos a realizar allí:

<div class="hero-unit">
    <h2>Get All</h2>
    <div>
        <table id="tblList" class="table table-bordered table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Last Name</th>
                    <th>Twitter</th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
    <h2>Get one</h2>
    <div>
        <ul>
            <li>
                <label>Id:</label>
                <input type="text" id="txtIdSearch" />
                <input type="button" id="btnSearch" value="Search" />
            </li>
            <li>
                <label>Name:</label>
                <input type="text" id="txtName" />
            </li>
            <li>
                <label>Last name:</label>
                <input type="text" id="txtLastName" />
            </li>
            <li>
                <label>Twitter:</label>
                <input type="text" id="txtTwitter" />
            </li>
            <li>
                <input type="button" id="btnDelete" value="Delete" />
                <input type="button" id="btnUpdate" value="Update" />
            </li>
        </ul>
    </div>
</div>
@section scripts
{
    <script type="text/javascript" src="@Url.Content("/Scripts/app/person.js")"></script>
}

Listo, ya tenemos el HTML listo, ahora a implementar cada funcionalidad:

Obtener todos

$(document).on("ready", function () {
    GetAll();
})

//Get all persons
function GetAll() {
    var item = "";
    $('#tblList tbody').html('');
    $.getJSON('/api/person', function (data) {
        $.each(data, function (key, value) {
            item += "
" + value.Name + "" + value.LastName + "" + value.Twitter + "
";
        });
        $('#tblList tbody').append(item);
    });
};

Lo primero que hacemos es validar que la página se ha cargado totalmente, luego realizamos un llamado a la función GetAll la cual se encarga de realizar la petición a la acción GetPerson() del controlador que retorna todo el conjunto de resultados, y cómo lo hace?, como estamos usando la función getJSON entonces ya la petición al verbo Get, y como no le estamos enviando parámetros entonces responde la acción que no recibe ninguno…si GetPeron(); luego con el $.each recorremos los elementos y llenamos la tabla.

Obtener un elemento

Ahora si queremos obtener 1 solo elemento, debemos pasarle el id que deseamos buscar, ya que el la acción en el controlador lo recibe GetPerson(Int32 id), entonces añadimos la siguiente función:

function GetPersonById(idPerson) {
    var url = '/api/person/' + idPerson;
    $.getJSON(url)
        .done(function (data) {
            $('#txtName').val(data.Name);
            $('#txtLastName').val(data.LastName);
            $('#txtTwitter').val(data.Twitter);
        })
        .fail(function (erro) {
            ClearForm();
        });
};

y al botón de buscar un manejador para el evento click:

$('#btnSearch').on('click', function () {
	GetPersonById($('#txtIdSearch').val());
})

De nuevo usamos $.getJSON, y en este caso cuando la petición se ejecuta correctamente entra al done donde asignamos valores a los campos de texto con los datos de la persona retornada, en caso que se produzca algún error se va la ejecución por el fail.

Eliminar un elemento por su id

Pasamos ahora a la eliminación, y en este caso vamos a enviarle el id de la persona que deseamos eliminar, por lo que se usará la acción DeletePerson(Int32 id), como antes lo primero es la función que hace el llamado:

function DeletePersonById(idPerson) {
    var url = '/api/person/' + idPerson;
    $.ajax({
        url: url,
        type: 'DELETE',
        contentType: "application/json;chartset=utf-8",
        statusCode: {
            200: function () {
                GetAll();
                ClearForm();
                alert('Person with id: ' + idPerson + ' was deleted');
            },
            404: function () {
                alert('Person with id: ' + idPerson + ' was not found');
            }
        }
    });
}

luego el manejador para el botón y el evento click:

$('#btnDelete').on('click', function () {
	DeletePersonById($('#txtIdSearch').val());
})

En este caso hacemos uso de $.ajax, definimos la url y le adicionamos el id de la persona a eliminar, definimos el type (verbo Http a usar) en DELETE, y manejamos entonces el código Http como respuesta de consumir el servicio (statusCode), ya que si revisan la acción esta retorna un HttpResponseMessage, en donde se tiene un código 200 (HttpStatusCode.OK) cuando la eliminación es correcta o un 404 (HttpStatusCode.NotFound) cuando no se encuentra una persona con el id enviado o la eliminación falla.

Actualizar un elemento

Solo nos resta la actualización, así que primero la función:

function UpdatePerson(idPerson,person) {
    var url = '/api/person/' + idPerson;
    $.ajax({
        url: url,
        type: 'PUT',
        data: person,
        contentType: "application/json;chartset=utf-8",
        statusCode: {
            200: function () {
                GetAll();
                ClearForm();
                alert('Person with id: ' + idPerson + ' was updated');
            },
            404: function () {
                ClearForm();
                alert('Person with id: ' + idPerson + ' was not found');
            },
            400: function () {
                ClearForm();
                alert('Error');
            }
        }
    });
}

luego el manejador para el botón y el evento click:

$('#btnUpdate').on('click', function () {
	var person = new Object();
	person.id = $('#txtIdSearch').val();
	person.name = $('#txtName').val();
	person.lastname = $('#txtLastName').val();
	person.twitter = $('#txtTwitter').val();
	UpdatePerson(person.id, JSON.stringify(person));
})

Al igual que para la eliminación usamos $.ajax pero en este caso el type lo definimos con el verbo PUT, en la url le pasamos el id de la persona y en el parámetro data le enviamos el objeto person y finalmente de nuevo validamos la respuesta usando los códigos Http, 200 (HttpStatusCode.OK), 404 (HttpStatusCode.NotFound) y 400 (HttpStatusCode.BadRequest).

En el llamado a la función creamos un nuevo objeto al cual le definimos los propiedades y sus correspondientes valores (mismos nombres que las propiedades en la clase C#) y la serializamos con JSON.stringify.

Insertar un nuevo elemento

Como última acción a implementar, ahora vamos a crear una nueva persona, primero entonces la función que hace el llamado:

function CreatePerson(person) {
    var url = '/api/person/';
    $.ajax({
        url: url,
        type: 'POST',
        data: person,
        contentType: "application/json;chartset=utf-8",
        statusCode: {
            201: function () {
                GetAll();
                ClearForm();
                alert('Person with id: ' + idPerson + ' was updated');
            },
            400: function () {
                ClearForm();
                alert('Error');
            }
        }
    });
}

El manejador para el botón y el evento click:

$('#btnCreate').on('click', function () {
	var person = new Object();
	person.name = $('#txtName').val();
	person.lastname = $('#txtLastName').val();
	person.twitter = $('#txtTwitter').val();
	CreatePerson(JSON.stringify(person));
})

Y de nuevo aparece en escena $.ajax en esta ocasión con el type POST, y manejamos la respuesta de nuevo con códigos Http, el 201 (HttpStatusCode.Created) que indica que se creo el elemento y el 400 para el error.

Finalmente la sencilla aplicación se verá como:

crud

Y hasta llegamos por esta vez, espero les guste el post y les ayude a seguir con Web API, en el próximo post haremos un refactoring en el lado de cliente para usar Knockoutjs y su facilidad para implementar propiedades observables.

Saludos!

Descarga el ejemplo!

[ASP.NET] Como obtener el Gravatar del usuario

Posted on Actualizado enn

Hola a todos, en los próximos post que serán cortos quiero mostrar algunas pequeñas funcionalidades que nos ayudarán a realizar pequeñas mejoras a nuestras aplicaciones y/o implementar funcionalidades muy especificas a nuestros desarrollos.

En este post vamos a revisar como usando la dirección de correo electrónico obtenemos el Gravatar del usuario, primero que todo si alguno no lo sabe el Gravatar es una imagen que podemos utilizar en algunas páginas Web, más info: Gravatar.

Ya entrando en materia lo primero es tener un muy sencillo formulario con una caja de texto para ingresar el email y un botón para obtener el Gravatar:

<h1>Gravatar</h1>
<hr />
<asp:Label ID="lblEmail" runat="server" Text="Email:"></asp:Label>
<asp:TextBox ID="txtEmail" runat="server" Width="250px"></asp:TextBox>
<asp:Button ID="btnGetGravatar" runat="server" OnClick="BtnGetGravatarClick" Text="Get Gravatar" />
<br />
<asp:Image ID="imgGravatar" runat="server" />

Luego necesitamos añadir el paquete ASP.NET Web Helpers Library utilizando Nuget:

image

Y finalmente en el evento click del botón:

protected void BtnGetGravatarClick(object sender, EventArgs e)
{
	if (!string.IsNullOrEmpty(txtEmail.Text))
	{
		var gravatar = Microsoft.Web.Helpers.Gravatar.GetUrl(txtEmail.Text);
		imgGravatar.ImageUrl = gravatar;
	}
}

Si probamos e ingresamos un correo, en este caso he ingresado el mío tenemos:

image

Espero les sea de utilidad, les dejo el ejemplo, saludos!

Descarga el ejemplo!