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

Friday, September 23, 2011

Multi-Tenant TCO


I recently presented an overview of multi-tenancy in one of my MBA classes.  I discussed how multi-tenant applications allow cloud vendors to achieve economies of scale.  As an example, I showed the effect on total cost of ownership for the infrastructure if a cloud vendor moved from physical to virtual and then to a multi-tenant architecture.  The multi-tenant architecture cuts cost by 80%.



Current State

Imagine that you work at a midsize company that develops and hosts a SaaS (Software as a Service) solution.  You have been growing recently and now have 100 customers.  Each customer gets its own server that your company hosts in its datacenter.

You are currently supporting 100 physical servers.  Unfortunately, the average utilization is only about 10%.  Over three years, the total cost of ownership is about $2.2M ($1M for hardware acquisition, $400K for power, and $800K for support).

Virtualization

Now imagine that IT moves to virtualization.  If we are only 10% utilized, it follows that we only need 10 physical servers (each hosting 10 virtual servers).  Leaving some room for growth and sudden spikes in demand, we decide to be safe and buy 20 servers (each hosting 5 virtual servers).  

This cuts our acquisition costs by 30%, and our power costs 80%, but notice that our support costs have gone up by 20%.  Why?  We now have 20 additional servers to support (100 VMs and 20 physical).

Multi-Tenancy

Now image that we rewrote our applications to allow us to host multiple clients on the same server (i.e. multi-tenant).  Again we decide to be safe and buy 20 servers.  But, note that we longer need the 100 virtual servers.  Therefore, our acquisition costs drop dramatically.  We just cut $500K in licensing costs.  In addition, out support team is only supporting 20 servers saving another $750K.

Conclusion

Multi-tenant applications have dramatic impact on total cost of ownership.  This allows cloud vendors to achieve economies of scale.   What does this mean for the CIO?  In general the best option for the average IT department is virtualization.  Therefore, a SaaS solution can often be cheaper than hosting a solution in your own datacenter.  This is true even after the vendor applies his markup.

BTW: Checkout the spreadsheet here.  You can adjust my assumptions and tune the results.