Friday, September 30, 2011

SharePoint 2010: Full Trust Proxy


If you’re using the multi-tenant features of SharePoint, you will want tenants to use the sandbox.  But, you will quickly find limitations.  For example, developers cannot call a web service, read data from a external database, or write to the event log.  One solution is for the farm administrator to deploy a full trust proxy that developers can use.  Microsoft has a good description here, but there are no good examples.  So I created one.

Let’s create a full trust proxy that will allow developers to write to the event log.  Sandbox code is executed in SPUWorkerProcess.exe (see diagram below).  The Code Access Security (CAS) policy in this process does not allow developers to access the event log.  Therefore, we will write a full trust proxy that will marshal the call to SPUWorkerProcessProxy.exe which can access the event log.


Figure 1: http://msdn.microsoft.com/en-us/library/ff798433.aspx

Create the FullTrustProxy

Start by creating a new “Empty SharePoint Project” that is deployed as a “farm solution”.  Remember this is the solution that the farm administrator deploys.  The tenant developer’s code will be deployed as a sandbox solution.

Next, add a new class called EventLogProxyArgs that inherits from Microsoft.SharePoint.UserCode.SPProxyOperationArgs.  This is the class that will hold the data that needs to be marshaled from SPUWorkerProcess.exe to SPUWorkerProcessProxy.exe.  Therefore it needs to be marked with a Serializable attribute.  The code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.UserCode;

namespace BrianBeach.FullTrustProxy.Diagnostics
{
    [Serializable]
    class EventLogProxyArgs : SPProxyOperationArgs
    {
        private string source;
        private string message;

        public string Source
        {
            get { return source; }
            set { source = value; }
        }

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
    }
}

Now, add a another class called EventLogProxy that inherits from Microsoft.SharePoint.UserCode.SPProxyOperation.  You will need to implement the Execute method.  The execute method is passed a copy of the EventLogsProxyArgs we created above.  The code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.UserCode;

namespace BrianBeach.FullTrustProxy.Diagnostics
{
    class EventLogProxy : SPProxyOperation
    {
        public override object Execute(SPProxyOperationArgs args)
        {
            string source = ((EventLogProxyArgs)args).Source;
            string message = ((EventLogProxyArgs)args).Message;
            System.Diagnostics.EventLog.WriteEntry(source, message);
            return null;
        }
    }
}

Create the Feature

Start by adding a new farm feature and fill out the title and description.



Now add an event receiver.  The event receiver will register the proxy with SharePoint. The code is below:

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.UserCode;
using Microsoft.SharePoint.Administration;

namespace BrianBeach.FullTrustProxy.Diagnostics.Features.Feature1
{

    [Guid("2f2d1b78-c30b-4197-ba44-06052b97448c")]
    public class Feature1EventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPUserCodeService service = SPUserCodeService.Local;
            string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba8f794a3acc2269";
            string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
            SPProxyOperationType operation = new SPProxyOperationType(assembly, type);
            service.ProxyOperationTypes.Add(operation);
            service.Update();
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPUserCodeService service = SPUserCodeService.Local;
            string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba8f794a3acc2269";
            string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
            SPProxyOperationType operation = new SPProxyOperationType(assembly, type);
            service.ProxyOperationTypes.Remove(operation);
            service.Update();
        }
    }
}

Deploy and Test

You’re ready to deploy your package.  Tenant developers can call the proxy from the sandbox.  Here is an example:

string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e6c3143148ad819b";
string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
EventLogProxyArgs args = new EventLogProxyArgs();
args.Source = “SP2010 Sandbox”;
args.Message = “A message from the sandbox!!!”;
SPUtility.ExecuteRegisteredProxyOperation(assembly, type, args);

Now, if you’re like me, the thought of writing seven lines of code to write one line to the log is crazy.  Therefore, I  added one more class to my solution with a helper method tenant developers can call.

Add a class called EventLog.  You will need to mark this class with the AllowPartiallyTrustedCallersAttribute attribute so that users can call it from the sandbox.  NOTE: I also marked the EventLogProxy and EventLogProxyArgs classes as internal so that developers would not be confused by them.  The code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Utilities;

[assembly: System.Security.AllowPartiallyTrustedCallersAttribute()]
namespace BrianBeach.FullTrustProxy.Diagnostics
{
    public class EventLog
    {
        public static void WriteEntry(string Source, string Message)
        {
            string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e6c3143148ad819b";
            string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
            EventLogProxyArgs args = new EventLogProxyArgs();
            args.Source = Source;
            args.Message = Message;
            SPUtility.ExecuteRegisteredProxyOperation(assembly, type, args);
        }
    }
}

At this point a developer can write to the event log with a single line of code as follows:

EventLog.WriteEntry(“SP2010 Sandbox”, “A message from the sandbox!!!”);

Finally, you can download the solution here.

you will likely run into issues.  If you need to check which proxies are registered, you can use the following PowerShell script.

$Service = [Microsoft.SharePoint.Administration.SPUserCodeService]::Local
$service.ProxyOperationTypes | Format-List

4 comments:

  1. Just wanted to add here that when I tried to create a full trust proxy, I got a TargetInvocationException on the call to ExecuteRegisteredProxyOperation and was scratching my head for an hour or two. Checked the InnerException and it turned out to be FileNotFoundException caused because the proxy project made references to DLLs that were not also included as additional assemblies in the sandboxed solution. Since it can't see the ones in the GAC, it can't load the ...Args or ...Client classes. Maybe a sound reason to split the solution up into 3 seperate projects? (The 3rd one being just to hold the arguments and client calling classes) - food for thought.

    ReplyDelete
  2. I should probably make sure I type the correct URL when I make a comment, huh? :-P

    ReplyDelete
  3. Can I monitor a sandbox solution that uses full trust proxy calls? Maybe I can but this statistics would be inaccurate right?

    ReplyDelete