views:

253

answers:

1

I am a newbie with F# and SL and playing with getting asynchronous HttpResponse through Silverlight. The following is the F# code pieces, which is tested on VS2010 and Window7 and works well, but the improvement is necessary. Any advices and discussion, especially the callback part, are welcome and great thanks.

module JSONExample
open System
open System.IO 
open System.Net 
open System.Text 
open System.Web 
open System.Security.Authentication 
open System.Runtime.Serialization 


[<DataContract>] 
type Result<'TResult> = { 
    [<field: DataMember(Name="code") >] 
    Code:string 
    [<field: DataMember(Name="result") >] 
    Result:'TResult array
    [<field: DataMember(Name="message") >] 
    Message:string 
    } 

// The elements in the list
[<DataContract>] 
type ChemicalElement = { 
    [<field: DataMember(Name="name") >] 
    Name:string 
    [<field: DataMember(Name="boiling_point") >] 
    BoilingPoint:string 
    [<field: DataMember(Name="atomic_mass") >] 
    AtomicMass:string 
} 



//http://blogs.msdn.com/b/dsyme/archive/2007/10/11/introducing-f-asynchronous-workflows.aspx
//http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!194.entry
type System.Net.HttpWebRequest with
    member x.GetResponseAsync() =
        Async.FromBeginEnd(x.BeginGetResponse, x.EndGetResponse)


type RequestState () = 
    let mutable request : WebRequest = null
    let mutable response : WebResponse = null
    let mutable responseStream : Stream = null
    member this.Request with get() = request and set v = request <- v
    member this.Response with get() = response and set v = response <- v
    member this.ResponseStream with get() = responseStream and set v = responseStream <- v

let allDone = new System.Threading.ManualResetEvent(false)


let getHttpWebRequest (query:string) = 
    let query = query.Replace("'","\"") 
    let queryUrl = sprintf "http://api.freebase.com/api/service/mqlread?query=%s" "{\"query\":"+query+"}" 

    let request : HttpWebRequest = downcast WebRequest.Create(queryUrl) 
    request.Method <- "GET" 
    request.ContentType <- "application/x-www-form-urlencoded" 
    request


let GetAsynResp (request : HttpWebRequest) (callback: AsyncCallback) = 
    let myRequestState = new RequestState()
    myRequestState.Request <- request
    let asyncResult = request.BeginGetResponse(callback, myRequestState)
    ()


// easy way to get it to run syncrnously w/ the asynch methods
let GetSynResp (request : HttpWebRequest) : HttpWebResponse  =      
    let response = request.GetResponseAsync() |> Async.RunSynchronously  
    downcast response

let RespCallback (finish: Stream -> _) (asynchronousResult : IAsyncResult) =
        try
            let myRequestState : RequestState = downcast asynchronousResult.AsyncState 
            let myWebRequest1 : WebRequest = myRequestState.Request
            myRequestState.Response <- myWebRequest1.EndGetResponse(asynchronousResult)
            let responseStream = myRequestState.Response.GetResponseStream()
            myRequestState.ResponseStream <- responseStream
            finish responseStream
            myRequestState.Response.Close() 
            ()
        with 
        | :? WebException as e
            -> printfn "WebException raised!"
               printfn "\n%s" e.Message
               printfn "\n%s" (e.Status.ToString())
               ()
        | _ as e
            -> printfn "Exception raised!"
               printfn "Source : %s" e.Source
               printfn "Message : %s" e.Message
               ()

let printResults (stream: Stream)= 
    let result = 
        try 
            use reader = new StreamReader(stream) 
            reader.ReadToEnd(); 
        finally 
            ()

    let data = Encoding.Unicode.GetBytes(result); 
    let stream = new MemoryStream() 
    stream.Write(data, 0, data.Length); 
    stream.Position <- 0L 

    let JsonSerializer = Json.DataContractJsonSerializer(typeof<Result<ChemicalElement>>) 
    let result = JsonSerializer.ReadObject(stream) :?> Result<ChemicalElement> 

    if result.Code<>"/api/status/ok" then 
        raise (InvalidOperationException(result.Message)) 
    else 
        result.Result |> Array.iter(fun element->printfn "%A" element) 

let test =
    // Call Query (w/ generics telling it you wand an array of ChemicalElement back, the query string is wackyJSON too –I didn’t build it don’t ask me!
    let request = getHttpWebRequest "[{'type':'/chemistry/chemical_element','name':null,'boiling_point':null,'atomic_mass':null}]"
    //let response = GetSynResp request 
    let response = GetAsynResp request (AsyncCallback (RespCallback printResults))
    () 

ignore(test)
System.Console.ReadLine() |> ignore
+2  A: 

The whole point of async is that you don't have to deal with state and IAsyncResult and Callbacks and whatnot. Below is a somewhat cleaned-up version of your code...

open System 
open System.IO  
open System.Net  
open System.Text  
open System.Web  
open System.Security.Authentication  
open System.Runtime.Serialization  

[<DataContract>]  
type Result<'TResult> = {  
    [<field: DataMember(Name="code") >]  
    Code:string  
    [<field: DataMember(Name="result") >]  
    Result:'TResult array 
    [<field: DataMember(Name="message") >]  
    Message:string  
    }  

// The elements in the list 
[<DataContract>]  
type ChemicalElement = {  
    [<field: DataMember(Name="name") >]  
    Name:string  
    [<field: DataMember(Name="boiling_point") >]  
    BoilingPoint:string  
    [<field: DataMember(Name="atomic_mass") >]  
    AtomicMass:string  
}  

//http://blogs.msdn.com/b/dsyme/archive/2007/10/11/introducing-f-asynchronous-workflows.aspx 
//http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!194.entry 
type System.Net.HttpWebRequest with 
    member x.GetResponseAsync() = 
        Async.FromBeginEnd(x.BeginGetResponse, x.EndGetResponse) 

let getHttpWebRequest (query:string) =  
    let query = query.Replace("'","\"")  
    let queryUrl = sprintf "http://api.freebase.com/api/service/mqlread?query=%s" "{\"query\":"+query+"}"  

    let request : HttpWebRequest = downcast WebRequest.Create(queryUrl)  
    request.Method <- "GET"  
    request.ContentType <- "application/x-www-form-urlencoded"  
    request 

let printResults (stream: Stream)=  
    let result =  
        try  
            use reader = new StreamReader(stream)  
            reader.ReadToEnd();  
        finally  
            () 

    let data = Encoding.Unicode.GetBytes(result);  
    let stream = new MemoryStream()  
    stream.Write(data, 0, data.Length);  
    stream.Position <- 0L  

    let JsonSerializer = Json.DataContractJsonSerializer(typeof<Result<ChemicalElement>>)  
    let result = JsonSerializer.ReadObject(stream) :?> Result<ChemicalElement>  

    if result.Code<>"/api/status/ok" then  
        raise (InvalidOperationException(result.Message))  
    else  
        result.Result |> Array.iter(fun element->printfn "%A" element)  

let test = async {
    // Call Query (w/ generics telling it you wand an array of ChemicalElement back, the query string is wackyJSON too –I didn’t build it don’t ask me! 
    let request = getHttpWebRequest "[{'type':'/chemistry/chemical_element','name':null,'boiling_point':null,'atomic_mass':null}]" 
    try 
        use! response = request.AsyncGetResponse()
        use responseStream = response.GetResponseStream() 
        printResults responseStream 
    with  
    | :? WebException as e 
        ->  printfn "WebException raised!" 
            printfn "\n%s" e.Message 
            printfn "\n%s" (e.Status.ToString()) 
    | _ as e 
        ->  printfn "Exception raised!" 
            printfn "Source : %s" e.Source 
            printfn "Message : %s" e.Message 
}

test |> Async.RunSynchronously 
System.Console.ReadLine() |> ignore 
Brian
Thanks for your reply. I have tried further. I want to get the information from the HttpWebResponse instance: response, such as the header, if I return "response", there will be an Idisposable exception will be thrown. I tried and know the reason is "use!". If I use "let!", but why? "................... let request = getHttpWebRequest "[{'type':'/chemistry/chemical_element','name':null,'boiling_point':null,'atomic_mass':null}]" try //use! response = request.AsyncGetResponse() let! response = request.AsyncGetResponse() return response with ............. "
J.K.J
I am unclear about your question - if you want to access to response object after this method (have the method return it), then yes, change `use` to `let` so as to avoid calling `Dispose` when the method completes.
Brian