domingo, 13 de enero de 2013

Dispositivos móviles consumiendo .NET WCF REST: Android !!!

untitledMuy buenas,

En esta ocasión y continuando con el mundo Java, veamos como nuestros actuales servicios WCF de .NET pueden ser expuestos como REST (Representational State Transfer) además de permanecer dando funcionalidad SOAP como hasta ahora. 

Actualmente ya existen numerosos artículos que hablan al respecto así que nos centraremos concretamente en las adaptaciones de nuestros servicios WCF .NET y la invocación correcta desde el Emulador de Android y Eclipse.

Cada vez más los dispositivos móviles son parte de cada minuto en nuestra vida. Toda compañía quiere acercar más su negocio a cada individuo e involucrarse en esta nueva era. REST tiene la particularidad adicional de poder ser consumido fácilmente por cualquiera de ellos de manera estándar minimizando impactos en el ancho de banda. Además, HTML 5 también está adquiriendo gran importancia y gracias a estos servicios REST, JQuery aporta la comunicación más adecuada con nuestros servicios.

Web API, es la recomendación de Microsoft al la creación de servicios REST desde cero. No obstante, si queremos dar a nuestros clientes la oportunidad de no tener que realizar una gran inversión en la creación de nuevos servicios veamos cuales serían nuestros pasos:

Creando Servicio WCF REST .NET

1) Creamos nuestro servicio WCF desde Visual Studio .NET, a partir de un proyecto de tipo “WCF Service Application” e indicamos como nombre “WcfService1”:

image

2) Modificamos el Service Contract para incluir las siguientes instrucción como puede verse a continuación:

[ServiceContract]
public interface IService1
{
[
OperationContract]
[
WebGet(
UriTemplate =
"GetData/{value}"
,
BodyStyle =
WebMessageBodyStyle
.WrappedRequest,
ResponseFormat =
WebMessageFormat
.Json,
RequestFormat =
WebMessageFormat
.Json)]
string GetData(string value);

[
OperationContract]
[
WebInvoke(
Method =
"POST"
,
UriTemplate =
"GetDataUsingDataContract"
,
BodyStyle =
WebMessageBodyStyle.Bare,
/* Importante. Usar "Bare" */
ResponseFormat = WebMessageFormat
.Json,
RequestFormat =
WebMessageFormat
.Json)]
CompositeType GetDataUsingDataContract(CompositeType composite);
}

3) Ejecutamos el servicio a fin de comprobar que todo esta correcto. “Aunque este mensaje puede indicarnos que se trata de un error, no es así”.


image


Instalando el plug-in de Android en Eclipse


4) En Eclipse, desde el menú: “Help – Install New Software…” e indicamos “https://dl-ssl.google.com/android/eclipse/” en el campo “Work with”:


imageimage


5) Accedemos a la carpeta “C:\Users\<USUARIO>\android-sdks” donde encontraremos el SDK de Android instalado.


6) Ejecutamos  “SDK Manager.exe”, marcamos “Android 4.2 (API 17)” e instalamos los 7 paquetes. (“En mi caso, como muestra la figura, se encuentran instados”):


image


7) Una vez instalado,  configuramos cualquiera de los emuladores de Android, por ejemplo, el de “Nexus One”:


Ejecutamos “AVD Manager.exe” (Android Virtual Device)


imageimage


8 Acceso al servicio desde .NET 4.0


8.1) Para la serialización/deserialización JSON optaremos por “Newtonsoft.Json.dll” (http://james.newtonking.com/pages/json-net.aspx).


8.2) A continuación completamos el código de nuestras peticiones GET y POST con el siguiente código:


Petición GET



   1: string strDataRest = String.Empty;
   2:  
   3: WebRequest request = WebRequest.Create(REST_URL_GET);
   4: using (WebResponse ws = request.GetResponse())
   5: {
   6:     using (StreamReader sr = new StreamReader(ws.GetResponseStream()))
   7:     {
   8:         string strJson = sr.ReadToEnd();
   9:         strDataRest = JsonConvert.DeserializeObject<string>(strJson);
  10:     }
  11: }

Petición POST



   1: CompositeType newCompositeData = new CompositeType() { BoolValue = true, StringValue = "ABC" };
   2: CompositeType compositeDataRest = null;
   3:  
   4: string jsonString = JsonConvert.SerializeObject(newCompositeData);
   5: byte[] requestBytes = ASCIIEncoding.UTF8.GetBytes(jsonString);
   6:                
   7: HttpWebRequest reqPost = WebRequest.Create(REST_URL_POST) as HttpWebRequest;
   8: reqPost.Method = "POST";
   9: reqPost.Accept = "application/json";
  10: reqPost.ContentType = "application/json";
  11: reqPost.ContentLength = requestBytes.Length;
  12: reqPost.Headers.Add("json", jsonString);
  13:                 
  14:  
  15: using (Stream postStream = reqPost.GetRequestStream())
  16: {
  17:     postStream.Write(requestBytes, 0, requestBytes.Length);
  18: }
  19:  
  20: using (HttpWebResponse response = reqPost.GetResponse() as HttpWebResponse)
  21: {
  22:     if (response.StatusCode != HttpStatusCode.OK)
  23:         throw new Exception(String.Format(
  24:             "Server error (HTTP {0}: {1}).",
  25:             response.StatusCode,
  26:             response.StatusDescription));
  27:  
  28:     using (StreamReader sr = new StreamReader(response.GetResponseStream()))
  29:     {
  30:         string strJson = sr.ReadToEnd();
  31:         compositeDataRest = JsonConvert.DeserializeObject<CompositeType>(strJson);
  32:     }
  33: }

 9 Acceso al servicio desde Android


En primer lugar crearemos un proyecto de tipo “Android Application Project” e indicaremos por ejemplo, el nombre “MyAndroidTest”:


imageimage


9.1) Una vez creado el proyecto arrastramos al formulario de Android una caja de texto como entrada de datos, dos botones para las peticiones GET y POST y un “TextView” para mostrar los resultados:


image


9.2) Editamos el fichero “activity_main.xml” y vinculamos a los eventos click los métodos que vamos a utilizar; uno para una petición GET y otro para una POST: “GetClick” y “PostClick” respectivamente:



   1: <Button
   2:      android:id="@+id/button1"
   3:      android:layout_width="wrap_content"
   4:      android:layout_height="wrap_content"
   5:      android:layout_alignRight="@+id/editText1"
   6:      android:layout_below="@+id/editText1"
   7:      android:layout_marginTop="26dp"
   8:      android:text="GET"
   9:      android:onClick="GetClick" />
  10:  
  11:  <Button
  12:      android:id="@+id/button2"
  13:      android:layout_width="wrap_content"
  14:      android:layout_height="wrap_content"
  15:      android:layout_above="@+id/textView1"
  16:      android:layout_alignRight="@+id/button1"
  17:      android:layout_marginBottom="14dp"
  18:      android:text="POST"
  19:      android:onClick="PostClick" />

9.3) Añadimos al fichero “AndroidManifest.xml” la instrucción “<uses-permission android:name="android.permission.INTERNET" />”. De no ser así, obtendremos este error: “Request Failed: org.apache.http.conn.HttpHostConnectException:Connection to http://10.0.2.2:<PUERTO> refused”.


9.4) Tenemos que tener en cuenta, que en lugar de utilizar URL del tipo: http://localhost o http://127.0.0.1, tendremos que utilizar en su lugar esta otra: http://10.0.2.2.


9.5) Modificamos el método “onCreate” de nuestra clase “MainActivity.java” para inicializar el uso de los botones y la caja de texto además de incluir una política para poder trabajar con la Interfaz de Android de manera “No responsiva”. En caso contrario obtendremos el error “Request Failed: Android.Os.NetWorkOnMainTreadExeption”. Aunque no es una buena práctica, para este ejemplo es suficiente y más aun, no siendo un experto en java Risa!



   1: @TargetApi(Build.VERSION_CODES.GINGERBREAD)
   2:     @Override
   3:     protected void onCreate(Bundle savedInstanceState) {
   4:         super.onCreate(savedInstanceState);    
   5:         setContentView(R.layout.activity_main);
   6:     
   7:            if (android.os.Build.VERSION.SDK_INT > 9) {
   8:                   StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
   9:                   StrictMode.setThreadPolicy(policy);
  10:                 }
  11:  
  12:         
  13:         label = (TextView)findViewById(R.id.textView1);
  14:         text = (EditText)findViewById(R.id.editText1);
  15:         
  16:         buttonGet = (Button)findViewById(R.id.button1);
  17:         buttonPost = (Button)findViewById(R.id.button2);        
  18:     }

9.6) Incluimos también el la clase “MainActivity.java” los dós métodos equivalentes a las peticiones GET y POST:


Petición GET:



   1: public void GetClick(View button) {
   2:     try {        
   3:         label.setText("Pulsado GET...");
   4:         
   5:         DefaultHttpClient httpClient = new DefaultHttpClient();
   6:         HttpGet request = new HttpGet(REST_URL_GET + text.getText());
   7:  
   8:         request.setHeader("Accept", "application/json");
   9:         request.setHeader("Content-type", "application/json");
  10:  
  11:         HttpResponse response = httpClient.execute(request);
  12:  
  13:         HttpEntity responseEntity = response.getEntity();
  14:  
  15:         // Read response data into buffer
  16:         char[] buffer = new char[(int)responseEntity.getContentLength()];
  17:         InputStream stream = responseEntity.getContent();
  18:         InputStreamReader reader = new InputStreamReader(stream);
  19:         reader.read(buffer);
  20:         stream.close();
  21:  
  22:         String str = new String(buffer);
  23:              
  24:         label.setText(str);     
  25:         
  26:     } catch (Throwable t) {
  27:          Toast.makeText(this, "Request failed: " + t.toString(), Toast.LENGTH_LONG).show();            
  28:     }
  29: }

Petición POST:



   1: public void PostClick(View button) {
   2:  
   3:    try {
   4:    
   5:         label.setText("Pulsado POST...");
   6:                       
   7:         JSONObject json = new JSONObject();
   8:         json.put("BoolValue", true);               
   9:         json.put("StringValue", text.getText());            
  10:  
  11:         HttpParams httpParams = new BasicHttpParams();
  12:         HttpConnectionParams.setConnectionTimeout(httpParams, 30000);
  13:         HttpConnectionParams.setSoTimeout(httpParams, 30000);
  14:         HttpClient client = new DefaultHttpClient(httpParams);
  15:   
  16:         HttpPost request = new HttpPost(REST_URL_POST);
  17:            
  18:         ByteArrayEntity entity = new ByteArrayEntity(json.toString().getBytes("UTF-8"));
  19:         entity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
  20:         entity.setContentType("application/json");           
  21:         request.setEntity(entity);
  22:         
  23:         HttpResponse response = client.execute(request);
  24:         HttpEntity entityReturn = response.getEntity();
  25:         InputStream instream = entityReturn.getContent();
  26:         String str = convertStreamToString(instream);                
  27:         
  28:         JSONObject myAwway = new JSONObject(str);
  29:         Boolean a = myAwway.getBoolean("BoolValue");
  30:         String b = myAwway.getString("StringValue");
  31:         
  32:         label.setText("BoolValue: " + Boolean.toString(a) + " StringValue: " + b);
  33:        } catch (Throwable t) {
  34:        Toast.makeText(this, "Request failed: " + t.toString(), Toast.LENGTH_LONG).show();
  35:        }
  36: }    

10) Una vez que todo esta completado, nos aseguramos que los servicios .NET se encuentran levantados y ejecutamos la aplicación  Android desde Eclipse:


image


11) Llegado este punto, ya podremos decir a nuestros clientes que sus servicios están siendo consumidos por aplicaciones móviles. WCF puede ser expuesto como WCF REST con tan sólo unas pocas líneas de código, seguro que nuestros clientes estarán contentos.



Acceso al servicio desde .NET 4.5


Adicionalmente, y aunque es muy probable que muchos clientes no hayan adoptado aún .NET Framework 4.5, me ha parecido buena idea incluir un ejemplo  al respecto además de una manera mucho mas “responsiva”:


Petición Get:



   1: HttpClient client = new HttpClient();
   2:  
   3: string strDataRest = String.Empty;
   4:  
   5: client.GetAsync(REST_URL_GET).ContinueWith((requestTask) =>
   6: {
   7:     HttpResponseMessage response = requestTask.Result;
   8:     response.EnsureSuccessStatusCode();
   9:     response.Content.ReadAsStringAsync().ContinueWith((readTask) =>
  10:         {
  11:             strDataRest = JsonConvert.DeserializeObject<string>(readTask.Result);
  12:         });
  13: });

Petición Post:



   1: CompositeType compositeDataRest = new CompositeType();
   2: CompositeType newCompositeData = new CompositeType() { BoolValue = true, StringValue = "Test...123..." };
   3:  
   4: StringContent content = new StringContent(JsonConvert.SerializeObject(newCompositeData));                
   5: content.Headers.ContentType = new MediaTypeHeaderValue("application/json");                               
   6:  
   7: client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
   8:  
   9: client.PostAsync(REST_URL_POST, content).ContinueWith((requestTask) =>
  10:     {
  11:         HttpResponseMessage response = requestTask.Result;
  12:         response.EnsureSuccessStatusCode();
  13:                        
  14:         response.Content.ReadAsStringAsync().ContinueWith((readTask) =>
  15:             {
  16:                 string strJson = readTask.Result;
  17:                 compositeDataRest = JsonConvert.DeserializeObject<CompositeType>(strJson);                                
  18:             });
  19:  
  20:         requestTask.Wait();
  21:     });

Nota: Ni que decir tiene que nuestros servicios podrán pasar a formar parte de “Windows Azure”, permitiendo así un mayor crecimiento y una gran escalabilidad en caso de que el negocio de nuestros clientes comience a crecer imprevisiblemente.


Espero, al igual que siempre haber estado a la altura y que sea de utilidad.


Saludos & Happy Coding
Juanlu, ElGuerre

Etiquetas: , ,


This page is powered by Blogger. Isn't yours?