0 Comments

The Problem

While playing around with SharePoint web service from Silverlight out of browser, I bumped into web service proxy limitation where I am forced to authenticate no matter what I pass to the ClientCredential.UserName property of the web service client proxy.

//This does not work... will get authentication prompt
var request = HttpWebRequest.Create(uri);
request.ClientCredential.UserName.UserName = "DOMAIN\\user1";
request.ClientCredential.UserName.Password = "password"; 

The Authentication Problem Workaround

Luckily, starting from Silverlight 3 up, you can use the ClientHttp object that allows passing specific credential for the web request.

//This now work wonderfully from Out of Browser Silverlight 3 or up
var request = WebRequestCreator.ClientHttp.Create(new Uri("http://localhost/_vti_bin/lists.asmx", UriKind.Absolute));

//This is required, or otherwise you will be prompted for authentication
//It basically said, no don't use the current login, I'll tell you what login to use later.
request.UseDefaultCredential = false

//Pass your credential here...
request.Credentials = new NetworkCredential("user1", "password", "DOMAIN");

Ouila! Authentication problem solved.  Bye bye authentication prompt.  See you later... or not.

What About My Web Service?

Now... for the second problem, without the nicely generated web service proxy, how the heck am I going to call my web service method?

Well, luckily, SharePoint and other web service for that matter, give you a neat example of what to pass in a SOAP message like so:

if you put something like http://localhost/_vti_bin/lists.asmx into your browser Address (URL) field, you'll get this:

Sharepoint_WebService_EndPoint

In this case, I am interested in adding a list item to one of my SharePoint list.  The method (SOAP action) for this is UpdateListItems.  If you don't believe me, see this.

So if you click the UpdateListItems from the nice list above, you'll get this:
Sharepoint_WebService_UpdateListItems

See that SOAP message example (in particular the top one which is a SOAP 1.1 request message)?
Yeah it's a bit ugly, but trust me underneath the nicely generated web service proxy that you usually get when adding a service reference, you'll see this message somewhere in there if you open up the generated proxy client code in one shape or form. 

<digress>Actually if you look inside the generated proxy inside Reflector or similar tool like ILSpy for those who don't want to pay for Reflector, you'll see the serializable form of this as class and class members which later on will be serialized into
the XML format that you see above. </digress>

Well, like it or not, you will need to create your own web service wrapper around the SOAP message.  The good thing is... it's not that hard.

Below is a bit of code that I whipped out and refactored in about 2 hours to do this (for now only the UpdateListItems is implemented plus the rest of SOAP Web Service infrastructure):

using System;
using System.IO;
using System.Net;
using System.Linq;
using System.Xml.Linq;
using System.Net.Browser;
 
namespace SilverlightApplication9
{
    public class SharePointWebService
    {
        static XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
        static XNamespace xsd = "http://www.w3.org/2001/XMLSchema";
        static XNamespace soap = "http://schemas.xmlsoap.org/soap/envelope/";
        static XNamespace sharepoint = "http://schemas.microsoft.com/sharepoint/soap/";
 
        XElement SoapEnvelopeTemplate;
        Uri WebServiceUri;
        NetworkCredential Credential;
 
        public SharePointWebService(Uri spWebServiceUri, NetworkCredential credential)
        {
            WebServiceUri = spWebServiceUri;
            Credential = credential;
 
            SoapEnvelopeTemplate = new XElement(soap + "Envelope",
            new XAttribute(XNamespace.Xmlns + "xsi", xsi),
            new XAttribute(XNamespace.Xmlns + "xsd", xsd),
            new XAttribute(XNamespace.Xmlns + "soap", soap),
            new XElement(soap + "Body"));
        }
 
        public void UpdateListItemsAsync(string listName, XElement updates, Action<XDocument> callback)
        {
            var message = CreateUpdateListItemsMessage(listName, updates);
 
            SoapActionInvokeAsync(
                "http://schemas.microsoft.com/sharepoint/soap/UpdateListItems",
                message,
                callback);
        }
 
        protected void SoapActionInvokeAsync(string soapAction, XElement soapMessage, Action<XDocument> callback)
        {
            WebRequest request = WebRequestCreator.ClientHttp.Create(WebServiceUri);
 
            if (Credential != null)
            {
                request.UseDefaultCredentials = false;
                request.Credentials = Credential;
            }
 
            request.Headers["SOAPAction"] = "\"" + soapAction + "\"";
            request.ContentType = "text/xml; charset=utf-8";
            request.Method = "POST";
 
            var message = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + soapMessage.ToString();
 
            request.BeginGetRequestStream((ar1) =>
            {
                using (var stream = request.EndGetRequestStream(ar1))
                {
                    using (var writer = new StreamWriter(stream))
                    {
                        writer.Write(message);
                    }
                }
 
                request.BeginGetResponse((ar2) =>
                {
                    XDocument xml = XDocument.Load(
                        request.EndGetResponse(ar2).GetResponseStream());
 
                    callback(xml);
                }, null);
            }, null);
        }
 
        public XElement CreateUpdateListItemsMessage(string listName, XElement updates)
        {
            EnsureSharePointNamespace(updates);
 
            var body = new XElement(sharepoint + "UpdateListItems",
                new XAttribute("xmlns", sharepoint),
                new XElement(sharepoint + "listName", listName),
                new XElement(sharepoint + "updates", updates));
 
            var envelope = XElement.Parse(SoapEnvelopeTemplate.ToString());
            envelope.Descendants(soap + "Body").First().Add(body);
 
            return envelope;
        }
 
        protected XElement EnsureSharePointNamespace(XElement el)
        {
            foreach (var e in el.DescendantsAndSelf())
            {
                e.Name = sharepoint.GetName(e.Name.LocalName);
            }
 
            return el;
        }
    }
}

And here is an example of how to use it from Silverlight:

var ws = new SharePointWebService(
                new Uri("https://localhost/_vti_bin/lists.asmx", UriKind.Absolute),
                new NetworkCredential("user1", "password", "DOMAIN"));
 
            ws.UpdateListItemsAsync("jtest",
                XElement.Parse(@"<Batch><Method ID=""1"" Cmd=""New""><Field Name=""Title"">New item</Field></Method></Batch>"),
                (doc) =>
                {
                    Dispatcher.BeginInvoke(() =>
                    {
                        textBox1.Text = doc.ToString();
                    });
                });

This has been a pretty fun exercise... It would be better if the proxy ClientCredential code can do similar thing, but well... live sucks and that's why you are a coder, right?  

<digress>Some of you might be thinking what the hell the relation between live sucks and being a coder.... Don't worry, I don't know either, LOL.  Guess that's why this blog is called Incoherent Rambling... </digress>

0 Comments

While playing around with SharePoint 2010, I want to add a link to my site home page that will do the same thing when I am in a particular document / form library and click the Add Document link. 

In other word, how can I launch Word or Excel or InfoPath from my own link without having to go inside the library and clicking Add Document?

At first, I couldn't figure out how to do this.  Decided to dive into the HTML source of the Form Library AllItems.aspx view and found a CoreInvoke JS method, like:

CoreInvoke(
    'createNewDocumentWithProgIDEx', 
    'template source URI', 
    event, 
    'save location URI', 
    'SharePoint.OpenXMLDocuments', 
    true); 

I plop that inside a link tag and it worked. 

After the fact, I look around the web and finally found someone else that has done this already...

See: http://www.sharepointkings.com/2010/02/create-new-document-link-in-listview.html

The syntax is something like:

createNewDocumentWithProgID(templateSourceURI, saveLocation, docType, false);

In my case, I want to launch an InfoPath form template, and I found the following work just fine:

createNewDocumentWithProgID(
    'http://intranet/spike/FormLib1/Forms/template.xml', 
    'http://intranet/spike/FormLib1', 
    'SharePoint.OpenXMLDocuments', 
    false);