Sandoxes and Proxies
Microsoft SharePoint Server offers a sandbox solution, which restricts users and developers from accessing resources outside the site collection to which it is deployed. This can be an advantage to SharePoint administrators because they can provide a development environment to users and not have to worry about monitoring applications for potential security risks: risky activities are simply not allowed in a sandbox solution.
But sometimes a sandbox application has a legitimate need to access resources outside the site collection to which it is deployed. For example, an application may need to access a file on disc or call an external web service.
SharePoint does provide a way to accomplish this. To access resources outside a site collection from within a sandbox solution, you must implement and call a Full Trust Proxy.
A Full Trust Proxy is deployed to the web farm and has more rights than a sandbox solution. It also exposes a simple interface that can be called from sandbox code.
Creating the Full Trust Proxy
To create a Full Trust Proxy, simply create a new empty SharePoint project.
The SharePoint Configuration wizard launches. At the "Trust level" prompt, select "Deploy as a farm solution". Click [Finish] to create the project.
Add to your project, a public class to act as a proxy. It doesn't matter what you name this class, but I like the name to end in "Operation" indicating that it is called to perform some secure operation.
At the top of the Operation class file add the following "using" statement:
using Microsoft.SharePoint.UserCode;
Change the Operation code so that it inherits from SPProxyOperation; then, override the Execute method, as shown below.
public class DivisionOperation : SPProxyOperation
{
public override object Execute(SPProxyOperationArgs args)
{
...
}
}
This is all that is necessary to create a Full Trust Proxy class. Your sandbox client will call the Execute method, which (when the project is deployed to the SharePoint farm) will have rights to call outside the sandbox code.
Notice that the Execute method accepts a single argument of type SPProxyOperationArgs and returns an object. This interface was kept generic so that it could be used in almost any situation to pass in and return out almost any type of arguments. The trick is to define the argument types appropriate to your application and cast them when necessary. If we want to pass in multiple values or return multiple values, we should create classes to hold those values.
For the input arguments, create a new public class in your project that inherits from Microsoft.SharePoint.UserCode.SPProxyOperationArgs. Again, the name doesn't matter, but I like to use the same root name as the Operation class and end the name with "Args". Decorate this class with the [Serializable] attribute, so it can be passed across process boundaries without loss of data. Then add public properties that you want your client to pass into the Execute method of the Operation class. It isn't required, but I like to add a constructor that allows me to initialize all the properties when instantiating this object. Below is an example of such a class.
[Serializable]
public class DivisionArgs : SPProxyOperationArgs
{
public DivisionArgs(int divisor, int dividend)
{
Divisor = divisor;
Dividend = dividend;
}
public int Divisor { get; set; }
public int Dividend { get; set; }
}
Next, you may wish to create a class to hold multiple return values. This class is not necessary if you only want to return a primitive type, such as a number or string. I like to keep the same naming convention, using the same base name as the other classes and a suffix of "ReturnValues". This class should also be marked Serializable. Below is an example of such a class.
[Serializable]
public class DivisionReturnValues
{
public int Quotient { get; set; }
public int Remainder { get; set; }
}
In the above example, the client can pass to the Execute method an object of type DivisionArg and cast Execute's return value to a DivisionReturnValues object. This is possible because Execute accepts an SPProxyOperationArgs input and returns an object and because the DivisionArgs and DivisionReturnValues classes inherit from these objects.
To utilize the values passed in, the first thing the Execute method should do is cast the input arguments to the well-known type we are using. In this case, that type is DivisionArgs. Then, the Execute will perform whatever work it needs to perform and return the well-known type that the client is expecting. Below is a sample proxy doing this.
public class DivisionOperation : SPProxyOperation
{
public override object Execute(SPProxyOperationArgs args)
{
// Cast the input args to the specific type
var divArgs = args as DivisionArgs;
// Perform some action
DivisionReturnValues returnValue = Divide(divArgs);
// Return an object
return returnValue;
}
///
/// Divide one number by another to determine the quotient and remainder
///
/// DivisionArgs containing Divisor and Dividend
///
protected DivisionReturnValues Divide(DivisionArgs divArgs)
{
int divisor = divArgs.Divisor;
int dividend = divArgs.Dividend;
int remainder;
int q = Math.DivRem(divisor, dividend, out remainder);
var returnValue = new DivisionReturnValues() { Quotient = q, Remainder = remainder };
return returnValue;
}
}
To keep things simple, our example proxy is just doing some simple division. In the real world, this task could be performed just as easily within a sandbox solution. Just imagine that the Proxy is doing something that the Sandbox is incapable of, such as calling an external web service or accessing the file system or accessing some data outside the sandbox site.
Finally, you will need to add code to Activate and Deactivate a feature when the Full Trust Proxy is deployed.
In the Solution Explorer, right-click the Features folder of the Full Trust Proxy project and select Add Feature. Right-click the newly-created Feature node and select Add Event Receiver. Visual Studio creates a class that inherits from SPFeatureReceiver. Notice the commented-out event handler code in this class.
Add event handlers for FeatureActivated and FeatureDeactivating events, as in the example below
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPUserCodeService userCodeService = SPUserCodeService.Local;
if (userCodeService != null)
{
// Define a variable to describe the proxy
SPProxyOperationType proxyOperation
= new SPProxyOperationType(
this.GetType().Assembly.FullName,
typeof(RequestRightsOperation).FullName);
// Add the proxy to the UserCodeService
userCodeService.ProxyOperationTypes.Add(proxyOperation);
// Save changes
userCodeService.Update();
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
// Retrieve e reference to the UserCodeService
SPUserCodeService userCodeService = SPUserCodeService.Local;
if (userCodeService != null)
{
// Define a variable to describe the proxy
SPProxyOperationType proxyOperation = new SPProxyOperationType(
this.GetType().Assembly.FullName,
typeof(RequestRightsOperation).FullName);
// Remove the proxy to the UserCodeService
userCodeService.ProxyOperationTypes.Remove(proxyOperation);
// Save changes
userCodeService.Update();
}
}
The proxy must be deployed to the SharePoint server and registered in the Global Assembly Cache. The following Powershell commands will accomplish this. Of course, you will need to replace the path with the path of the bin directory on your computer.
add-spsolution -literalpath C:\Giard\MyFullTrustProxy\MyFullTrustProxy\bin\Debug\MyFullTrustProxy.wsp
or
Update-SPSolution -identity MyFullTrustProxy.wsp -gacdeployment -force -literalpath C:\Giard\MyFullTrustProxy\MyFullTrustProxy\bin\Debug\MyFullTrustProxy.wsp
Install-SPSolution -identity MyFullTrustProxy.wsp -gacdeployment -force
You can see the solution in the Global Assembly Cache (GAC) by navigating to c:\windows\Assembly on the server in Windows Explorer, as shown in Figure 3. Make a note of the Public Key Token. You will need this value when you call the proxy.
The Client: A Sandbox Web Part
To create the client, create an Empty SharePoint project and add a Web Part (Fig 3). Do NOT select Visual Web Part as these are not supported within a Sandbox project.
Within the web part project, set a reference to the Full Trust Proxy class, so you can call the Proxy class's Execute method and access the 'Arguments' and 'ReturnValues' classes.
Add a "using" statement to the top of your web part to import the namespace of the classes in the full trust proxy.
Call the Proxy with code similar to the following:
object proxyResults =
SPUtility.ExecuteRegisteredProxyOperation(
"AssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=tokenGUID",
"OperationNamespaceAndClassName",
ProxyArgumentsInstance);
You will need to replace the Namespace, the Operations Class Name, and the Arguments class with the names of the classes you created and the PublicKeyToken with the value you saw in the Global Assembly Cache. Below is an example:
var myProxy = new MyFullTrustProxy.DivisionArgs(dividend, divisor);
object proxyResults =
SPUtility.ExecuteRegisteredProxyOperation(
"MyFullTrustProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62f295c504bc90d9",
"MyFullTrustProxy.DivisionOperation",
myProxy);
Since the proxy Operation returns an object, you will want to cast this to the expected return value.
The code below gets the gets two numbers from textboxes on a web part and passes them to a Full Trust Proxy operation that divides them and returns an object containing the quotient and remainder.
var results = proxyResults as DivisionReturnValues;
Now, you can get properties out of the return results.
int quotient = results.Quotient;
int remainder = results.Remainder;
Below is a full listing of the code in the sample application to call the proxy and retrieve the strongly-typed results
statusLabel.Text = "Calculating...";
try
{
int divisor = Convert.ToInt32(divisorTextbox.Text);
int dividend = Convert.ToInt32(dividendTextbox.Text);
var myProxy = new MyFullTrustProxy.DivisionArgs(dividend, divisor);
//quotientLabel.Text = (Convert.ToInt32 (divisorTextbox.Text) / Convert.ToInt32 (dividendTextbox.Text)).ToString();
object proxyResults =
SPUtility.ExecuteRegisteredProxyOperation(
"MyFullTrustProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62f295c504bc90d9",
"MyFullTrustProxy.DivisionOperation",
myProxy);
var results = proxyResults as DivisionReturnValues;
int quotient = results.Quotient;
int remainder = results.Remainder;
quotientLabel.Text = quotientLabel.ToString();
remainderLabel.Text = remainder.ToString();
statusLabel.Text = "Done";
}
catch (Exception ex)
{
statusLabel.Text = "An error occurred: " + ex.Message;
}
Sample Application
The sample application contains a Full Trust Proxy project (MyFullTrustProxy) that should be deployed to your SharePoint farm and activated; and a Sandbox solution (MySandboxWebparts) containing a web part (MathWebPart.cs) that calls the Full Trust Proxy. You can download the project here.
Conclusion
In this article, we looked at how to create and deploy a Full Trust Proxy assembly and how to call that assembly from a SharePoint Sandbox solution.