martes, 9 de julio de 2013

Validando email en SQL



En ocasiones necesitamos verificar que un correo electrónico tenga un formato válido, es decir una bandeja y su dominio respectivos divididos por una arroba.

En esta entrada veremos una forma sumamente simple pero que servirá como punta de lanza para que utilices el potencial que tienen los ensablados del .NET Framework para integrarlos en SQL Server.

Hay que decir y dejar muy claro que no es la panacea para las cosas complicadas de SQL, es simplemente un punto más, una herramienta más que tenemos a la mano para poder utilizar y facilitar nuestro trabajo.

Hacer la función que valide el formato del correo electrónico se podría describir en un proceso muy simple:
  1. Crear la función como estática en .NET.
  2. Crear un objeto ASSEMBLY en nuestra base de datos.
  3. Crear la función que será mapeada hacia un elemento almacenado en el ASSEMBLY.
Creando función en .NET

Vamos a crear un proyecto de tipo Class Library en Visual Studio y eliminaremos el archivo Class1.cs que trae por defecto la plantilla.

Vamos agregar una clase que alojará nuestra función estática que realizará la validación del correo electrónico, el código quedaría de la siguiente manera:

using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;

namespace EmailValidator
{
    public class Validators
    {
        [SqlFunction]
        public static SqlBoolean ValidateEmail(SqlString email)
        {
            return SqlBoolean.Parse(Regex.IsMatch(email.Value, @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$").ToString());
        }
    }
}


El atributo SqlFunction que está calificando a la función ValidateEmail le hace saber a SQL Server que ahí hay una función que puede utilizarse.

Debemos manejar tipos de dato de SQL Server, es por ello que la función devuelve un valor de tipo SqlBoolean y recibe una cadena del tipo SqlString los cuales están definidos en el espacio de nombres System.Data.SqlTypes.

La clase RegEx está definida en el espacio de nombres System.Text.RegularExpressions y su función IsMatch devuelve un valor booleano indicando que su primer parámetro cumple con el patrón definido en el segundo parámetro.

Antes de continuar al siguiente paso de esta entrada, deberemos anotar la ruta completa para llegar a nuestra función EmailValidator.Validators.ValidateEmail.

Vamos a compilar nuestro proyecto y deberá generar el DLL con nuestra función incluida.

Debemos validar el framework bajo el que está siendo construido nuestro DLL, si va a ser utilizado en SQL Server 2008 deberá estar compilado en el framework 2.0, en caso que vayamos a utilizarlo en SQL Server 2012 puede estar compilado en el framework 4.0

Crear ASSEMBLY en base de datos

Vamos a obtener la ruta completa para llegar al DLL que acabamos de generar y la utilizaremos para importarlo en nuestra base de datos.

Vamos a abrir un nuevo query que utilice la base de datos de usuario de tu elección, en mi caso tengo una base de datos llamada ExpertsExchange.

USE ExpertsExchange
GO
CREATE ASSEMBLY Validators
FROM 'D:\Personal\Blogger\EmailValidator\EmailValidator\bin\Debug\EmailValidator.dll'
WITH PERMISSION_SET = SAFE


Nuestro assembly se va a llamar Validators y tendrá un conjunto de permisos SAFE, ¿qué significa esto?

SAFE (valor por defecto) es el nivel más recomendado dado que no permite que la función o funciones incluidas en el assembly tengan acceso al sistema de archivos, a la red, a las variables de entorno o al registro de windows.

EXTERNAL_ACCESS le permite al assembly utilizar recursos externos como son archivos, red, variables de entorno y registro de windows.

UNSAFE le permite al assembly utilizar recursos externos tal como lo hace EXTERNAL_ACCESS y aparte le permite al assembly ejecutar código no administrado (código no hecho en .NET)

Crear función mapeada

Ahora vamos a crear una función escalar que reciba como parámetro el correo electrónico que se quiere validar y devuelva un BIT que indique si es válido o no.

La parte interesante es que sólo declararemos la firma de la función, el cuerpo del código ya está definido en nuestro assembly.

USE ExpertsExchange
GO
CREATE FUNCTION udfValidateEmail(
    @Email NVARCHAR(255)
)
RETURNS BIT
AS EXTERNAL NAME Validators.[EmailValidator.Validators].ValidateEmail


Notemos que la firma es como la de cualquier función que crearíamos en SQL Server, la diferencia radica en el código que está después de la especificación del tipo de resultado de nuestra función.

Aun cuando un correo electrónico no lleva caracteres extraños, es imprescindible utilizar NVARCHAR dado que las cadenas que maneja .NET son UNICODE y por lo tanto SQL Server envía NVARCHAR a .NET para que éstas sean manejadas.

¿Cómo saber qué poner en external name?, es fácil:

SQLAssemblyName.[FullNamespace.Class].NETFunctionName


Ahora vamos a probar nuestra función con algunos correos electrónicos tanto válidos como no válidos:

USE ExpertsExchange
GO
SELECT dbo.udfValidateEmail(N'valid@mail.com')
SELECT dbo.udfValidateEmail(N'invalid@mail..com')
SELECT dbo.udfValidateEmail(N'invalid@mail')
SELECT dbo.udfValidateEmail(N'valid@mail.com.mx')


Si recibes un mensaje de error indicando que el .NET Framework está deshabilitado es porque SQL Server no acepta la ejecución de código CLR externo por defecto.    Vamos a habilitarlo con el siguiente código:

sp_configure 'clr enabled', 1
GO
RECONFIGURE


Vuelve a probar la función y verás que el primer y cuarto correos son válidos y el segundo y tercero no lo son.

Espero te haya resultado de utilidad esta entrada, la integración de funciones CLR a SQL Server es un tema apasionante debido a la gran diversidad de soluciones que se pueden implementar via .NET, pero ten mucho cuidado, esta no es la solución a todos nuestros problemas, necesitamos hacer pruebas que demuestren que el uso de recursos y la velocidad de respuesta son las deseadas.

jueves, 4 de julio de 2013

Emails a tabla



En esta entrada quiero mostrar una forma para recibir una lista de correos electrónicos divididos por coma o por punto y coma y separarlos en filas independientes en una tabla, una vez desarrollado el algoritmo crearemos la función que podremos utilizar de forma muy simple en cualquier base de datos.

Para entender plenamente el ejercicio creo muy importante el explicar brevemente cómo operan las diferentes funciones para manejo de cadenas que utilizaremos en el código.

REPLACE(string, stringToFind, stringToReplace)

Esta función reemplaza todas las apariciones de una cadena en específico por la cadena que nosotros queramos.     Por ejemplo, supongamos que queremos sustituir todas las letras "a" con acento por "á", el código sería el siguiente:

SELECT REPLACE('Me salieron ámpulas por subirme al árbol', 'á', 'á')

CHARINDEX(stringToFind, string[, startIndex])

Esta función busca la primera posición de izquierda a derecha en la que encuentre la cadena stringToFind dentro de string.    El tercer parámetro opcional indica el índice a partir del cual queremos comenzar la búsqueda de la cadena stringToFind.    Por ejemplo, queremos obtener el índice de la primera ",":

SELECT CHARINDEX(',', 'Primero, segundo y tercero')

La función devolverá un valor 0 cuando no haya encontrado la cadena que se estaba buscando.

RTRIM(string) y LTRIM(string)

La función RTRIM elimina todos los espacios en blanco que encuentre a la derecha de la cadena string, es decir, todos los espacios en blanco de relleno que pueda tener la cadena, en inglés encontramos que a esto se le llama trailing spaces.

La función LTRIM elimina todos los espacios en blanco que encuentre al inicio de la cadena string.

La combinación de ambas funciones RTRIM(LTRIM(string)) tiene un resultado como el de la función Trim de un objeto String de .NET

Supongamos que queremos eliminar todos los espacios a la izquierda y a la derecha de una palabra:

SELECT RTRIM(LTRIM('    con espacios antes y después    '))

SUBSTRING(string, startIndex, length)

Esta función extrae length caracteres de la cadena string a partir del caracter startIndex.    Por ejemplo, queremos extraer la palabra "penca" de la siguiente frase:

SELECT SUBSTRING('Grabé en la penca de un maguey', 13, 5)

La palabra "penca" comienza en el caracter número 13 y tiene una longitud de 5.

Es importante notar que en SQL Server las cadenas comienzan con el índice 1, no como en .NET donde comienzan en el índice 0.

Bueno, una vez que tenemos estas funciones en mente comencemos con la programación de nuestra función.

Vamos a comenzar por declarar una variable donde se encuentren los correos electrónicos que recibiriemos como parámetro en la función:

DECLARE @Emails VARCHAR(2000) = 'email1@domain1.com, email2@domain2.com; email3@domain3.com '

Como podrás notar el primer y segundo correo están divididos por una coma mientras que el segundo y tercer correo están divididos por un punto y coma.    También es importante ver que hay un espacio en blanco al final de la cadena.

Esta serie de cosas las puse así para tomar en cuenta posibles errores por parte del usuario al momento de hacerle llegar el parámetro a la función.

Vamos a cambiar los ";" por "," y así esté homogéneo el divisor de correos:

SET @Emails = REPLACE(@Emails, ';', ',')

Vamos a necesitar un par de variables, una que lleve la posición actual de análisis de la cadena de correos y otra que tenga la posición donde se encuentra la ",":

DECLARE @CurrentPos INT = 1
DECLARE @CommaPos INT = CHARINDEX(',', @Emails)


Vamos a crear una tabla temporal para ir guardando los correos, esta tabla será declarada como parte de la firma de nuestra función:

DECLARE @Result TABLE (
    Email VARCHAR(255)
)


El algoritmo es sencillo, mientras siga habiendo "," hay más correos por analizar, debido a ello estableceremos un ciclo WHILE:

WHILE @CommaPos > 0
BEGIN
END


Cuando queramos extraer el correo electrónico, deberemos sacar del caracter @CurrentPos hasta el índice donde se encuentra la "," pero sin incluir la ",".    También recordemos que tenemos que quitar cualquier espacio en blanco al inicio o al final del correo:

INSERT INTO @Result (Email)
VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, @CommaPos - @CurrentPos))))


Debemos poner especial atención al tercer parámetro, en .NET generalmente le colocaríamos un -1 pero como en SQL Server la posición del primer caracter es 1, no es necesario realizar ese pequeño ajuste.

Vamos a actualizar nuestras variables de navegación @CurrentPos y @CommaPos:

SET @CurrentPos = @CommaPos + 1
SET @CommaPos = CHARINDEX(',', @Emails, @CurrentPos)


Notemos que en este caso estamos utilizando el tercer parámetro de la función CHARINDEX, esto se debe a que la búsqueda de la "," queremos que se realice después de la que ya habíamos encontrado.

Nuestro WHILE quedaría de la siguiente manera:

WHILE @CommaPos > 0
BEGIN
    INSERT INTO @Result (Email)
    VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, @CommaPos - @CurrentPos))))

    SET @CurrentPos = @CommaPos + 1
    SET @CommaPos = CHARINDEX(',', @Emails, @CurrentPos)
END


Al salir del WHILE estaremos en condiciones de obtener el último correo con el siguiente código:

INSERT INTO @Result (Email)
VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, LEN(@Emails) - @CurrentPos + 1))))


Nuestro código final debe quedar de la siguiente manera:

DECLARE @Emails VARCHAR(2000) = 'email1@domain1.com, email2@domain2.com; email3@domain3.com '
SET @Emails = REPLACE(@Emails, ';', ',')

DECLARE @CurrentPos INT = 1
DECLARE @CommaPos INT = CHARINDEX(',', @Emails)

DECLARE @Result TABLE (
    Email VARCHAR(255)
)

WHILE @CommaPos > 0
BEGIN
    INSERT INTO @Result (Email)
    VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, @CommaPos - @CurrentPos))))

    SET @CurrentPos = @CommaPos + 1
    SET @CommaPos = CHARINDEX(',', @Emails, @CurrentPos)
END

INSERT INTO @Result (Email)
VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, LEN(@Emails) - @CurrentPos + 1))))

SELECT * FROM @Result


Una vez que lo hayamos probado y verifiquemos que funciona tal como lo esperábamos, vamos a encapsularlo en una función para poder reutilizarlo:

CREATE FUNCTION udfSplitEmails(
    @Emails VARCHAR(2000)
)
RETURNS @Result TABLE(
    Email VARCHAR(255)
)
AS
BEGIN
    SET @Emails = REPLACE(@Emails, ';', ',')

    DECLARE @CurrentPos INT = 1
    DECLARE @CommaPos INT = CHARINDEX(',', @Emails)

    WHILE @CommaPos > 0
    BEGIN
        INSERT INTO @Result (Email)
        VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, @CommaPos - @CurrentPos))))

        SET @CurrentPos = @CommaPos + 1
        SET @CommaPos = CHARINDEX(',', @Emails, @CurrentPos)
    END

    INSERT INTO @Result (Email)
    VALUES (RTRIM(LTRIM(SUBSTRING(@Emails, @CurrentPos, LEN(@Emails) - @CurrentPos + 1))))

    RETURN
END


Ahora se podrá utilizar de la siguiente manera:

SELECT * FROM udfSplitEmails('email1@domain1.com, email2@domain2.com; email3@domain3.com ')

Espero te haya resultado de utilidad esta entrada, es una función que se puede incluso integrar en la base de datos model para que forme parte del machote de una base de datos cuando sea creada en el servidor.

miércoles, 12 de junio de 2013

WCF REST Service con JQuery



En esta nueva entrada veremos la forma de exponer un servicio WCF a través de un endpoint que soportará peticiones REST desde un cliente jquery.

¿Por qué un servicio WCF?, recordemos de la implementación del servicio está totalmente separada de la forma de comunicación, esto significa que podremos utilizar la misma implementación para exponer nuestra información a clientes de otras plataformas o a computadoras o servidores que se encuentren en la red interna de nuestro host.    Todo esto sin tener que estar generando nuevas implementaciones.

Es importante notar que utilizaremos la base de datos AdventureWorks, en caso de que no la tengas la puedes obtener de la siguiente dirección: http://msftdbprodsamples.codeplex.com/releases/view/93587

Abriremos el Visual Studio 2010 ejecutándolo como administrador (para evitar el uso de netsh para apartar el puerto) y comenzaremos por crear una solución en blanco llamada WCFJQuery, en esta solución vamos a agregar una biblioteca de clases y la llamaremos AWDB.

AWDB.

En este proyecto vamos a eliminar el archivo Class1.cs que trae por defecto y vamos a agregar un nuevo elemento de tipo ADO.NET Entity Data Model.    Si ya sabes cómo crear un modelo entonces sáltate hasta la creación del siguiente proyecto, la indicación es crear el modelo llamado AW con Production.ProductSubcategory y Production.Product con las entidades pluralizadas, de lo contrario sigue conmigo.

Al nuevo elemento de tipo ADO.NET Entity Data Model vamos a llamarlo AW.edmx, en el asistente elegimos la opción "Generate from database", vamos a crear la conexión hacia AdventureWorks utilizando autenticación de Windows dando clic en el botón "New connection", escribimos el nombre del servidor y elegimos la base de datos AdventureWorks de la lista desplegable, damos clic en "Test connection" y deberá salir un mensaje como el que se ve en la imagen.


Damos clic en Ok para cerrar el mensaje que dice que la conexión fue exitosa y damos clic en Ok para crear la conexión, el nombre de la cadena de conexión será AdventureWorksEntities.    Al dar clic en siguiente nos mostrará la lista de objetos utilizables y deberemos elegir las tablas Production.ProductSubcategory y Production.Product, elegimos la opción "Pluralize or singularize ..." y al modelo lo nombraremos AWModel; la configuración deberá quedar como se ve en la siguiente imagen:


Damos clic en finalizar y ya tenemos nuestro modelo terminado.

AWServices.

Vamos a agregar un proyecto de tipo biblioteca de clases y lo llamaremos AWServices, eliminamos el archivo Class1.cs que trae por defecto.    Agregamos referencia al proyecto AWDB, también tenemos que agregar referencia a System.Data.Entity y System.ServiceModel.Web.

También agregamos un elemento de tipo WCF Service llamado ProductsCatalog y una clase llamada Product que tenga las propiedades públicas Id (int), Name (string) y ListPrice (decimal) siendo todas de lectura-escritura, recuerda hacerla pública, por defecto al agregar una clase la declara como privada.

Ahora agregamos otra clase que se llame Subcategory que tendrá las propiedades públicas Id (int) y Name (string) siendo de lectura-escritura, recuerda hacerla pública.

En IProductsCatalog vamos a eliminar la operación DoWork que aparece por defecto y vamos a agregar una nueva que nos permitirá obtener los productos que pertenezcan a una subcategoría en particular con el siguiente código:

[OperationContract]
List<Product> GetProducts(int subcategoryId);

También vamos a agregar otra operación que nos devolverá la lista de subcategorías que estén registradas en la base de datos con el siguiente código:

[OperationContract]
List<Subcategory> GetSubcategories();

Ya que tenemos el contrato de nuestro servicio, ahora vamos a crear la implementación del mismo en el archivo ProductsCatalog.cs.

Eliminamos el procedimiento DoWork que trae por defecto, damos clic derecho en la interfaz que implementa y elegimos Implement Interface - Implement Interface, esto nos creará los stub necesarios para la implementación de las funciones que tiene la interfaz.

En la función GetSubcategories vamos a proyectar las subcategorías que están en la base de datos hacia una lista de objetos de tipo Subcategory con el siguiente código:

public List<Subcategory> GetSubcategories()
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
    using (AWDB.AdventureWorksEntities db = new AWDB.AdventureWorksEntities())
    {
        return db.ProductSubcategories.Select(sc => new Subcategory()
        {
            Id = sc.ProductSubcategoryID,
            Name = sc.Name
        }).ToList();
    }
}

En la función GetProducts buscaremos a los productos que pertenecen a la subcategoría cuyo identificador recibimos como parámetro y los proyectamos hacia una lista de objetos de tipo Product con el siguiente código:

public List<Product> GetProducts(int subcategoryId)
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
    using (AWDB.AdventureWorksEntities db = new AWDB.AdventureWorksEntities())
    {
        return db.Products.Where(p => p.ProductSubcategoryID == subcategoryId).Select(p => new Product()
        {
            Id = p.ProductID,
            Name = p.Name,
            ListPrice = p.ListPrice
        }).ToList();
    }
}

En ambas operaciones encontraremos una línea que agrega una cabecera Access-Control-Allow-Origin con un valor "*", esto es para que pueda ser utilizado desde cualquier dominio.

Hasta ahora nuestro servicio ya está listo para ser alojado y utilizado, pero debemos hacerle unas pequeñas modificaciones o ajustes a la implementación para que soporte REST.    Vamos a agregar el espacio de nombres System.ServiceModel.Web en la implementación del servicio y vamos a agregar el siguiente atributo a la función GetSubcategories:

[WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "Subcategories")]
public List<Subcategory> GetSubcategories()

Con esta configuración el mensaje que se devolverá a los clientes que realicen el request a la dirección Subcategories estará en formato JSON con lo que podremos utilizarlo de forma muy sencilla desde javascript.

También vamos a ajustar la función GetProducts para que reciba solicitudes utilizando REST:

[WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "Products?scid={subcategoryId}")]
public List<Product> GetProducts(int subcategoryId)

En esta configuración podemos notar que estamos agregando una variable por query string que se llamará scid y cuyo valor estará asignado al parámetro subcategoryId que solicita nuestra función.
Compila la solución y corrije cualquier detalle que aparezca en la lista de errores, si seguiste al pie de la letra lo que hemos hecho hasta ahora no deberás tener problema alguno.

AWHost.

Vamos a agregar un proyecto de tipo Aplicación de Consola y lo llamaremos AWHost, en este proyecto se estará alojando nuestro servicio AWServices.

Vamos a agregar referencia a System.ServiceModel y al proyecto AWServices.    Agregamos un nuevo elemento de tipo Application Configuration File y le dejaremos el nombre por defecto que mostrará: app.config, debemos abrir este archivo y pegar la cadena de conexión que tenemos en el proyecto AWDB en su archivo app.config, posteriormente registraremos nuestro servicio ProductsCatalog utilizando el siguiente código:

<system.serviceModel>
    <behaviors>
        <endpointBehaviors>
            <behavior name="HelpEnabled">
                <webHttp helpEnabled="true"/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <services>
        <service name="AWServices.ProductsCatalog">
            <endpoint address="" binding="webHttpBinding" contract="AWServices.IProductsCatalog" behaviorConfiguration="HelpEnabled"></endpoint>
            <host>
                <baseAddresses>
                    <add baseAddress="http://JorgeToriz-PC:8001/AWServices"/>
                </baseAddresses>
            </host>
        </service>
    </services>
</system.serviceModel>

Deberás modificar el atributo baseAddress para que haga referencia al nombre de tu equipo, en el código que estoy poniendo hace referencia a mi equipo.

El behavior HelpEnabled permitirá que la dirección http://JorgeToriz-PC:8001/AWServices/help exponga todos los servicios que están a la disposición, en esta descripción aparecerá la forma en que deben llamarse y también los resultados que devolverán las funciones.

En el proyecto necesitamos referencia a System.ServiceModel.Web para poder alojar este servicio REST, para poder hacerlo necesitamos que el proyecto utilice el .NET Framework 4 y no el .NET Framework 4 Client Profile.    Para cambiar esta configuración abriremos las propiedades del proyecto y en el tab Application buscaremos la opción "Target Framework", de la lista desplegable deberemos elegir ".NET Framework 4", el IDE nos mostrará un mensaje de aviso, vamos a darle que "Sí" para que realice los cambios necesarios.

Ahora sí, vamos a agregar referencia a System.ServiceModel.Web y vamos a abrir nuestro archivo Program.cs, en él agregaremos el espacio de nombres System.ServiceModel.Web y escribiremos el siguiente código para poner disponible nuestro servicio:

static void Main(string[] args)
{
    WebServiceHost host = new WebServiceHost(typeof(AWServices.ProductsCatalog));
    host.Open();
    Console.WriteLine("El servicio ya está en ejecución, presiona cualquier tecla para detenerlo");
    Console.ReadKey();
    host.Close();
}

Al ejecutar el proyecto (sin debug) nos deberá mostrar una pantalla como esta:


Si visitamos la URL http://JorgeToriz-PC:8001/AWServices/help (cambiando JorgeToriz-PC por el nombre de tu equipo) deberá mostrar el navegador nuestra lista funciones como en la siguiente imagen:


Si damos clic en Products o Subcategories nos mostará la información que devuelve cada una de las URL.

AWClient.

Vamos a agregar un sitio web vacío a nuestra solución al que llamaremos AWClient, en ese proyecto crearemos una carpeta llamada js donde meteremos el script de jquery, si no lo tienes puedes descargar jquery de esta liga (http://code.jquery.com/jquery-1.10.1.min.js).

Dentro de nuestro sitio web vamos a agregar una página HTML a la que llamaremos ProductsCatalog, enlla agregaremos referencia a jquery y también agregaremos el siguiente código que hace llamadas utilizando REST para poder obtener el catálogo de productos:

<table>
    <tr>
        <td>Subcategories</td>
        <td>
            <select id="ddlSubcategories" onchange="return loadProducts()"></select>
            <script language="javascript" type="text/javascript">
                function loadProducts() {
                    $.ajax({
                        url: 'http://JorgeToriz-PC:8001/AWServices/Products?scid=' + $('#ddlSubcategories').val(),
                        type: 'GET',
                        success: loadProducts_success
                    });
                    return false;
                }
                function loadProducts_success(d) {
                    var products = '<table border="1"><tr><td>Id</td><td>Name</td><td>List price</td></tr>';
                    for (var i = 0; i < d.length; i++) {
                        products += '<tr>';
                        products += '<td>' + d[i].Id + '</td>';
                        products += '<td>' + d[i].Name + '</td>';
                        products += '<td>' + d[i].ListPrice + '</td>';
                        products += '</tr>';
                    }
                    products += '</table';
                    $('#divProducts').html(products);
                }
                function loadSubcategories() {
                    $.ajax({
                        url: 'http://JorgeToriz-PC:8001/AWServices/Subcategories',
                        type: 'GET',
                        success: loadSubcategories_success
                    });
                }
                function loadSubcategories_success(d) {
                    var subcategories = $('#ddlSubcategories');
                    for (var i = 0; i < d.length; i++) {
                        $('<option value="' + d[i].Id + '">' + d[i].Name + '</option>').appendTo(subcategories);
                    }
                    loadProducts();
                    subcategories.focus();
                }
                $(document).ready(loadSubcategories);
            </script>
        </td>
    </tr>
</table>
<div id="divProducts"></div>

Resumen.

Como podemos ver no es tan complicado el exponer un servicio WCF para ser utilizado como REST desde javascript, un punto fino es permitir las llamadas desde otros dominios utilizando la cabecera Access-Control-Allow-Origin en el WebOperationContext actual, mientras que el otro punto importante es elegir bien la estructura de las direcciones para REST y configurarlas en la implementación del servicio utilizando el atributo WebGet.

Espero te haya resultado de utilidad esta entrada.