Logo SharePoint Thoughts   Downloads   About   Up to Bluedog Limited
Advanced coding technique: using AppDomains to get past OM limitations
Posted on 11/15/2005 1:15 AM by Maurice Prather
Updated on 11/15/05 with links to reference articles on impersonation and code access security
Published: June 12, 2004
Today's topic requires advanced coding knowledge.  I will try my best to layout the fundamental pieces, but to prevent this post from becoming too large, you may be required to read external references.
 
There are some interesting limitations placed on the SharePoint OM.  Those limitations can really make some operations very hard if not impossible to complete. 
 
In some cases, you can revert to self (thus assuming the identity of the AppPool) or otherwise impersonate a user identity with higher permissions than the current user to allow certain OM methods to execute.  On top of that, you may have used the SPSite.CatchAccessDeniedException property to prevent those annoying authentication dialogs from appearing.  However, there are times, neither technique will suffice.
 
Here's an example:

foreach (SPRole r in this.CurrentWeb.CurrentUser.Roles) {
  writer.WriteLine (Utility.ParagraphEnclose("Role: " + r.Description));
}

For an admin, this works great.  For any other user, this absolutely fails.  Even if you were to wrap this call within the context of an impersonated user, it would still fail.
 
Why?
The SharePoint object model, when running under the context of an IIS request, will always validate its actions against the original context of the request.  Therefore, reverting to self or impersonating an admin account is absolutely worthless.
 
Is this a bug or bad design?
It's both.  However, that's another topic altogether.
 
Are there any solutions?
Yes... and this where you need to put your coding hat on. 
 
The limiting factor is the fact the OM always looks at the original identity of the http context.  If you can step outside of that context, the OM will operate in a "normal" console-like fashion.  It will be forced to use identity of the thread, rather than attempting to fallback to the identity of the http context.
 
How can this be accomplished?
It's quite simple, but rather complex at the same time.
  1. Revert to self or impersonate
  2. Create an AppDomain (I'll refer to this as the Secondary AppDomain)
  3. Do the work in the Secondary AppDomain.
  4. Marshall back the necessary results
  5. Unload the AppDomain
  6. Resume orginal identity

Please note the following terminology will be used to describe where an action occurs:

  • Primary AppDomain: the original AppDomain where your WebPart is running.  This AppDomain was created by Asp.Net.
  • Secondary AppDomain: the AppDomain created by your WebPart code.

If you're not familiar with the concept of AppDomains, I strongly suggest that you visit MSDN for more information.  There are a large number of articles which describe how AppDomains can be used.  Here's a small set of useful links:

.Net Framework Class Library: AppDomain Class
Suzanne Cook's .NET CLR Loader Notes: Unloading an Assembly
cbrumme's WebLog: AppDomains ("application domains")

As you can see, AppDomains offer a great way of isolating code.  In our case, this isolation ensures we effectively "lose" the http context whenever working in the secondary AppDomain. However, getting data from one AppDomain to another requires some work as it does not come for free.  The AppDomain class offers functionality which allows you to execute code in a secondary AppDomain.  This method is rather clunky as you must first set data, execute, and finally retrieve data (reference AppDomain.DoCallBack Method for more information).  I prefer to use a different technique, which is discussed in just a bit.

The key thing to remember when working with AppDomains and, more importantly, marshalling data between AppDomains is the fact you wish isolate which assemblies are loaded in which AppDomain.  You can pass any type of data between AppDomains; however, if your secondary AppDomain attempts to pass a complex object, the CLR will attempt to load the assembly which contains that object in the primary AppDomain.  For this discussion, this factoid may not absolutely critical, but it definitely something to remember given that loading an assembly will lock the given assembly.

My favorite technique for using AppDomains requires a little more overhead, but it is amazingly flexible.  The technique is fondly called the "interface loader" technique.  It works on the premise that method execution is controlled via an interface.  This makes your code much easy to maintain and use once you've setup the necessary framework.  Here's a quick summary of what is needed:

  1. Define an interface class
  2. Define a remote loader class which implements the interface
  3. Define a factory class which 
    • creates the secondary AppDomain
    • Instantiate the remote loader class in the secondary AppDomain
    • Retrieves interface handle to the instance created in step b.
  4. Code in primary AppDomain executes methods via interface. 

To help all of this make sense, some code sample is needed. 

I'll quickly outline each section so that you can see how all of this fits together.  Please note that in order to load assemblies in the secondary AppDomain, a higher set of permissions is required both from the code access security side and the user identity.  If you want a complete list of permissions, you'll have to dig into the msdn documentation. 

Update 11/15/2005
For more information on code access security, please check out the following posts:

For this example, I would simply suggest I would simply suggest a) grant your assembly FullTrust by installing the assembly into the GAC (yes, this is an anomaly for me) and b) impersonate an admin as noted in the comments.

To help make this code as simple as possible, all calls to revert to self/impersonate have been eliminated.  Additionally, please note this sample is in no way optimized.  Before implementing this technique, please be aware of performance and memory consumption implications.  The purpose of the sample code is is to demonstrate how to implement the loader interface technique.  You should definitely tailor the code to account for shadow copying and other advanced AppDomain properties (especially if you intend to install your assembly in the bin folder).

The loader interface technique is without a doubt one of the best tools in a coder's arsenal.  I've used the technique for the past two and half years on a wide variety of projects... and, as you can see, it can be readily used to make the SharePoint OM ignore an http context to help make your WebPart code run under all user identities.

Update 11/15/2005
  1. For more information on how to properly handle impersonation when using this technique, please reference Q&A on the interface loader technique and AppDomains.
  2. A working demo is available - AppDomain - Interface Loader - Demo Page.

-Maurice

Step 1 - Define an interface class

  public interface IRemoteMethods : IDisposable {
   
    string ListRoles (Guid siteID, Guid webID, Guid userID);
   
  }  // End of IRemoteMethods interface

Step 2 - Define a remote loader class which implements the interface

  public class RemoteMethods : MarshalByRefObject, IRemoteMethods  {
   
    string ListRoles (Guid siteID, Guid webID, Guid userID) {
     
      string userRoles = null;
        using (SPSite site = new SPSite(siteID)) {
          using (SPWeb web = site.OpenWeb (webID)) {
            foreach (SPRole r in web.Users.GetByID(userID).Roles) {
              userRoles += Utility.ParagraphEnclose("Role: " + r.Description);
            }
          }
        }
        return userRoles;
       
    }  // End of ListRoles

    public void Dispose() {
      // Add dispose code...
    }  // End of Dispose

  }  // End of RemoteMethods class

Step 3 - Define a factory class

  public class RemoteInterfaceFactory : IDisposable {
   
    private AppDomain appDomain               = null;
    private IRemoteMethods remoteInterface = null;
   
    public IRemoteMethods Loader {
      get {
        return remoteInterface;
      }
    }  // End of property Loader
   
    public RemoteInterfaceFactory () {
     
      this.appDomain = AppDomain.CreateDomain (SecondaryAppDomain, AppDomain.CurrentDomain.Evidence);
       
        // Create an instance of the RemoteMethods class in the secondary domain
        // and get an interface to that instance.  This ensures you can use the
        // instance but are not loading the instance into the primary AppDomain.
        remoteInterface = (IRemoteMethods) appDomain.CreateInstanceAndUnwrap (Assembly.GetExecutingAssembly().GetName().FullName, typeof(RemoteMethods).FullName);
        
    }  // End of RemoteInterfaceFactory
   
    public void Dispose() {
     
      if (this.remoteInterface != null) {
        this.remoteInterface.Dispose ();
        this.remoteInterface = null;
      }
     
      if (this.appDomain != null) {
        AppDomain.Unload (appDomain);
        this.appDomain = null;
      }
     
    }  // End of Dispose
   
  }  // End of RemoteInterfaceFactory class

Step 4 - Code in primary AppDomain executes methods via interface

  string listOfCurrentUserRoles = null;   
  // Impersonate or call revert to self (this is left to the reader to implement)
  using (RemoteInterfaceFactory remoteFactory = new RemoteInterfaceFactory ()) {
    listOfCurrentUserRoles = remoteFactory.Loader.ListRoles (siteID, webID, userID);
  }
  // resume original identity
  writer.WriteLine (listOfCurrentUserRoles);

re: Advanced coding technique: using AppDomains to get past OM limitations
Hi Maurice,
 
Great post, but you leave out the code for 'revert to self', which I can't figure out how to do :-(
 
Any help?
 
Also, SPUserCollection.GetByID expects an int rather than a Guid.
 
Thanks!
Jim Duncan @ 8/27/2004 6:47 PM
re: Advanced coding technique: using AppDomains to get past OM limitations

I purposefully left out that section of code so as to not divert attention from main premise of the post.  On top of that, I also felt that sufficient resources exist and can be easily googled (thus allowing you to either revert-to-self or impersonate based on your needs).  If you don’t have any luck with finding a working solution, let me know and I’ll dig around and see what I come up with.

Maurice Prather @ 9/2/2004 12:08 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hi, which assemblies or references are required for this code.
I am having the exact same problem with iterating through users and showing their roles.
dave @ 10/6/2004 9:19 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
The code presented relies on standard .net assemblies (i.e. System.dll).  The impersonation or reverttoself code that you implement will have its own requirements.
Maurice Prather @ 10/9/2004 9:07 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
I have implementet the code from above, but using the SPS OM UserProfileManager isted. (For listing all users in the ProfileDatabase) But no matter what I do, I'll get this error when I'm not an administrator.:
 
Access Denied: Only an administrator may enumerate through all user profiles.
 
I have tried to change the ThreadPrincipal and PrincipalPolicy to an administrator account with no luck. It seems to me that it do not matter that I create a new AppDomain.
 
Heres a sample of my code...
 
The UserProfileListFactory.cs class:
 
 public class UserProfileListFactory : IDisposable
 {
  private AppDomain appDomain              = null;
  private IUserProfileList remoteInterface = null;
  public IUserProfileList Loader
  {
   get
   {
    return remoteInterface;
   }
  } 
  public UserProfileListFactory()
  {
    this.appDomain = AppDomain.CreateDomain("SecondaryAppDomain", AppDomain.CurrentDomain.Evidence);
    string AssemFullName = Assembly.GetExecutingAssembly().GetName().FullName;
    string classFullName = typeof(UserProfileList).FullName;
    remoteInterface = (IUserProfileList) appDomain.CreateInstanceAndUnwrap (Assem, classFullName );
  }
 
 
The UserProfileClass.cs
 
  public ArrayList GetUserProfiles()
  {
   string localPath = @"http://"; + Environment.MachineName;
   Uri uri = new Uri(localPath);
   TopologyManager topMan = new TopologyManager();
   PortalSiteCollection sites = topMan.PortalSites;
   PortalContext ctx = PortalApplication.GetContext(sites[uri]);
   UserProfileManager upManager = new UserProfileManager(ctx);
   ArrayList alUserProfiles = new ArrayList();
   IEnumerator IUserProfiles = upManager.GetEnumerator();
   IUserProfiles.Reset();
   while(IUserProfiles.MoveNext())
   {
    alUserProfiles.Add(((UserProfile)IUserProfiles.Current).ID.ToString());
   }
   return alUserProfiles;
  }  
 
 
What to do ?! Help!
Carsten Keutmann @ 10/15/2004 8:02 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Please note that my code work under administrator. The linie
    remoteInterface = (IUserProfileList) appDomain.CreateInstanceAndUnwrap (AssemFullName, classFullName );
is fixed.
Carsten Keutmann @ 10/15/2004 8:05 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Are you sure the impersonation (or revert to self) is working as listed in step 4?  Note that the call must be made before the factory class is created.
Anonymous User @ 10/19/2004 5:57 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
 
Based on this website, it seems that we cant instantiate an object from an interface.
 
[quote] private IRemoteMethods remoteInterface = null;
[/quote]
 
could you explain, i m very confuse? What does this code above do.
btw. i m new to C# and to SPS/WSS programming
Anonymous User @ 1/16/2005 7:58 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
sorry for the previous post. quite a noobish question.
umm i m facing when i try to deploy the codes given by the author.
i seem to get
 
Request for the permission of type System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed.
 
error. This exception is thrown when the following code tries to execute.
 
this.appDomain = AppDomain.CreateDomain("FriendlyNmae",AppDomain.CurrentDomain.Evidence);
can anyone help....i use an impersonation technique given by this web site
 
 
Is this because the..is LOCKED just like the author stated in this article.any walk around for this 
Anonymous User @ 1/27/2005 7:58 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Quick answers:
 
1) "private IRemoteMethods remoteInterface = null" simply declares an object of type IRemoteMethods and sets the object to a null value.
 
2) If you run into a security exception, then your assembly does not have the appropriate permissions.  The article mentions this requirement and includes a link where you can find more information.
 
-M.
Bluedog @ 3/9/2005 11:11 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Thank you!  This was a *major* help!  I spent a few hours Googling around, finding nothing but suggestions about how to get Web Parts to impersonate.  Tried messing with the thread CurrentPrincipal, main context, etc... and all I could figure out (from occasional failures) was that something in SharePoint was fetching credentials from IIS.  And sure enough, I was right.  This was a big time-saver, thanks again!
Joseph Riesen @ 3/22/2005 3:37 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Thanks for the article. I have a similar requirement, to check the LoginUser is member of a particular SiteGroup. When the webpart is accessed in Admin account it works fine. When the webpart is accessed in normal user account and I impersonated admin, it throws me an error and the error is "Unable to connect to database. Check database connection information and make sure the database server is running".
 
Any help is appreciated. Thanks
Ranga Potluri @ 4/8/2005 3:25 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Ranga,
 
I would double check the placement of your impersonation code.  It sounds as though you are trying to do something that requires a database query and you either have the wrong user context or are otherwise not impersonating properly.
Anonymous User @ 4/15/2005 6:19 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
this was very helpful
Anonymous User @ 5/17/2005 7:54 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hi,
 
I'm getting the following error. Any clue why I'm getting this error. It seems to me that it is unable to find the path of the .dll. How do I enforce to look in perticular direction?
 
File or assembly name InviteUserControl, or one of its dependencies, was not found.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.IO.FileNotFoundException: File or assembly name InviteUserControl, or one of its dependencies, was not found.
Source Error:

Line 28:    // and get an interface to that instance.  This ensures you can use the
Line 29:    // instance but are not loading the instance into the primary AppDomain.
Line 30:    remoteInterface = (IRemoteMethods) appDomain.CreateInstanceAndUnwrap (Assembly.GetExecutingAssembly().GetName().FullName,
Line 31:     typeof(RemoteMethods).FullName);
Line 32:        
 
Source File: c:\inetpub\wwwroot\inviteusercontrol\remoteinterfacefactory.cs    Line: 30
Assembly Load Trace: The following information can be helpful to determine why the assembly 'InviteUserControl' could not be loaded.

=== Pre-bind state information ===
LOG: DisplayName = InviteUserControl, Version=1.0.1983.21362, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = c:\windows\system32\inetsrv\
LOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===
LOG: Application configuration file does not exist.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Post-policy reference: InviteUserControl, Version=1.0.1983.21362, Culture=neutral, PublicKeyToken=null
LOG: Attempting download of new URL file:///c:/windows/system32/inetsrv/InviteUserControl.DLL.
LOG: Attempting download of new URL file:///c:/windows/system32/inetsrv/InviteUserControl/InviteUserControl.DLL.
LOG: Attempting download of new URL file:///c:/windows/system32/inetsrv/InviteUserControl.EXE.
LOG: Attempting download of new URL file:///c:/windows/system32/inetsrv/InviteUserControl/InviteUserControl.EXE.
 
Stack Trace:

[FileNotFoundException: File or assembly name InviteUserControl, or one of its dependencies, was not found.]
   System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +264
   System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +877
   System.AppDomain.CreateInstanceAndUnwrap(String assemblyName, String typeName) +0
   InviteUserControl.RemoteInterfaceFactory..ctor() in c:\inetpub\wwwroot\inviteusercontrol\remoteinterfacefactory.cs:30
   InviteUserControl.AddressBook.Page_Load(Object sender, EventArgs e) in c:\inetpub\wwwroot\inviteusercontrol\addressbook.aspx.cs:103
   System.Web.UI.Control.OnLoad(EventArgs e) +67
   System.Web.UI.Control.LoadRecursive() +35
   System.Web.UI.Page.ProcessRequestMain() +750
 
Thanks
Hari @ 6/6/2005 1:06 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
You'll get a System.IO.FileNotFoundException if it can't find the assembly in the GAC. (Did you forget to install the assembly into the GAC?)
 
Lars Dahl @ 7/15/2005 7:03 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Thanks for the post.  I think this will really help me. (assuming I can get it to work)
 
I'm actually looking to determine the roles for the current user and I tried the code you suggested.
 
I get an exception thrown when I call the site.openWeb or if I call site.AllWebs[WebID]
 
the exception text says "Cannot complete this action. Please try again."
 
Do you, or anyone, have any suggestions as to what I am doing wrong?
Hans Leuschner @ 8/6/2005 6:42 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Thanks for the post.  I have successfully implemented the technique you demonstrated.  Everything works as expected, usually.  Sometimes I get a SystemNotSupportedException saying that A WindowsIdentity object cannot be serialized across processes.  The strange thing is that if I execute the exact same code from a different part of my application, it works.  Any thoughts?
 
Thanks.
 
Mike @ 8/26/2005 9:19 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
A potential simpler solution to all this mess is go directly to the Sharepoint DB with an impersonation and get what you need to since we are already introducing hacks to get around MS bugs and poor design..
tg @ 9/3/2005 2:01 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
No.  Don't go against the database directly.  It's an unsupported action. 
Maurice Prather @ 9/3/2005 5:39 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
I'm getting the same error as Hans (Cannot complete this action. Please try again.)  I'm running the App Pool under an account named SPSExec.  If I impersonate this account, supplying credentials, I am able to access the site.AllWebs collection.  If I use RevertToSelf, which is SPSExec, I get the "Cannot Complete" error.  I really don't understand this.
Marc Weidinger @ 9/5/2005 8:02 PM
Maurice Prather
Thanks for the post. It's very helpful.
 
Our server is shared and no assembly is allowed in GAC. So I have to install assembly in the bin folder.
 
I tried creating a new AppDomainSetup object and specifying the ConfigurationFile and ApplicationBase directly. But got error.
 
I was able to get PortalContext, but failed to retrieve one list in portal. The error is:
 
Microsoft.SharePoint.SPException: Cannot complete this action.
Please try again. ---> System.Runtime.InteropServices.COMException (0x80004005): Cannot complete this action.
Please try again.
   at Microsoft.SharePoint.Library.SPRequestInternalClass.GetListsAsSafeArray(String bstrUrl, String bstrListInternalName, Int32 dwBaseType, UInt32 dwGetListFlags, UInt32 dwListFilterFlags, Boolean bPrefetchMetaData, Boolean bSecurityTrimmed, UInt32& pdwColCount, UInt32& pdwRowCount, Object& pvarDataSet)
   at Microsoft.SharePoint.Library.a.a(String A_0, String A_1, Int32 A_2, UInt32 A_3, UInt32 A_4, Boolean A_5, Boolean A_6, UInt32& A_7, UInt32& A_8, Object& A_9)
   --- End of inner exception stack trace ---
   at Microsoft.SharePoint.Library.a.a(String A_0, String A_1, Int32 A_2, UInt32 A_3, UInt32 A_4, Boolean A_5, Boolean A_6, UInt32& A_7, UInt32& A_8, Object& A_9)
   at Microsoft.SharePoint.SPListCollection.EnsureListsData(String strListName)
   at Microsoft.SharePoint.SPListCollection.get_Item(String strListName)
   at UserRegistration.module.RemoteMethods.getGPIProductsList() in c:\inetpub\jimdev\userregistration\module\remotemethods.cs:line 52

Our application is a normal ASP.NET site. We need to update some lists in one SharePoint Portal according to user's input. This site is using same Application Pool as the portal.
 
I'm new to AppDomain. I really appreciate it if anybody can share some idea how to install assembly in the bin folder.
Jim Chen @ 9/5/2005 9:49 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Given the recent popularity of this article and the number of stumbling points some folks are running into, I'm going to post a follow up article in the coming days.
 
Stay tuned,
-Maurice
 
Maurice Prather @ 9/7/2005 12:29 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
I've gotten this working after several late nights.  It's not super clean or optimized but it works.  If anyone would like my source code, let me know and I'll send it.  Better yet, give me a place to post a zip file.
 
Marc Weidinger @ 9/12/2005 7:22 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
I've been trying to implement this code and keep coming up with a StackOverflowException being thrown.  I have traced the error to the IRemoteMethods Loader method.  For some reason, the remoteInterface is null, although no exception is thrown when setting the remoteInterface object. 
 
remoteInterface = (IRemoteMethods) appDomain.CreateInstanceAndUnwrap
                    ( Assembly.GetExecutingAssembly().GetName().FullName,
                    typeof(RemoteMethods).FullName ); <-- remoteInterface is set to null
 
The two arguments for CreateInstanceAndUnwrap are correct when I write them out.
 
If anyone has any insight, I'd appreciate it.
Thanks. 
Tim Q @ 9/12/2005 8:49 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Can the Remotable object be hosted by a windows service?
How does the Secondary App Domain concept and the factory change in that scenario?
rasputin @ 10/31/2005 12:45 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Wooh !
Nice article , many thanks !!! I've spend a lot of time to code an aspx "admin application" to make some tool helping me in administrative task of a big intranet (10000 users) , and in one tool, I've used impersonation, identity admin on the webapp pool and finally I've work against the database witch I souldn't because not supported...
 
So, now I must try your code, I hope it will work ! :D
paslatek @ 11/30/2005 6:29 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
try setting allowunsafeupdates = true before updating the list. We got the same error until we used the above method.
Anonymous User @ 12/8/2005 7:34 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Seems to work for me using a much easier method, see below....
 
   SPUser UserID = currentSite.CurrentUser;
   WindowsImpersonationContext wic = CreateIdentity("userid", "domain", "password").Impersonate();
   SPWeb web = rootSite.OpenWeb();
   
   SPUserCollection Administrators = web.Roles["Administrator"].Users;
   SPUserCollection ContentManagers = web.Roles["Web Designer"].Users;
   
   
   foreach (SPUser SiteUser in Administrators)
   {
    if (SiteUser.LoginName==UserID.ToString())
    {
     LiteralControl myOptions;
     myOptions = new LiteralControl();
     myOptions.Text="Admin Tools: <a href=\"" + currentSite.Url + "/_layouts/1033/settings.aspx\">Settings</a>&nbsp;|&nbsp;<a href=\"" + currentSite.Url + "/_layouts/1033/viewlsts.aspx\">Lists</a>&nbsp;";
     Controls.Add(myOptions);
    }
   }
   foreach (SPUser SiteUser in ContentManagers)
   {
    if (SiteUser.LoginName==UserID.ToString())
    {
     LiteralControl myOptions;
     myOptions = new LiteralControl();
     myOptions.Text="Manage Content:&nbsp;<a href=\"" + currentSite.Url + "/_layouts/1033/viewlsts.aspx\">Lists</a>&nbsp;";
     Controls.Add(myOptions);
    }
   }
   wic.Undo();
Dan @ 2/28/2006 1:07 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Dan,
 
Impersonation, as you've described, is simply not supported.  It is not guaranteed to work in all cases.
 
Please reference KB article 892866 for more info.
 
-Maurice
Maurice Prather @ 3/7/2006 4:06 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
As best as I can tell, when I execute the command :
 
  remoteInterface = (IRemoteMethods)appDomain.CreateInstanceAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().GetName().FullName, typeof(RemoteMethods).FullName);
 
I get an error :Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: count
 
I have determined that the inputs to the call are Assembly is
WebApplication2, Version=1.0.2265.26860, Culture=neutral, PublicKeyToken=null   and the type is WebApplication2.WFF+RemoteMethods
 
Does anyone have a resolve for this?  I am fairly new to .NET and SharePoint.  Thank you in advance for your time.
 
Ed @ 3/16/2006 12:55 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
I added another try/catch and got better exception information.
 
System.IO.FileNotFoundException: File or assembly name WebApplication2, or one of its dependencies, was not found.
...
===Pre-bind state information===
LOG: DisplayName = WebApplication2, Version=1.0.227..., Culture=neutral, PublicKeyToken=null (Fully-specified)
LOG: Appbase=d:\windows\system32\inetsrv\
LOG: Initial PrivatePath=NULL Calling assembly : (Unkown).
LOG: Application configuration file does not exist.
LOG: Policy not being applied to reference at this time (private, custum, partial, or location-base assembly bind).
LOG: Post-policy reference: WebApplication2, Version 1.0.227..., Culture=neutral, PublicKeyToken=null
LOG:         "                                                           .EXE
 
How is the Appbase set? Why is the config file not found?
 
I added code to create an AppDomainSetup for the call to the app_domain found in public RemoteInterfaceFactory().
 
AppDomainSetup  appDomainSetup  =  new AppDomainSetup();
appDomainSetup.ApplicationName = appDomain.CurrentDomain.SetupInformation.ApplicationName + "_WFF";
appDomainSetup.ConfigurationFile = "web.config"
appDomainSetup.PrivateBinPath = "bin";
appDomainSetup.PrivateBinPathProbe = "true";
 
I also added to my web.config the following after </system.web> :
 
<configuration>
  <system.web>
.
.
.
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
           <codeBase version="2.0.0.0"
                      href=\\ip address\d$\Program Files\Common Files\...\BIN\WebApplication2.dll
       </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
 
Could anyone give me a resolve and thank you in advance for your time.
Ed @ 3/23/2006 10:39 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Thank you for the great post, you saved me a headache! :-)
 
BTW, I was also getting the System.IO.FileNotFoundException exception, but solved it by changing the appDomain.CreateInstanceAndUnwrap call to appDomain.CreateInstanceFromAndUnwrap. HTH.
 
- Marco
Marco Bellinasttp @ 5/5/2006 1:53 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hello,
 
First, thanks a lot for this article. This allows me to answer multiple needs in my company.
 
Everything works fine for all the users which are in the same domain as the account running the application pool. For all the users which are in other domains, I get the same error as Mike ( Mike message 8/26/2005 9:19 AM ) when creating the instance using the secondary AppDomain :
 
A WindowsIdentity object cannot be serialized across processes.
 
I use the impersonation code of the 15seconds article with LogUserA native method. The domains have approbations relations between them.
 
The exact exception message :
 
Exception has been thrown by the target of an invocation.
Server stack trace:
   at System.Reflection.RuntimeConstructorInfo.SerializationInvoke(Object target, SerializationInfo info, StreamingContext context)
   at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)
   at System.Runtime.Serialization.ObjectManager.FixupSpecialObject(ObjectHolder holder)
   at System.Runtime.Serialization.ObjectManager.DoFixups()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, IMethodCallMessage methodCallMessage)
   at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm)
   at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeMessageParts(MemoryStream stm)
   at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage.FixupForNewAppDomain()
   at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm)
   at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm)
Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at System.AppDomain.CreateInstance(String assemblyName, String typeName)
   at Calyon.Zeta.SharePoint.Security.RightsUtility.InternalGetUserRoles(Guid spsSiteId, Guid spsWebId, String domainUserName, Boolean useCache)
 
Any thoughts ? Thanks !
Gildas @ 5/19/2006 8:19 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hi, anyone able to help to translate these codes to vb.net? I need help to understand it in that context. Thanks.
Anonymous User @ 5/25/2006 7:54 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hi Maurice,
 
Is it possible to share with me this method with VB.NET? Thanks a lot. reon@commontown.com
Reon @ 5/25/2006 8:17 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hi, I've tried to implement these codes in Visual Basic. The codes are as follows but I can't seem to get it to work. I've removed the interface. Is it crucial to have it? I've a feeling the problem may be from the CreateDomain method, whereby I don't know whether there's a need to put in AddHost and AddAssembly for it, cos I dun really know where to get these info.
 
Public Class RemoteMethods
 Inherits MarshalByRefObject
 Public Function GetUserByID(ByVal site As Microsoft.SharePoint.SPWeb, ByVal userID As Integer) As SPUser
  Return site.AllUsers.GetByID(userID)
 End Function
 Public Sub Dispose()
 End Sub
End Class
Public Class UserFactory
 Implements IDisposable
 Private myAppDomain As AppDomain = Nothing
 Private remoteInterface As RemoteMethods = Nothing
 Sub New()
  Me.myAppDomain = AppDomain.CreateDomain("newAppDomain", AppDomain.CurrentDomain.Evidence)
  remoteInterface = myAppDomain.CreateInstanceAndUnwrap([Assembly].GetExecutingAssembly.GetName.FullName, GetType(RemoteMethods).FullName)
 End Sub
 Public ReadOnly Property Loader() As RemoteMethods
  Get
   Return remoteInterface
  End Get
 End Property
 Public Sub Dispose() Implements System.IDisposable.Dispose
  If Not Me.remoteInterface Is Nothing Then
   Me.remoteInterface.Dispose()
   Me.remoteInterface = Nothing
  End If
  If Not Me.myAppDomain Is Nothing Then
   AppDomain.Unload(myAppDomain)
   Me.myAppDomain = Nothing
  End If
 End Sub
End Class
 
'The code to access the information
Dim myUserFactory As UserFactory = New UserFactory
Dim Author As SPUser = myUserFactory.Loader.GetUserByID(site, Int32.Parse(AuthorTemp.Substring(0, AuthorTemp.IndexOf(";"))))
Reon @ 5/26/2006 2:59 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
ive implemented watevr uve said in this article..but still not able to unload the dll. Actually ive made the interface and 2 classe as u said here in a C#library and then it is being linked to another C++.net assembly. C# dll added as a refernce in the C++ .net assembly
made a method in the remoteinterface class which unloads the appdomain. But on unloading although no exceptions are being thrown the dll is not unloaded.
 
can anybody help me
 
thanx in advnce
crazie @ 7/14/2006 5:50 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
You're the king! You made my week. Now I will go fishing. Thanks for a brilliant explanation!
Jens @ 8/10/2006 11:22 PM
Maybe easyer way?
I tried approach with secondary AppDomain but with no luck. Maybe I missed something :(
I needed to get list of roles for current user.
Solution described below worked fine:
1). Create delegate
2). Call Invoke
3). Impersonate
4). Get list of roles
5). Undo impersonation
Here some code:
public delegate string[] GetRolesDelegate(Guid siteId, Guid webId, int userId);
....
public string[] GetRoles()
{
SPWeb currentWeb = SPControl.GetContextWeb(Context);
GetRolesDelegate d = new GetRolesDelegate(GetRoles);
string[] userRoles = d.Invoke(currentWeb.Site.ID, currentWeb.ID, currentWeb.CurrentUser.ID);
return userRoles;
}
public string[] GetRoles(Guid siteId, Guid webId, int userId)
{
WindowsImpersonationContext context = null;
string[] roles = null;
try
{
// LogonUser is my wraper function over
// API function LogonUser (see MSDN)
context = LogonUser(UserName, Domain, Password);

using (SPSite site = new SPSite(siteId))
{
using (SPWeb web = site.AllWebs[webId])
{
SPUser user = web.AllUsers.GetByID(userId);
SPRoleCollection userRoles = user.Roles;
roles = new string[userRoles.Count];
for (int i = 0; i < userRoles.Count; i++)
roles[i] = userRoles[i].Name;
}
}
}
finally
{
if (context != null)
{
context.Undo();
context.Dispose();
}
}

return roles;
}
Andrew Kurochka @ 8/17/2006 7:55 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Maurice,
 
Would impersonating AND clearing the HttpContext.Current property before calling into the OM work (even though unsupported ;)?
 
 
using( new ImpersonationScope("user","password","domain")
{
   using( new ClearContextScope(HttpContext) )
   {
      // Call OM
      doWork();
   }
}
 
 
Thanks!
/Jonas
 
Why?
The SharePoint object model, when running under the context of an IIS request, will always validate its actions against the original context of the request.  Therefore, reverting to self or impersonating an admin account is absolutely worthless.
 
Is this a bug or bad design?
It's both.  However, that's another topic altogether.
Jonas @ 10/3/2006 9:42 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Good article on usage of another AppDomain to get around the oblect model, but I think that using this approach is a bit of an overkill. To get around the OM reverting back to the IIS caller's cred's you can just use the existing web services that are built in to expose the information that you want.
 
The reason that I say that it is an overkill is because in order to create another AppDomain on the fly, the impersonating user would have to have admin access to the machine and this is not always available. Instead, I would recommed the following.
 
This example is assuming that the SharePoint application pool is running under an account that has rights to all the sites and you want to expose the users within the SharePoint role.
 
1. First revert to use the impersonation of the user who is running the app pool.
 
2. Consume the built in UserGroup web service for the site (You can find this at (http://<MySite>/_vti_bin/UserGroup.asmx).
 
3. Make a call to the GetUserCollectionFromRole method and iterate through the collection of users for the group.
 
4. Output your results.
 
Here is a bit of code for example...
 
I am omitting the UserGroupWebService because this just a regular proxy class that is easily created using your IDE. Just remember to override you UserGroupWebService constructor to set the Url dynamically.
 
using System.Security.Principal;
using System.Xml;
using Microsoft.SharePoint;
 
public class ImpersonateAsAppPoolId : System.IDisposable
{
  private WindowsImpersonationContext WinImpersonationContext = null;
  public ImpersonateAsAppPoolId()
  {
    Impersonate();
  }
  private void Impersonate()
  {
    try
    {
      WinImpersonationContext = WindowsIdentity.Impersonate(System.IntPtr.Zero);
    }
    catch{}  
  }
  private void UndoImpersonation()
  {
    try
    {
      if(WinImpersonationContext != null)
      {
        WinImpersonationContext.Undo();
      }
    }
    catch{}
  }
  public void Dispose()
  {
    UndoImpersonation();
  }
}

public class MyClass
{
  public XmlDocument GetAllUsersInSharePointRole(string roleName)
  {
    using(new ImpersonateAsAppPoolId())
    {
      UserGroupWebService.UserGroup userGroupWs = new    UserGroupWebService.UserGroup(SPControl.GetContextWeb(Context).Url + "/_vti_bin/UserGroup.asmx");
      userGroupWs.Credentials = System.Net.CredentialCache.DefaultCredentials;
      XmlDocument xDoc = new XmlDocument();
      xDoc.LoadXml(userGroupWs.GetUserCollectionFromRole(roleName).InnerXml);
   
      return xDoc;
    }
  }
}
 
Now, all you have to do is call the method GetAllUsersInSharePointRole and you get back an XmlDocument that contains all the users within your requested role. Just refer to the WSS SDK to see how the XmlDocument is formed so that you can extract the appropriate fields.
 
I would also recommend to cache your results for a given period so that every call to a page doesn’t hit the web service every time because web services can be a bit slow.
 
Happy Coding...
 
David Cooper
David Cooper @ 10/29/2006 7:28 PM
Whoooo hooo
I have been searching for days for a solution and this page has everything i need to accomplish it. Thanks to all who have posted comments as well, this is fantastic.
Jared @ 11/23/2006 3:08 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
David Cooper...
 
I agree the example presented is overkill.  However, I will contend that creating a web service is even more so. 
 
There are advantages to using a web service; however, there are some distinct disadvantages.
  1. You are increasing the surface area for security vulnerabilities.  Are you ready to test for Denial of Service, cross domain attacks, etc?
  2. Deployment - there is no easy way to readily deploy a web service (in v2).
  3. Its more stuff to manage from both the administrative and development standpoints

Yes, it's a workable solution but there is a cost associated with deploying a web part + web service solution.

-Maurice

Maurice Prather @ 11/27/2006 12:08 PM
Can we can access a sharepoint server remotely using object model by remotely?
I want to to access a sharepoint server remotely using object model remotely I mean a stand alone exe running on a machine where sharepoint is not installed. Is it possible? If anyone have some idea Please help me Thanks
Pradyumna @ 12/11/2006 3:14 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Maurice Prather...
 
The web service that I am talking about in my previous post is an out of the box web service that comes built in to the product (SharePoint) when you install it so I don't understand what web service deployment is needed. There is no need to create the web service, only the web part. Read the SDK for more information on what I am talking about.
 
Happy Coding...
 
David Cooper
David Cooper @ 12/23/2006 10:41 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
David,
 
I don't disagree with you.  For what its worth, I have read (and helped author portions of) the SDK many times. 
 
The technique you presented is valid as long as the out of box services provide everything your code require.  However, this is often times not the case for complex solutions and thus a developer is left with the dilemna of either a) deploying a new web service or b) using the OM via a page or a web part. 
 
Each has development technique has an associated cost - pick what you need and go with it.  Aren't we happier when the pieces of the puzzle come together and the solution just works?  :)
 
-Maurice
Maurice Prather @ 12/23/2006 12:12 PM
OM accessible on a second thread !
I had the same problem and resolved it first, by creating a separate Application Domain as suggested in the post. That worked great, but introduced a small performance hit.
Later I have discovered that just switching to the new thread is enough!!
// impersonate IIS application pool
RevertToSelf();
// process SP object model
Thread thread = new Thread(ProcessSharepointOM);
thread.Start();

Where ProcessSharepointOM is a method that accessing SP object model.  When I run ProcessSharepointOM on the same thread as Web Part, I am getting an Access Denied exception. On the second thread, as showed above, it works fine !
Victor Ch @ 9/9/2007 4:43 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
I see this method anywhere, but it nor work
 
public static void EnsureFolder(string url)
        {

            using (System.Web.Hosting.HostingEnvironment.Impersonate())
            {
//impersonated as Administrator
                AppDomainSetup objAppDomainSetup = AppDomain.CurrentDomain.SetupInformation;
                Evidence objEvidence = AppDomain.CurrentDomain.Evidence;
                AppDomain objMyAppDomain = AppDomain.CreateDomain("MyAppDomain", objEvidence, objAppDomainSetup);
// created new domain
                try
                {
                    string destinUrl = SPUtility.GetServerRelativeUrlFromPrefixedUrl(url);
                    SPWeb parentSite = SPContext.Current.Web;
                    parentSite.
                    parentSite.AllowUnsafeUpdates = true;
                    SPFolder folder = parentSite.GetFolder(destinUrl);
// here i can see that parentSite.CurrentUser stay old (not administrator) WHY!!!!
                    if (!folder.Exists)
                    {
                        SPFolder currentFolder = parentSite.RootFolder;
                        foreach (string name in folder.Url.Split('/'))
                        {
                            if (string.IsNullOrEmpty(name)) continue;
                            currentFolder = currentFolder.SubFolders.Add(name);
                        }
                    }
                    parentSite.AllowUnsafeUpdates = false;
                }
                finally
                {
                    AppDomain.Unload(objMyAppDomain);
                }
            }
        }
 
Please help if you can
 
AppPool runned with admin privilegy
wsa @ 10/2/2007 8:00 AM
Having problem looping through SPS sites
I have the error message when trying to iterate through SPS sites and sub-sites. 
Cannot complete this action.
Please try again.   at Microsoft.SharePoint.Library.a.a(Int32 A_0, UInt32& A_1, UInt32& A_2, Object& A_3)   at
Microsoft.SharePoint.Administration.SPSiteCollection.b()   at
Microsoft.SharePoint.Administration.SPSiteCollection.get_Count()   at
MyTeamSitesTest.Form1.getSubSites(DataTable ldtTempUserDetail) in C:\MyTeamSitesTest\Form1.vb:line 340
When run it on development box, message is popping up after all the process is done, while on Integration box this message is coming right at the beginning.  My web.config has trust level="Full". Also I noticed that .config file parsing with errors :<error: an exception of type: {Microsoft.SharePoint.SPException} occurred
Please, advise!
 
Anonymous User @ 10/22/2007 12:56 PM
Having problem looping through SPS sites
I have the error message when trying to iterate through SPS sites and sub-sites. 
Cannot complete this action.
Please try again.   at Microsoft.SharePoint.Library.a.a(Int32 A_0, UInt32& A_1, UInt32& A_2, Object& A_3)   at
Microsoft.SharePoint.Administration.SPSiteCollection.b()   at
Microsoft.SharePoint.Administration.SPSiteCollection.get_Count()   at
MyTeamSitesTest.Form1.getSubSites(DataTable ldtTempUserDetail) in C:\MyTeamSitesTest\Form1.vb:line 340
When run it on development box, message is popping up after all the process is done, while on Integration box this message is coming right at the beginning.  My web.config has trust level="Full". Also I noticed that .config file parsing with errors :<error: an exception of type: {Microsoft.SharePoint.SPException} occurred
Please, advise!
 
Anonymous User @ 10/22/2007 12:57 PM
re: Advanced coding technique: using AppDomains to get past OM limitations
Hi ALL,
 
I need to know a simple and direct way of getting a UserID using SharePoint + Visual Studio Web Site.
 
I have a web site which will run under SharePoint and I am sure that will need to keep the data relation between both sites/applications...
 
Any help is greatful.
 
All the best, Agni.
Agni @ 1/3/2008 12:03 PM
This worked like a charm
You are quite the man. This worked like a charm for me. I was left scratching my head why half of my impersonation code didn't work. Enumerating through user profiles is apparently a no no for Microsoft. Nice workaround.
Tim @ 4/4/2008 12:59 PM
Performance and memory usage
Hi Maurice, thanks for highlighting another way of approaching a common problem. I was wondering if your solution has better performance and memory usage as well - have you tried any profiling? Regards, Alex.
Alex Angas @ 7/23/2008 1:37 AM
re: Advanced coding technique: using AppDomains to get past OM limitations
Excellent post, creative work around for some wonky OM limitations.
Stephen Vick @ 2/20/2009 10:29 AM
re: How to Add a Custom ASP.NET Application into a Site Template
Gud Article
sudhanshu @ 3/14/2010 2:37 PM
RSS feed
Microsoft Certified Master
MVP Logo
Follow me on Twitter!
Keyword Search
 
View by category
 

Disclaimer:
The contents of this site represent thoughts and opinions of the authors , not those of anyone else - such as past, present and future employers.  This a forum of the exchange of ideas centered on SharePoint technologies.  It is not a support channel.  :)

Copyright © 2004-2010 Maurice Prather, Inc. All rights reserved.