SharePoint FBA & Reactive Extensions

Some of the dangers with new toys is to overuse them, I fear I might be doing that with reactive extensions but oh well.. It’s all about the learningSmile

Today I did a test to do Form Based Authentication with SharePoint using reactive extensions. This is the first attempt, it can probably be made more streamlined as I figure out more about Rx.

Oh, apologize for the syntax coloring in the code, I really need to find some better CSS for this…

I have two classes: One for doing the FBA (which also holds the cookie jar as we will get a cookie once the FBA is successful and need to pass this along to the next web service call) and one for doing web service calls.

public class FormsBasedAuthentication {
    public static CookieContainer CookieJar = new CookieContainer();
    private const string AuthEnvelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <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:Body>
                    <Login xmlns=""http://schemas.microsoft.com/sharepoint/soap/"">
                      <username>{0}</username>
                      <password>{1}</password>
                    </Login>
                  </soap:Body>
                </soap:Envelope>";

    public static IObservable<string> Authenticate(string server, string username, string password) {
        Uri authService = new Uri(string.Format("{0}/_vti_bin/authentication.asmx", server));
        HttpWebRequest authRequest = WebRequest.Create(authService) as HttpWebRequest;
        authRequest.CookieContainer = CookieJar;
        authRequest.Headers["SOAPAction"] = "http://schemas.microsoft.com/sharepoint/soap/Login";
        authRequest.ContentType = "text/xml; charset=utf-8";
        authRequest.Method = "POST";
        return (from request in Observable.FromAsyncPattern<Stream>(authRequest.BeginGetRequestStream, authRequest.EndGetRequestStream)().Do(stream => {
            UTF8Encoding encoding = new UTF8Encoding();
            string envelope = string.Format(AuthEnvelope, username, password);
            byte[] bytes = encoding.GetBytes(envelope);
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
        })
                from response in Observable.FromAsyncPattern<WebResponse>(authRequest.BeginGetResponse, authRequest.EndGetResponse)()
                select HandleResponse(response, authRequest));
    }

    private static string HandleResponse(WebResponse response, HttpWebRequest request) {
        var httpResponse = response as HttpWebResponse;
        if (httpResponse != null){
            var result = XDocument.Load(response.GetResponseStream());
            var code = result.Descendants(XName.Get("ErrorCode", "http://schemas.microsoft.com/sharepoint/soap/")).FirstOrDefault();
            if (code != null)
                return code.Value;
        }
        return "ErrorOccured";
    }

}

This will return an observable that will return the authentication result – if the login was successful, the published value will be NoError.

Next we have our web service class:

public class BaseWebService {
       private 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:Body>{0}</soap:Body></soap:Envelope>";

       private XDocument soapEnvelopeXml;
       public string BaseUrl { get; set; }
       #region Helper methods
       private XDocument CreateSoapEnvelope(string content) {
           var envelope = string.Format(soapEnvelope, content);
           XDocument soapEnvelopeXml = XDocument.Parse(envelope);

           return soapEnvelopeXml;
       }

       private HttpWebRequest CreateWebRequest(string url, string action) {
           Uri serviceUrl = new Uri(url, UriKind.Absolute);
           var webRequest = WebRequest.Create(serviceUrl) as HttpWebRequest;
           webRequest.Headers["SOAPAction"] = action;
           webRequest.ContentType = "text/xml;charset=\"utf-8\"";
           webRequest.Accept = "text/xml";
           webRequest.Method = "POST";
           webRequest.CookieContainer = FormsBasedAuthentication.CookieJar;
           return webRequest;
       }
       #endregion

       public IObservable<XDocument> CallService<T>(string url, string action, string soapContent) {
           soapEnvelopeXml = CreateSoapEnvelope(soapContent);
           HttpWebRequest webRequest = CreateWebRequest(url, action);
           var call = (from request in Observable.FromAsyncPattern<Stream>(webRequest.BeginGetRequestStream, webRequest.EndGetRequestStream)().Do(stream => {
                                                                                                                                                       soapEnvelopeXml.Save(stream);
                                                                                                                                                       stream.Close();
                                                                                                                                                   })
                       from response in Observable.FromAsyncPattern<WebResponse>(webRequest.BeginGetResponse, webRequest.EndGetResponse)()
                       select Handle(response));
           return call;
       }
       private XDocument Handle(WebResponse response){
           var doc = XDocument.Load(response.GetResponseStream());
           return doc;
       }
   }

This produces an observable which will publish a XDocument with the result of the web service call. As you can see, we are using our cookie jar from the FBA class (see the CreateWebRequest method) so that our authentication token gets passed along.

To put this together and do an authenticated call to the Lists.asmx web service in SharePoint, we need two small methods.

private static void GetList(string authStatus){
            if (authStatus == "NoError"){
                BaseWebService webService = new BaseWebService();
                webService.CallService<XDocument>(“<url>/_vti_bin/Lists.asmx”, ActionGetList, string.Format(SoapGetList, "Pages")).Subscribe(Handle);
            }
        }

This method gets the published value from the FBA class and checks that we can proceed with our next call.

Oh, must not forget the SoapAction and content of the envelope:

private const string ActionGetList = "http://schemas.microsoft.com/sharepoint/soap/GetList";
private const string SoapGetList = @"<GetList xmlns='http://schemas.microsoft.com/sharepoint/soap/' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'><listName>{0}</listName></GetList>";

 

private static void Handle(XDocument doc){
            Console.WriteLine(doc.ToString());
        }

And this one handles the result from the call to Lists.asmx (not doing much at this point).

So, to start it all off, we need to get our FBA observable set up:

var auth = FormsBasedAuthentication.Authenticate("<url to sharepoint site>", "<user>", "<password>");

Next we start it off:

auth.Do(GetList).Start();

What this does is that our FBA observable will call GetList for every published value that the FBA observable gives us (which should be just one) and to fire the entire process off we turn the ignition and .Start() Smile

This is all then done asynchronously.

Advertisements

4 Responses to SharePoint FBA & Reactive Extensions

  1. Hi, I’m trying this out, but the:

    webService.CallService(“/_vti_bin/Lists.asmx”, ActionGetList, string.Format(SoapGetList, “Pages”)).Subscribe(Handle);

    line keeps throwing an error:

    The best overloaded method match for ‘System.IObservable.Subscribe(System.IObserver)’ has some invalid arguments

    Seems like it doesn’t like the signature for:

    private static void Handle(XDocument doc){
    Console.WriteLine(doc.ToString());
    }

    Any thoughts?

    Thanks

  2. Frode Hus says:

    There is no other error? Usually if it expects different parameters VS.NET will add another error message in addition to the one you have there.

    I just double checked the code in the post against the code I have running in my application and it’s identical and works just fine so I’m a bit clueless why it doesn’t for you. Did you copy it exactly?

  3. Well, this is really odd..there were actually 2 errors before, and I spent a good few hours trying to figure out what was wrong with the Handle signature, but it works just fine now and I haven’t done anything! Anyway, I still run into the same issue I have been trying to resolve for a couple of days now. I keep getting the annoying “System.Net.WebException…The remote server returned an error: NotFound” error. My actual issue is I have some code that calls a web service (with the soap header and all), and if I have this on the same WP7 page as the one where I get the authentication token, then it works fine. However, I move the same code to a new page, and pass the token, using:
    NavigationService.Navigate(new Uri("/mysite.xaml?token=" + token, UriKind.RelativeOrAbsolute));
    then it fails. Been following the guide here – , and then came across your post while trying to find an alternative solution. Anyway, I’ll post my query there and see. Thanks for your help. Great post btw 🙂

    • Frode Hus says:

      I did some more testing on this and you’re right; if the auth code and subsequent web service call all reside in the same place it will work, but if you place the code elsewhere (in my case in a different assembly) it will fail. If you find any answer to this, I’d love to hear it:)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: