Crear un servicio Web con un editor de textos |
En este artículo te voy a explicar cómo crear un
servicio Web usando un editor de textos.
Veremos "paso a paso" el código que tenemos que escribir. También veremos
cómo compilar desde la línea de comandos y cómo podemos crear un cliente que
use este servicio Web, todo por medio de la línea de comandos.
¿Por qué usar un editor de textos en lugar de Visual Studio .NET?
No es un capricho. En principio vamos a crear el servicio Web con un editor de textos para "saltarnos" los extras que el Visual Studio .NET añade a un proyecto del tipo Servicio Web, ya que, al menos desde mi punto de vista, le añade cosas de más... que realmente no nos son necesarias.
¿Qué hace Visual Studio que no es necesario?
Cuando creamos un nuevo proyecto del tipo Servicio Web en Visual Studio, éste crea el servicio Web en nuestro servidor local (localhost), pero lo hace creando un "sitio Web", es decir, el servicio Web lo crea en algo parecido a lo que sería un sitio Web "completo", para que lo entiendas, este sitio Web en el que estás leyendo este artículo sería el equivalente al sitio Web que Visual Studio crea para cualquier tipo de proyecto ASP.NET, por tanto, creo que es más interesante que no cree algo que al final acabe de confundirnos, ya que lo que nos interesa es crear el servicio Web para después usarlo en el sitio que tengamos en Internet.
¿Perderemos funcionalidad al hacerlo "a mano"?
No. Porque el hecho de que usemos un editor de textos no nos impedirá que hagamos lo mismo que hace Visual Studio, es decir, vamos a crear el servicio Web usando un ensamblado (DLL) con el código que dicho servicio Web usará, que es al fin y al cabo lo que hace Visual Studio.
Aunque también te explicaré cómo hacerlo más fácil, es decir, sin usar un ensamblado DLL. En ese caso todo el código lo incluiremos en el fichero que finalmente publicaremos en nuestro sitio de Internet.
Crear un servicio Web en un solo fichero
Vamos a empezar viendo cómo crear un servicio Web que incluye todo lo que necesita en un mismo fichero, después "desglosaremos" el contenido del servicio Web para que podamos crear un ensamblado con el código.
Nota:
El servicio Web que vamos a crear usará la base de datos Northwind. El servicio Web recibirá como parámetro una cadena de selección con los datos que queremos (la típica cadena SELECT) y devolverá un DataSet con los datos solicitados.Lo primero que haremos es crear un fichero con la extensión .asmx, esta es la extensión usada para los servicios Web, en nuestro ejemplo, el fichero se va a llamar: Northwind.asmx
En este fichero vamos a crear una clase llamada Northwind con un solo método: Empleados.
Este método recibirá un parámetro del tipo String en el que podemos indicar la cadena de selección o bien podemos usar una cadena vacía, en cuyo caso se usará una cadena de selección que devolverá algunos campos de la tabla Employees. El valor devuelto por esa función será un objeto DataSet con los datos indicados en la cadena de selección.Nota:
Un servicio Web realmente lo que contiene es una clase con el código que usaremos para darle "funcionalidad", por tanto, siempre debemos crear una clase con cada servicio Web. Aunque el código del servicio Web puede contener más de una clase, solamente una es la que se expone como parte a usar del servicio Web.Para que .NET se entere de que lo que contiene el fichero es un servicio Web debemos usar una directiva de ASP.NET:
<%@ WebServiceCon esto le indicamos que lo que contiene el fichero es un servicio Web, ya que no solo basta que la extensión sea .asmx, aunque si no tiene esa extensión no lo reconocerá como servicio Web.
A continuación, en la misma directiva ASP.NET le decimos el lenguaje de programación que vamos a usar, en el caso de VB indicaremos:
Language = "VB"Si vamos a usar C#, sustituimos VB por C#:
Language = "C#"Por último tenemos que indicar el nombre de la clase en la que escribiremos el código que utilizará este servicio Web:
Class = "NorthwindSW"Y cerramos la directiva de ASP.NET:
%>A partir de este momento, lo que debemos escribir es el código en el lenguaje indicado para definir la clase y los métodos que el servicio Web expondrá públicamente. Esos métodos serán los que podremos usar en una aplicación cliente.
Como vamos a acceder a una base de datos de SQL Server, añadiremos los espacios de nombres en los que están definidas las clases que vamos a usar, para Visual Basic:
Imports System.Data
Imports System.Data.SqlClientPara C#:
using System.Data; using System.Data.SqlClient;Como te comentaba antes, cuando definimos un servicio Web realmente estamos definiendo una clase y dentro de esa clase indicaremos que métodos (funciones) queremos exponer, para exponer esas funciones como parte del servicio Web, tendremos que usar unos atributos que están definidos en el espacio de nombres System.Web.Services, por tanto, para ahorrarnos la escritura de algunos caracteres en nuestro código, también incluiremos una importación de ese espacio de nombres:
Imports System.Web.Services ' para VB using System.Web.Services; // para C#Nota:
Como ya sabrás, (si no lo sabes te lo recuerdo yo), las importaciones de espacios de nombres sirve para ahorrarnos escribir el nombre completo de una clase. Ese nombre completo está formado por el espacio de nombres y el nombre de la clase. Es como si incluyéramos un directorio en el PATH del sistema, así podremos acceder a las clases que estén en ese "directorio/espacio de nombres", sin necesidad de tener que estar repitiendo el sitio donde están las clases que queremos usar.
Por ejemplo, para acceder a la clase DataSet, si tenemos la importación de System.Data podemos usarla de forma directa: DataSet, o también usando el nombre completo: System.Data.DataSet.Una vez indicadas las importaciones de espacios de nombres, definimos la clase, a la que le vamos a aplicar el atributo WebServiceAttribute para indicar el espacio de nombres del servicio Web (recomendable al 100%) y una descripción de lo que hace este servicio Web, con idea de que si alguien lo "localiza", sepa para que sirve.
Las clases que definen un servicio Web deben estar derivadas de System.Web.Services.WebService, por tanto debemos indicarlo también:Para Visual Basic:
<WebService(Namespace:="http://elGuille/ServiciosWeb/", _ Description:="Acceso a la base de datos Northwind (local) desde un servicio Web")> _ Public Class NorthwindSW Inherits System.Web.Services.WebServicePara C#:
[WebService(Namespace="http://elGuille/ServiciosWeb/", Description="Acceso a la base de datos Northwind (local) desde un servicio Web")] public class NorthwindSW : System.Web.Services.WebService {Nota:
A partir de aquí, te dejo adivinar cual es el código para Visual Basic .NET y cual es para C#. Así que, si ves dos líneas o trozos de código que son parecidos... usa el que tengas que usar dependiendo del lenguaje que hayas elegido.
Una pista: El código de VB no termina en punto y coma (;) ni usa llaves ({ o })
Si no te aclaras, no te preocupes, al final te muestro todo el código al completo, el ir mostrando el código en los dos lenguajes es para que veas que es prácticamente el mismo en VB que en C#, salvo por las diferencias sintácticas.Para acceder a la tabla de la base de datos usaremos un DataAdapter de SQL:
Private da As SqlDataAdapterprivate SqlDataAdapter da;Ahora definimos la función (o método) que se expondrá en el servicio Web, a estas funciones "expuestas" se las llama métodos Web y debemos aplicarle el atributo WebMethodAttribute, al que también le podemos añadir una descripción para que sepamos que es lo que hace. El único método Web que tendrá nuestra clase, será uno llamado Empleados el cual recibe una cadena como parámetro, y devuelve un objeto DataSet con los datos que hayamos obtenido al usar esa cadena como cadena de selección:
<WebMethod(Description:="Devuelve datos indicados en el parámetro de la base Northwind")> _ Public Function Empleados(sel As String) As DataSet[WebMethod(Description="Devuelve datos indicados en el parámetro de la base Northwind")] public DataSet Empleados(string sel) {Si el parámetro es una cadena vacía, usaremos una selección predeterminada para extraer los datos de la tabla Employees, aunque en la cadena de selección que pases por parámetro puedes usar la tabla de Northwind que quieras:
If sel = "" Then sel = "SELECT LastName, FirstName, Title, BirthDate FROM Employees" End Ifif( sel == "" ) sel = "SELECT LastName, FirstName, Title, BirthDate FROM Employees";Ahora creamos el objeto DataAdapter usando esta cadena de selección y las instrucciones necesarias para que se conecte con el servidor de SQL Server y la base de datos Northwind, en este ejemplo se utiliza la autenticación de Windows:
da = New SqlDataAdapter(sel, _ "integrated security=true; data source=(local); initial catalog=Northwind")da = new SqlDataAdapter(sel, "integrated security=true; data source=(local); initial catalog=Northwind");Si queremos usar un nombre de usuario y un password, también podríamos hacerlo:
da = New SqlDataAdapter(sel, _ "user id=usuario; password=clave; data source=(local); initial catalog=Northwind")da = new SqlDataAdapter(sel, _ "user id=usuario; password=clave; data source=(local); initial catalog=Northwind");
Nota:
En este sitio: http://www.connectionstrings.com/ puedes encontrar ejemplos de cadenas de conexión a distintos tipos de bases de datos, como SQL Server, Access, Oracle, MySQL, etc., además usando diferentes "proveedores": OleDb, Odbc, etc.Para conservar los datos, vamos a usar un DataSet, por tanto tendremos que crear un nuevo objeto de este tipo para después usarlo con el DataAdapter:
Dim ds As New DataSet()DataSet ds = new DataSet();Ahora tenemos que "llenar" el DataSet con los datos solicitados, para ello utilizamos el método Fill del DataAdapter. Como no nos fiamos de que todo vaya a funcionar bien, y para no provocar un desastre mayor, es recomendable a la hora de usar ese método, hacerlo dentro de un bloque Try/Catch para poder interceptar los posibles errores que se produzcan:
Try da.Fill(ds) Catch ex As Exception Throw ex End Trytry { da.Fill(ds); } catch(Exception ex) { throw ex; }Como puedes comprobar, si se produce una excepción, se la devolvemos al cliente que use nuestro servicio Web, así tendrá más cuidado a la hora de usar las cadenas de selección.
Por último devolvemos el DataSet con los datos:
Return ds End Function End Classreturn ds; } }Algunas comprobaciones extras para que no nos "la cuelen"
Cuando estemos trabajando con cadenas de SQL, siempre deberíamos tener cuidado de que no se pasen valores incorrectos e incluso "peligrosos".
En este ejemplo, sólo deberíamos admitir sentencias SELECT, por tanto haremos algunas comprobaciones de que la cadena pasada como argumento no tenga código malicioso.
Después del IF de que la cadena está vacía, añade el siguiente código:' Comprobar que están indicando valores correctos (o casi) ' ' Que no sea una cadena vacía If sel = "" Then Throw New ArgumentException("La cadena no puede ser nula") End If ' Comprobar que realmente se use SELECT, If sel.ToUpper().IndexOf("SELECT") = -1 Then Throw New ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>") End If ' no permitir comentarios ni algunas instrucciones maliciosas If sel.IndexOf("--") > -1 Then Throw New ArgumentException("No se admiten comentarios de SQL en la cadena de selección") End If If sel.ToUpper().IndexOf("DROP") > -1 Then Throw New ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>, no DROP y otros comandos no adecuados...") End If// Comprobar que están indicando valores correctos (o casi) // // Que no sea una cadena vacía if( sel == "" ) throw new ArgumentException("La cadena no puede ser nula"); // // Comprobar que realmente se use SELECT, if( sel.ToUpper().IndexOf("SELECT") == -1 ) throw new ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>"); // // no permitir comentarios ni algunas instrucciones maliciosas if( sel.IndexOf("--") > -1 ) throw new ArgumentException("No se admiten comentarios de SQL en la cadena de selección"); // if( sel.ToUpper().IndexOf("DROP") > -1 ) throw new ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>, no DROP y otros comandos no adecuados...");
Guarda el fichero, y cópialo en el directorio del servidor local (localhost) el cual estará en C:\Inetpub\wwwroot
Ahora solo queda hacer un cliente que consuma el servicio Web, además de crear una clase que permita a ese cliente usarlo.
Crear una clase "proxy" para acceder al servicio Web
Una vez que tenemos el servicio Web, debemos crear una clase para usarla en la aplicación cliente. La clase la vamos a generar automáticamente con la utilidad WSDL.exe.
Esta clase servirá para que el compilador sepa dónde está el servicio Web y qué funciones tiene, con idea de que podamos usar la clase como una clase normal y corriente.Nota:
Si tienes instalado Visual Studio .NET, tendrás un acceso directo a "Símbolo del sistema de Visual Studio .NET 2003" que te carga las variables de entorno necesarias para usar las herramientas de .NET.
Si solo tienes el SDK de .NET, en el directorio bin del SDK habrá un fichero .bat llamado sdkvars.bat que carga los valores adecuados para usar desde la línea de comandos, crea un acceso directo que contenga esta instrucción:
%comspec% /k "<directorio del SDK>\sdkvars.bat"
Y úsalo para compilar y ejecutar las herramientas del SDK.Abre una ventana de MSDOS con el path a las herramientas de .NET Framework y escribe lo siguiente:
Para Visual Basic:
WSDL /l:vb http://localhost/Northwind.asmxPara C#:
WSDL http://localhost/Northwind.asmxEsto creará una clase llamada de la misma forma que el nombre de la clase definida en el servicio Web: NorthwindSW.vb o NorthwindSW.cs, según el lenguaje indicado.
A esta clase me referiré como la clase "proxy". Que es la que se usará para acceder al servicio Web.
Continuará...
Bueno, vamos a dejar el tema por ahora... Al final te muestro el código de un cliente de consola que utiliza este servicio Web y las instrucciones para compilarlo desde la línea de comandos.
Nos vemos.
Guillermo
Código para Visual Basic
El código completo para Visual Basic .NET, tanto del servicio Web como de la aplicación cliente para usarlo.
El servicio Web:
<%@ WebService Language="VB" Class="NorthwindSW" %>'------------------------------------------------------------------------------ ' Servicio Web para acceder a una tabla de Northwind (15/Mar/05) ' ' ©Guillermo 'guille' Som, 2005 ' ' Para crear la clase "proxy": ' WSDL /l:vb http://localhost/Northwind.asmx '------------------------------------------------------------------------------ Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Web.Services <WebService(Namespace:="http:"//elGuille/ServiciosWeb/", _ Description:="Acceso a la base de datos Northwind (local) desde un servicio Web")> _ Public Class NorthwindSW_vb Inherits System.Web.Services.WebService ' Private da As SqlDataAdapter ' <WebMethod(Description:="Devuelve datos indicados en el parámetro de la base Northwind")> _ Public Function Empleados(sel As String) As DataSet ' If sel = "" Then sel = "SELECT LastName, FirstName, Title, BirthDate FROM Employees" End If ' ' Comprobar que están indicando valores correctos (o casi) ' ' Que no sea una cadena vacía If sel = "" Then Throw New ArgumentException("La cadena no puede ser nula") End If ' Comprobar que realmente se use SELECT, If sel.ToUpper().IndexOf("SELECT") = -1 Then Throw New ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>") End If ' no permitir comentarios ni algunas instrucciones maliciosas If sel.IndexOf("--") > -1 Then Throw New ArgumentException("No se admiten comentarios de SQL en la cadena de selección") End If If sel.ToUpper().IndexOf("DROP") > -1 Then Throw New ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>, no DROP y otros comandos no adecuados...") End If ' da = New SqlDataAdapter(sel, _ "integrated security=true; data source=(local); initial catalog=Northwind") ' Dim ds As New DataSet() ' Try da.Fill(ds) Catch ex As Exception Throw ex End Try ' Return ds End Function End Class
La aplicación cliente (de consola)
Para compilar esta aplicación, necesitamos la clase proxy generada con WSDL.
La línea de comando para compilar es:vbc ClienteNorthwindSW.vb NorthwindSW.vb /r:System.dll /r:System.Data.dll /r:System.Web.Services.dll /r:System.Xml.dll
'------------------------------------------------------------------------------ ' ClienteNorthwindSW (05/Abr/05) ' ' ©Guillermo 'guille' Som, 2005 ' ' Para crear el EXE: ' vbc ClienteNorthwindSW.vb NorthwindSW.vb /r:System.dll /r:System.Data.dll /r:System.Web.Services.dll /r:System.Xml.dll '------------------------------------------------------------------------------ Option Strict On Option Explicit On Imports Microsoft.VisualBasic Imports System Imports System.Data Public Class ClienteNorthwindSW ' <STAThread> _ Shared Sub Main(args As String()) ' punto de entrada del ejecutable Dim sw As New NorthwindSW Dim sel As String = "SELECT LastName, FirstName, Title, BirthDate FROM Employees" ' ' Si se ha indicado algún parámetro en la línea de comandos ' será la cadena de selección If args.Length = 1 Then ' Si hay uno, se habrá indicado entre comillas dobles sel = args(0) ElseIf args.Length > 1 Then ' Se habrá indicado sin usar comillas dobles ' unir todas las cadenas en una sola sel = String.Join(" ", args).Trim() End If ' Dim ds As DataSet Try ds = sw.Empleados(sel) Catch ex As Exception Console.WriteLine("ERROR: " & ex.Message) Return End Try ' ' Para ajustar el texto mostrado Const mostrar As Integer = 21 Dim sb As New System.Text.StringBuilder ' ' Mostrar las cabeceras For Each columna As DataColumn In ds.Tables(0).Columns If columna.DataType.ToString = "System.DateTime" Then Console.Write("{0} ", ajustar(columna.ColumnName, 10)) sb.AppendFormat("{0} ", New String("-"c, 10) ) Else Console.Write("{0} ", ajustar(columna.ColumnName, mostrar)) sb.AppendFormat("{0} ", New String("-"c, mostrar) ) End If Next Console.WriteLine() Console.WriteLine(sb.ToString) ' Mostrar los datos For Each fila As DataRow In ds.Tables(0).Rows For Each columna As DataColumn In ds.Tables(0).Columns ' Si es una fecha, usar un formato especial If TypeOf fila(columna) Is DateTime Then Console.Write("{0} ", CType(fila(columna), DateTime).ToString("dd/MM/yyyy")) Else Console.Write("{0} ", ajustar(fila(columna).ToString, mostrar)) End If Next Console.WriteLine() Next End Sub ' Ajustar la cadena al ancho indicado Private Shared Function ajustar(cadena As String, ancho As Integer) As String Return ( cadena & New String(" "c, ancho) ).Substring(0, ancho) End Function End Class
Código para C#
El código completo para C#, tanto del servicio Web como de la aplicación cliente para usarlo.
El servicio Web:
<%@ WebService Language="C#" Class="NorthwindSW" %>//----------------------------------------------------------------------------- // Servicio Web para acceder a una tabla de Northwind (15/Mar/05) // // (c)Guillermo 'guille' Som, 2005 // //----------------------------------------------------------------------------- using System; using System.Data; using System.Data.SqlClient; using System.Web.Services; [WebService(Namespace="http:"//elGuille/ServiciosWeb/", Description="Acceso a la base de datos Northwind (local) desde un servicio Web")] public class NorthwindSW : System.Web.Services.WebService { // private SqlDataAdapter da; // [WebMethod(Description="Devuelve datos indicados en el parámetro de la base Northwind")] public DataSet Empleados(string sel) { // if( sel == "" ) sel = "SELECT LastName, FirstName, Title, BirthDate FROM Employees"; // // Comprobar que están indicando valores correctos (o casi) // // Que no sea una cadena vacía if( sel == "" ) throw new ArgumentException("La cadena no puede ser nula"); // // Comprobar que realmente se use SELECT, if( sel.ToUpper().IndexOf("SELECT") == -1 ) throw new ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>"); // // no permitir comentarios ni algunas instrucciones maliciosas if( sel.IndexOf("--") > -1 ) throw new ArgumentException("No se admiten comentarios de SQL en la cadena de selección"); // if( sel.ToUpper().IndexOf("DROP") > -1 ) throw new ArgumentException("La cadena debe ser SELECT <campos> FROM <tabla>, no DROP y otros comandos no adecuados..."); // da = new SqlDataAdapter(sel, "integrated security=true; data source=(local); initial catalog=Northwind"); // DataSet ds = new DataSet(); try { da.Fill(ds); } catch(Exception ex) { throw ex; } return ds; } }
La aplicación cliente (de consola)
Para compilar esta aplicación, necesitamos la clase proxy generada con WSDL.
La línea de comando para compilar es:csc ClienteNorthwindSW.cs NorthwindSW.cs /r:System.dll /r:System.Data.dll /r:System.Web.Services.dll /r:System.Xml.dll
//------------------------------------------------------------------------------ // ClienteNorthwindSW (05/Abr/05) // // ©Guillermo 'guille' Som, 2005 // // Para crear el EXE: // csc ClienteNorthwindSW.cs NorthwindSW.cs /r:System.dll /r:System.Data.dll /r:System.Web.Services.dll /r:System.Xml.dll //------------------------------------------------------------------------------ using System; using System.Data; public class ClienteNorthwindSW{ // [STAThread] static void Main(string[] args) { // punto de entrada del ejecutable NorthwindSW sw = new NorthwindSW(); string sel = "SELECT LastName, FirstName, Title, BirthDate FROM Employees"; // // Si se ha indicado algún parámetro en la línea de comandos // será la cadena de selección if( args.Length == 1 ){ // Si hay uno, se habrá indicado entre comillas dobles sel = args[0]; }else if( args.Length > 1 ){ // Se habrá indicado sin usar comillas dobles // unir todas las cadenas en una sola sel = String.Join(" ", args).Trim(); } // DataSet ds; try{ ds = sw.Empleados(sel); }catch(Exception ex){ Console.WriteLine("ERROR: " + ex.Message); return; } // // Para ajustar el texto mostrado const int mostrar = 21; System.Text.StringBuilder sb = new System.Text.StringBuilder(); // // Mostrar las cabeceras foreach(DataColumn columna in ds.Tables[0].Columns){ if( columna.DataType.ToString() == "System.DateTime" ){ Console.Write("{0} ", ajustar(columna.ColumnName, 10)); sb.AppendFormat("{0} ", new string('-', 10) ); }else{ Console.Write("{0} ", ajustar(columna.ColumnName, mostrar)); sb.AppendFormat("{0} ", new string('-', mostrar) ); } } Console.WriteLine(); Console.WriteLine(sb.ToString()); // Mostrar los datos foreach(DataRow fila in ds.Tables[0].Rows){ foreach(DataColumn columna in ds.Tables[0].Columns){ // Si es una fecha, usar un formato especial if( fila[columna] is DateTime ){ Console.Write("{0} ", ( (DateTime)fila[columna] ).ToString("dd/MM/yyyy")); }else{ Console.Write("{0} ", ajustar(fila[columna].ToString(), mostrar)); } } Console.WriteLine(); } } // Ajustar la cadena al ancho indicado private static string ajustar(string cadena, int ancho) { return ( cadena + new string(' ', ancho) ).Substring(0, ancho); } }