2011-11-17 6 views
5

Abbiamo un servizio Web attivo e funzionante costruito in VS2010.Esiste un buon modo per estendere un servizio WCF utilizzando basicHttpBinding per consentire anche al servizio REST di comunicare con JSON?

Molti dei contratti operativi si presenta così:

[OperationContract] 
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion); 

Vale a dire lo stand ha argomenti complessi e tipi di ritorno complessi, o anche più resi.

Abbiamo recentemente avviato un progetto di iPhone in outsourcing e stiamo consentendo loro di utilizzare questo servizio per comunicare con il nostro server. Da quello che ho imparato da loro ho capito che questa non è una buona pratica per comunicare con l'iPhone (mancanza di buoni modi per consumare il WSDL per esempio). E quindi ho iniziato a considerare la possibilità di esporre il servizio come servizio REST che comunica con JSON.

ho aggiunto un nuovo endpoint, utilizzando webHttpBinding, decorato i contratti di questo tipo:

[OperationContract] 
    [WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)] 
    ITicket Login(string userName, string password, string softwareVersion); 

Questo metodo ora funziona come dovrebbe.

Allora ho provato a decorare un altro metodo come questo:

[OperationContract] 
    [WebGet(UriTemplate = "/GetMetaData?ticket={ticket}",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] 
    IMetaData GetMetaData(ITicket ticket); 

Quando io ora provo ad accedere a questa ricevo il seguente errore:

Server Error in '/Jetas5MobileService' Application. Operation 'GetMetaData' in contract 'IJetas5MobileService2' has a query variable named 'ticket' of type 'Jetas.MobileService.DataContracts.ITicket', but type 'Jetas.MobileService.DataContracts.ITicket' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.

devo riuscire a costruire un OperationContract che solo prende una stringa come argomento e poi analizza il thin-end usando il DataContractJsonSerializer, ma sembra più un brutto scherzo.

C'è un modo per risolvere questo in un modo migliore? Sono un principiante quando si tratta di WCF e REST, quindi non abbiate paura di indicarmi qualche tutoraggio per principianti che potrebbe esserci là fuori. Ho provato a cercarli ma la grande quantità di fonti rende difficile trovare quelli buoni.

+0

quale versione di WCF stai usando? –

+0

Sto usando .net4 e VS2010, risponde la domanda? Altrimenti fammi sapere come posso cercare. –

risposta

2

Il problema maggiore non è la mancanza di buoni "strumenti" ma la mancanza di comprensione di ciò che WSDL è e di come funzionano i servizi Web. Tutti questi strumenti che generano stub di servizio per gli sviluppatori hanno causato che gli sviluppatori non capiscono cosa è sotto il cofano. Funziona per scenari di base in cui tutta la magia è fatta per te, ma una volta che gli sviluppatori devono rintracciare qualsiasi problema o estendere lo "strumento" con funzionalità aggiuntive hanno grossi problemi (e di solito si traduce in una cattiva soluzione). Ad essere sinceri, lo sviluppo di SW non riguarda gli scenari di base.

REST rappresenta una grande sfida per gli sviluppatori perché non fornisce strumenti "magici". REST riguarda l'uso corretto del protocollo HTTP e sfrutta appieno le infrastrutture HTTP esistenti. Senza comprendere le basi del protocollo HTTP non creerai un buon servizio REST. Ecco dove dovresti iniziare.

Ecco alcuni esempi di uso scorretto:

[OperationContract] 
[WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)] 
ITicket Login(string userName, string password, string softwareVersion); 

Login metodo è ovviamente qualcosa che svolgono una certa azione - Credo che crea biglietto. È assolutamente inadatto per la richiesta HTTP GET. Questo dovrebbe essere sicuramente una richiesta POST alla risorsa Login che restituisce la nuova rappresentazione ITicket per ogni chiamata. Perché? Perché le richieste GET dovrebbero essere sicure e idempotenti.

  • Sicuro: la richiesta non dovrebbe causare effetti collaterali = non dovrebbe apportare modifiche alla risorsa ma nel tuo caso molto probabilmente crea una nuova risorsa.
  • Idempotente: questo non è così importante per l'esempio perché hai già violato la regola di sicurezza, ma significa che la richiesta alla risorsa deve essere ripetibile. Significa che la prima richiesta con lo stesso nome utente, password e versione può creare una nuova risorsa ma quando la richiesta viene eseguita di nuovo non dovrebbe creare una nuova risorsa ma restituirne una già creata. Questo ha più senso quando la risorsa viene mantenuta/mantenuta sul server.

Poiché la richiesta HTTP GET è basata su un'infrastruttura HTTP considerata sicura e idempotente, viene gestita in modo diverso. Ad esempio, le richieste GET possono essere memorizzate nella cache reindirizzate ecc. Quando la richiesta non è sicura e idempotente, dovrebbe utilizzare il metodo POST. Quindi la definizione corretta è:

[OperationContract] 
[WebInvoke(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)] 
ITicket Login(string userName, string password, string softwareVersion); 

perché WebInvoke per impostazione predefinita metodo POST. Questo è anche il motivo per cui tutto il tunneling del protocollo (ad esempio SOAP) utilizza di solito i metodi POST HTTP per tutte le richieste.

L'altro problema nel precedente esempio può essere di nuovo l'approccio REST = sfruttando appieno l'infrastruttura HTTP. Dovrebbe usare l'autenticazione basata su HTTP (login) = Basic, Digest, OAuth, ecc. Ciò non significa che non si può avere una risorsa simile ma si dovrebbe prima considerare l'utilizzo della modalità HTTP standard.

Il tuo secondo esempio è in realtà molto meglio ma presenta problemi con la limitazione di WCF. WCF può leggere dall'URL solo i tipi di base (btw. Come si desidera passare l'oggetto nell'URL?). Qualsiasi altro tipo di parametro richiede un comportamento WCF personalizzato. Se avete bisogno di esporre il metodo che accetta contratto di dati è necessario ancora una volta utilizzare il metodo HTTP che accetta parametri in corpo - ancora una volta usare POST e luogo JSON biglietto serializzato al corpo della richiesta:

[OperationContract] 
[WebInvoke(UriTemplate = "/GetMetaData",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] 
IMetaData GetMetaData(ITicket ticket); 
+0

Grazie per l'ottimo feedback! Leggerò di più sul protocollo HTTP. Ho già una domanda però. GetMetaData è fondamentalmente una query che restituisce i dati, a seconda del ticket. Quando leggo http://www.w3.org/2001/tag/doc/whenToUseGet.html#checklist, interpreto questo come GET sarebbe il più adatto per questa azione, vale a dire lo stato del server è lo stesso. È a causa del tipo complesso dell'argomento che il POST è preferito? –

+0

Sì, è più adatto per GET, ma a causa delle impostazioni predefinite della WCF, la definizione del metodo richiede che il POST accetti il ​​tipo complesso. –

2

Ho affrontato il problema simile utilizzando il kit di avviamento del WCF Rest.

Se ricordo correttamente, le variabili UriTemplate nel percorso vengono sempre risolte in stringhe quando si utilizza WebGet o WebInvoke. È possibile associare solo le variabili UriTemplate a int, long e così via quando si trovano nella porzione di query di UriTemplate. Quindi non c'è modo di passare un oggetto complesso in.

Penso che non ci sia un modo pulito per farlo. Ho appena usato la soluzione di analisi come fai tu.

Ora è possibile controllare il nuovo stack per eseguire REST con WCF chiamato WCF Web Api. Tratta molto bene con tipi complessi come parametri di metodo.

+0

Grazie! Darò un'occhiata a quello. –

1

Si consiglia di inviare dati JSON al setup metodo e lattina la dichiarazione del tipo:

[OperationContract] 
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, 
     UriTemplate = "/login", BodyStyle = WebMessageBodyStyle.Wrapped)] 
    ITicket Login(string userName, string password, string softwareVersion); 

quindi aggiungere un nuovo punto finale alla configurazione come segue (lasciando gli endpoint e la configurazione esistenti il ​​modo in cui si trova, si è appena aggiunto il nuovo endpoint JSON e un nuovo comportamento):

<service behaviorConfiguration="Your.ServiceBehavior.Here" name="Your.Stuff.Here"> 
     <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicBindingSettings" behaviorConfiguration="basic" contract="Your.Contract.Here"> 
     </endpoint> 
     <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> 
     <endpoint address="json" binding="webHttpBinding" contract="Your.Contract.Here" behaviorConfiguration="web"></endpoint> 
     </service> 
<behaviors> 
     <endpointBehaviors> 
     <behavior name="web"> 
      <webHttp /> 
     </behavior> 
    </behaviors> 

Quindi è possibile pubblicare qualcosa come "{" userName ":" testuser "," password ":" testpass "," softwareVersion ":" 1.0.0 "}" all'URL https://yourdomain.com/service.svc/json/login.

Se si desidera passare un tipo complesso, è sufficiente passare in JSON che corrisponde all'oggetto personalizzato. Quindi, se avessi un oggetto animale con proprietà di colore e dimensione, il JSON sarebbe simile a "{" animal ": {" Color ":" red "," Size ":" Large "}}".

Questo dovrebbe fare e non è necessario modificare l'implementazione del metodo. WCF restituirà solo i dati formattati JSON quando richiamati nel modo sopra, contro l'endpoint JSON. I tuoi metodi SOAP esistenti continueranno a funzionare normalmente.

+0

Sia il mio endpoint che il metodo "login" funzionano per me. È quando provo con i tipi più complessi che inizia l'errore. –

+0

Mi rendo conto che alcune cose incluse nel mio post sono già state completate, ma stavo esaminando i passaggi da cima a fondo per completezza, nel caso in cui qualcun altro si imbattesse in questo. La chiave con tipi complessi nello scenario che ho descritto è l'impostazione di RequestFormat = WebMessageFormat.Json e quindi la formattazione corretta della richiesta JSON. – GCaiazzo

Problemi correlati