Calling service using SOAP message (.NET)

If you need to call a web service at low-level this is a good post for you.

For this purpose, you have to use HttpWebRequest and HttpWebResponse for treating the call. With Visual Studio’s auto-generated proxy for calling web methods of a web service hides the communication language between client and server. You do not have any control about the SOAP message generated. The only way is to call web methods using low-level protocol (HttpRequest)

Look an example below:


HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("<<Url of your web method>>");

request.Headers["SOAPAction"] = <<web method URI>>;
request.ContentType = "text/xml;charset=\"utf-8\"";
request.Accept = "text/xml";
request.Method = "POST";

As you can see I have created the request using the information related to the web service. Next step is to build the SOAP Envelope

//Global variable for helping

static string soapEnvelope = @"<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Header></soap:Header><soap:Body></soap:Body></soap:Envelope>";


StringBuilder sb = new StringBuilder(soapEnvelope);
sb.Insert(sb.ToString().IndexOf("</soap:Header>"), String.Format("<AuthorizationToken xmlns=\"<<some URI>>"><Token>{0}</Token></AuthorizationToken>", authToken.Token));
sb.Insert(sb.ToString().IndexOf("</soap:Body>"), String.Format("<YOURMethod xmlns=\"<<some URI>>"><Parameter1>{0}</Parameter1><Parameter2>{1}</Parameter2></YOURMethod>", serviceName, methodName));

XDocument soapEnvelopeXml = XDocument.Parse(sb.ToString());

 

And finally, make the call


IAsyncResult result = request.BeginGetRequestStream((IAsyncResult asynchronousResult) =>
{

//inserting soap message in the request

using (Stream postStream = request.EndGetRequestStream(asynchronousResult))
{
soapEnvelope.Save(postStream);
postStream.Close();
}

request.BeginGetResponse(new AsyncCallback(asyncresult =>
{
XElement res = null;
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
Stream streamResponse = response.GetResponseStream();
StreamReader streamRead = new StreamReader(streamResponse);
string responseString = streamRead.ReadToEnd();
XDocument res = XDocument.Parse(responseString);
XNamespace ns = "<<your namespace>>";

streamResponse.Close();
streamRead.Close();

response.Close();

XElement results = res.Descendants(ns + "<<your method name>>Response").FirstOrDefault();
if (results == null)
{
}
else
{
res =  results.Elements().FirstOrDefault();
}
}), request);
}, null);

 

DataContractSerializer no deserialitza correctament

Hi poden haver moltes raons perquè el DataContractSerializer no deserialitzi bé. Però he pensat que era el millor títol que podia donar ja que en realitat és un problema que sembli exactament el que diu: Que no deserialitza correctament.

Però en realitat no és així, el problema està en què no sabem a vegades com funciona del tot.

Aquesta és la instrucció:


Countries = MVVMUtils.DataContractDeserializeObject<Countries>(e.Result.ToString());

I aquesta és la classe Countries


[CollectionDataContract(Namespace="")]
public class Countries : List<Country> { }

[DataContract(Namespace = "")]
public class Country {
[DataMember]
public string cty_key { get; set; }

[DataMember]
public string cty_code { get; set; }

[DataMember]
public string cty_idd_code { get; set; }
}

I aquest el fitxer XML que volem deserialitzar


<Countries xmlns="">
<Country>
<cty_key>8C445CE2-5AF6-48B6-BDDA-B10B77724566</cty_key>
<cty_code>United States</cty_code>
<cty_idd_code>1</cty_idd_code>
</Country>
<Country>
<cty_key>18227E69-725D-4A17-8245-4E2683094ED0</cty_key>
<cty_code>Canada</cty_code>
<cty_idd_code>1</cty_idd_code>
</Country>
<Country>

Si observes bé les propietats no estan ordenades alfabèticament. Aquest XML s’ha extret d’un altre sistema de serialització. Si s’hagués fet amb el DataContractSerializer estarien completament ordenades.

Aquest és el problema.DataContractSerializer necessita que les propietats estiguin ordenades i quan això no és possible llavors hem de indicar un atribut més al model : Order

Per tant el model hauria de ser:


[CollectionDataContract(Namespace="")]
public class Countries : List<Country> { }

[DataContract(Namespace = "")]
public class Country {
[DataMember(Order=1)]
public string cty_key { get; set; }

[DataMember(Order = 2)]
public string cty_code { get; set; }

[DataMember(Order = 3)]
public string cty_idd_code { get; set; }
}

Més informació:
Why needs DataContractSerializer alphabetically sorted XML?

Entity Framework Complex Type i ADO.NET Data Services

Dues tecnologies molt interessant en opinió meva i molt utils per un ràpid desenvolupament que et permeti poder crear serveis amb accés a bases de dades.

Entity Framework ens permet mapejar les bases de dades en entitats per tal de que podem aplicar LINQ per poder consultar els valors de la base de dades. També ens permet poder gestionar la base de dades sense entrar per res en llenguatge natiu de la base de dades.

Amb EF sovint trobem la necessitat de tenir Complex Types ja sigui perquè volem tenir una estructura auxiliar dins el nostre entorn o perquè important un Stored Procedure de la base de dades aquesta com a sortida té una estructura complexa.

ADO.NET Data Services ens permet poder crear serveis WCF simples que estan orientats a les dades i no pas als mètodes. Aquests serveis es configuren per un EF i per tant és en el client del servei que s’utilitza els contexte creat per poder accedir a les dades com si estiguessin en local.

Però de moment sembla ser que els Complex Types tenen un problema de compatibilitat i no es poden utilitzar per ADO.NET Data Services. Per tant qualsevol Stored Procedure o Web Method que poguem tenir dins el servei no actuarà correctament.

El problema és unicament al moment de serialitzar l’estructura i per tant una manera de solucionar-ho seria tu mateix recorrer a una serialització pròpia.

L’altre és crear un servei WCF normal que puguis consultar aquest tipus de informació.

És per això que ADO.NET Data Services està orientat a les dades i no pas als mètodes com si el servei WCF.

Per més informació:

The closed type does not have a corresponding element settable property

EF4: The closed type ‘xxxx’ does not have a corresponding element settable property

Issues with using Silverlight 4, WCF Data Service Service Operation with Entity Framework 4.0

BadRequest en servicios de datos ADO.NET

En servicios de datos ADO.NET puede haber varios motivos por los que tenemos un error BadRequest en nuestras peticiones. Este tipo de servicios no suele dar mucha información del error que se produce y en alguna ocasiones tenemos que mirar un poco e investigar cual puede ser el motivo.

En el caso de BadRequest con código 400 uno de los motivos puede ser el tamaño del mensaje. Sorprendetemente el tamaño esta limitado por defecto a 65536 bytes. Estamos hablando de tan solo 64KB. En aplicaciones que deban enviar tipos de datos complejos o imagenes este tamaño queda en seguida en insuficiente y es en este momento que recibimos el error BadRequest.

Para solucionarlo debemos configurar el servicio de la siguiente manera:

1) Des del servidor abriendo el web.config añadimos lo siguiente para configurar el servicio para que acepte el volumen en bytes que nosotros deseamos.

<services>
    <!-- The service name below has to be the EXACT Namespace.ClassName of your WCF Data Service-->
    <service name="YourDomainNameHere.YourClassNameHere">
        <!-- The address below must be the EXACT address of your service or blank-->
        <endpoint address ="http://localhost:19766/YourServiceName.svc" binding="webHttpBinding" bindingConfiguration="higherMessageSize" contract ="System.Data.Services.IRequestHandler">
        </endpoint>
    </service>
</services>

2) Necesitas especificar el tamaño en la propiedad maxReceivedMessageSize del elemento binding de la configuración de enlace higherMessageSize

<bindings>
    <webHttpBinding>
        <!-- The maxReceivedMessageSize and the maxBufferSize must both be specified as shown below-->
        <binding name="higherMessageSize" maxReceivedMessageSize ="2048000" maxBufferSize="2048000"/>
    </webHttpBinding>
</bindings>

No olvides que si el servicio se hospeda en un IIS ese mismo tiene limitado el tamaño de las peticiones a 4MB. Por lo tanto debes añadir en el archivo de configuración lo siguiente para equilibrar los valores.

<system.web> 
  <httpRuntime MaxRequestLength="ValueInKiloBytes" />
</system.web>

 

Otros enlaces:

How do I increase the size of data sent to ADO.Net Data Services?

Change max message size in DataServiceContext

 

ADO.NET Data Services y GUID

En el post anterior hablando de como podemos enlazarnos a un servicio web para leer imagenes comenté que debiamos construir la URL de la siguiente manera:

http://<url servei>/<nom servei>/<nom taula>(<valor clau primaria>)/Image/$value

Es correcto. Pero cuando la clave primaria es un numero unico GUID el formato cambia un poco. Debes cambiarlo por:

http://<url servei>/<nom servei>/<nom taula>(guid'<el valor del guid>')/Image/$value

Que cosas verdad?

ADO.NET Data Services i tipus Image a Sql Server 2008

Considero que ADO.NET Data Services és una tecnologia que va molt bé per generar serveis web que gestionen una base de dades d’una manera ràpida i senzilla, a més a més, utilitza REST per comunicar-se amb el servei. Només per peticions HTTP es pot fer totes les operacions bàsiques a una base de dades. Aquestes peticions bàsiques segueixen una sintaxi determinada per un protocol estàndard que està definit a odata.org

Per tant hi treballo bastant però quan hi estic posat sempre recordo els tipus de dades de SQL Server que no son compatibles (de moment) amb aquesta tecnología. Al següent link hi ha el mapeig que es fa de tipus SQL Server a .Net Framework.

SQL Server Data Type Mappings

Un dels casos que ens podem trobar sovint és com gestionem les imatges. M’explico: Tenim un servei que emmagatzema imatges, per exemple les fotos del perfil, directament a la base de dades amb format binary (utilitzant per tant el tipus Image de Sql Server). Per sort aquest tipus de dades no té cap problema amb ADO.NET Data Services i es pot utilitzar tranquil·lament.

En la imatge es pot veure com les imatges es transporten com arrays binaris per la web.

Ara toca construir la nostra aplicació client web que ens permeti almenys visualitzar aquest contingut. Utilitzant DataBinding pots agafar un GridView i enllaçar-lo a un ObjectDataSource que ens farà de proxy per el servei ADO.NET. El GridView colocaràs un ImageField per poder enllaçar el camp de la imatge que t’arriba en format binary, però l’enllaç simple no et pot funcionar ja que el que vol ASP.NET aqui és una URL. Per exemple el següent tros de codi mostra el que no funcionarà, tingues en compte que el camp de la taula de la base de dades que conté la imatge en format binary és Image:

<asp:ImageField DataImageUrlField="Image" HeaderText="Image">
 </asp:ImageField>

Per tant una solució és tornar a demanar al servei que vols aquest camp però directament el seu valor. Per fer-ho construeixes la Url de la següent manera:

http://<url servei>/<nom servei>/<nom taula>(<valor clau primaria>)/Image/$value

el resultat final et quedaria com:

<asp:ImageField DataImageUrlField="IdRegistre" DataImageUrlFormatString="http://localhost:52159/Service.svc/Profiles({0})/Image/$value" HeaderText="Image">
 </asp:ImageField>

on IdRegistre indica el camp de la taula que és clau primaria.

Com pots veure ADO.Net Data Service és una bona manera de construir serveis per accedir a les taules de la base de dades sense haver d’escriure grans linies de codi. Tot l’accés és per HTTP i és aplicable des de JQUERY i Silverlight i altres sense cap tipus de problema.

Links que et poden ampliar aquesta informació o et poden interessar:

Images as a Service with ADO.NET Data Services

Retrieving Images from a Database ( C# )

ImageField.DataImageUrlFormatString (Propiedad)

Wcf Data Services i autentificació per forms

Quina sorpresa aquest mati quan he publicat una aplicació que estic desenvolupant feta en Silverlight.

Evidentment com tot developer en un entorn de desenvolupament cap problema però quan es passa a l’entorn release apareixen problemes inesperats.

L’aplicació utilitza WCF Data Services, tecnología de la qual, trobo que és molt interessant. Aquesta tecnología que utilitza el protocol REST es basa en unes crides Http a una Uri. Aquesta Uri és un WCF, per tant, una extensió .svc que en primera vista sembla que hagi de funcionar igual que qualsevol recurs que es troba dins una web.

Em refereixo a que no hauria de funcionar sota un entorn anònim? Doncs si, si que funciona.

No hauria de funcionar sota autentificació per windows? Doncs si, si que funciona

Per tant, i només em queda, no hauria de funcionar sota autentificació per formularis? Doncs NO exactament.

Però per què? quin és el motiu? no estem parlant de peticions Http (Get, Post, Delete, Put)? No ens han ensenyat que aquestes peticions passen per un IIS que en verifica els permisos? (aplicable al IIS7)

Doncs exactament no sé quin és el motiu però he hagut de fer servir en google per trobar la solució al problema que s’em presentava.

La resposta l’he trobada aquí: http://blogs.msdn.com/b/astoriateam/archive/2010/07/21/odata-and-authentication-part-7-forms-authentication.aspx

Es tracta d’utilitzar interceptors per verificar si la petició bé o no autenticada i deixar la ruta (Uri) obert, és a dir, amb accés anònim!.

La solució no és dificil, no patiu. Però no hauria de ser tot més senzill?