This post was migrated from an older system. Some links may point to content that no longer exists. Comments were not migrated.
Update 2/20/2007
Updated path to the perf mon image - and included it in the post now the v3 allows images in rich text fields :).
One of my
first posts discussed the interface loader technique
which allowed you to move beyond impersonation limitations of the SharePoint OM.
The original post did not discuss the details of how a RevertToSelf or
LogonUser/Impersonate could/should be used. Instead, I brushed over the
topic and left it for the reader to determine which methodology was best for
their application.
Over the past couple of months, I've received several email discussing a
variety of issue surrounding impersonation and the SharePoint OM. In each
case, I would always offer two alternatives: try using AppDomains or use a COM+
solution. Most folks decided to use the AppDomain technique... and
invariably some of them would come back with failures. Good or bad, my
thought process was invariably "check your impersonation code, you must not be doing
it right... the article on the loader technique is accurate and works".
Well, if enough people run across problems, then it's time to double check
the source to make
sure all the i's have been dotted and the t's crossed. The truth is I was
somewhat right, but I had forgotten a small trick that I had used in my own code.
Without a 1-line adjustment/fix to the impersonation code, you would run into
the infamous "Cannot complete this action" error message even though everything else appeared to be
working correctly.
On to a Q&A style to get things working...
What's the one line fix?
After you've called completed your impersonation magic (i.e.
RevertToSelf, LogonUser, Impersonate), call the Impersonate
method one more time in the context of the newly established identity. Although this seems a bit counterintuitive, calling
impersonate one more time resets the state in the Context in way that ensures
the impersonation "takes hold". If you don't do this, you will eventually
run into the "Cannot complete this action" error message.
Add the
following line to your impersonation library immediately after you've done the
heavy lifting of calling RevertToSelf, LogonUser, or WindowsIdentity.Impersonate
(IntPtr):
// If the following
call is not made, the Context is somewhat out of whack...
WindowsIdentity.GetCurrent().Impersonate();
Back to basics... isn't impersonation not supported?
Yes. In a simple, inline manner as described by
KB article 892866,
impersonation is not supported. You can't do the following and expect it
to work all the time.
// Running as normal
user...
// Now switch to privileged user...
MethodWhichImpersonates ();
// Execute a privileged task of some type. For example...
CreateSiteForMyUnderprivilegedUser ();
Doesn't using AppDomains or COM+ contradict the KB
article?
Not really. The whole purpose of using those techniques is to remove
any knowledge of the http context. The basis for the KB article is that
the SharePoint OM may perform security checks relative to the original
request/context identity and not the newly established one.
If the http context space is eliminated, the SharePoint OM
operates as though it's a console application. Yes, the starting point may have well been
an http request, but the code running in secondary AppDomains or as a COM+
application has no knowledge of the context. This is where the power of
those techniques shine through. You get all the benefits of a console app.
How about if I use the 1-line fix to my inline
code... will that make the KB article go away?
No. You're still
under the bounds of the KB article. There is no guarantee everything will
work as desired when you use impersonation and remain within the http context
space.
Using the AppDomain technique, what type of
parameters should I pass to methods?
Simple, serializable types. Use simple types as method
parameters (i.e. string, int, custom classes). Whatever you do, don't try
to pass an Assembly or any of the context types. If you do so, you will
force the operating domains to load (and subsequently lock) assemblies which
were once isolated.
Should I specify shadow copying and other advanced
properties for my AppDomain?
Yes, it's probably a good idea to walk through all of
the
AppDomainSetup methods and properties. The following is how I
generally setup my AppDomains to ensure I don't lock the main assemblies by
caching them to a unique shadow copy folder:
AppDomainSetup appDomainSetup
= new AppDomainSetup();
appDomainSetup.ApplicationBase = applicationBase;
// this is the value of this.Page.MapPath("/")
appDomainSetup.ApplicationName =
AppDomain.CurrentDomain.SetupInformation.ApplicationName + "_MyAppName";
appDomainSetup.CachePath
= AppDomain.CurrentDomain.SetupInformation.CachePath;
appDomainSetup.ConfigurationFile = "web.config";
appDomainSetup.DisallowCodeDownload = true;
appDomainSetup.PrivateBinPath = "bin";
appDomainSetup.PrivateBinPathProbe = "true";
appDomainSetup.ShadowCopyFiles = "true";
this.appDomain = AppDomain.CreateDomain (domainName,
AppDomain.CurrentDomain.Evidence,
appDomainSetup);
Please review the settings to ensure they generate the behavior you desire -
there's a chance you don't want to deal with shadow copied files.
Can the AppDomain technique be used in a partially
trusted environment?
Yes, your web part code (or whever you implement this technique) does
not require Full trust. This is a good homework assignment - can you tell
me what set of permissions are needed to make the technique work? :-)
How does the AppDomain technique look like with a
debugger attached?
If you attach a debugger to the process, the output should look like the
following color-coded section. I've trimmed a good bit of the output to
reduce the overall size of the post, but the general outline is still there.
The gray highlighted section is the normal information regarding your virtual
server's primary AppDomain. VS will list all the assemblies loaded in the active w3wp
process. When you commit an action which fires up a new AppDomain, the
yellow highlighted section will represent all the actions taken by your new
AppDomain. Note that "DomainX" is created, then the necessary assemblies
are loaded into that AppDomain.
In a perfectly working and synchronous world, the two section (gray
and yellow) should be completely separate and not intermixed. This is easy to witness on a developer's machine because you can isolate server usage.
'DefaultDomain': Loaded 'c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll',
No symbols loaded.
Auto-attach to process '[5772] w3wp.exe' on machine 'WESTGATE' succeeded.
'DefaultDomain': Loaded '\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll',
No symbols loaded.
'DefaultDomain': Loaded '\gac\system\1.0.5000.0__b77a5c561934e089\system.dll',
No symbols loaded.
'DefaultDomain': Loaded '\gac\system.enterpriseservices\1.0.5000.0__b03f5f7f11d50a3a\system.enterpriseservices.dll',
No symbols loaded.
'DefaultDomain': Loaded '\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll',
No symbols loaded.
'/LM/W3SVC/1285842265/Root-3-127716846597562722': Loaded 'p3odzxx_', No
symbols loaded.
'/LM/W3SVC/1285842265/Root-3-127716846597562722': Loaded '\gac\microsoft.sharepoint.intl\11.0.0.0__71e9bce111e9429c\microsoft.sharepoint.intl.dll',
No symbols loaded.
'/LM/W3SVC/1285842265/Root-3-127716846597562722': Loaded
'RegexAssembly3132_0', No symbols loaded.
'/LM/W3SVC/1285842265/Root-3-127716846597562722': Loaded
'RegexAssembly3132_0.dll', No symbols loaded.
'Domain4': Loaded 'c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll',
No symbols loaded.
'demo': Loaded '\microsoft.net\framework\v1.1.4322\temporary asp.net
files\root\13e9a3d7\ec3137a3_testApp\assembly\dl2\bf0ad338\ee227dea_90bdc501\bluedoglimited.webparts.scratch.dll',
Symbols loaded.
'demo': Loaded '\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll',
No symbols loaded.
'demo': Loaded '\gac\microsoft.sharepoint\11.0.0.0__71e9bce111e9429c\microsoft.sharepoint.dll',
No symbols loaded.
'demo': Loaded '\gac\system.xml\1.0.5000.0__b77a5c561934e089\system.xml.dll',
No symbols loaded.
'demo': Loaded '\gac\system\1.0.5000.0__b77a5c561934e089\system.dll', No
symbols loaded.
'demo': Loaded '\microsoft.net\framework\v1.1.4322\temporary asp.net
files\root\13e9a3d7\ec3137a3_testApp\assembly\dl2\3926c5e3\27119fd6_8dbdc501\bluedoglimited.webparts.syndication.dll',
No symbols loaded.
'demo': Loaded 'yzqk0nww', No symbols loaded.
'demo': Loaded 'vqusmshf', No symbols loaded.
The program '[5772] w3wp.exe: demo' has exited with code 0 (0x0).
Most importantly, though, note the very last line - "w3wp.exe: demo" has
exited. If you don't see your custom AppDomain
exiting (in this example, my AppDomain was called "demo"), you've got a memory leak ... so be sure you always properly dispose of
the your custom AppDomain. Technically, garbage collection will likely take care of reclaiming your memory, but don't wait - just do it yourself.
What does all of this look like in perfmon?
The following image shows what occurs when a postback is executed that fires off
method which creates a new AppDomain and creates a site for an anonymous user.
Note how the AppDomain count goes up by 1, process executes, then the count goes
down by 1. Correspondingly, available memory decreases and then returns to
its original level.
Ok... how about a demo?
The following page contains a web part that allows an anonymous user to create a
new site, get some data about a user, and get a list of sites. It took me
less than 10 minutes to create this web part. In fact, I spent more time
touching up the UI than I did coding up the interface loader. :)
AppDomain - Interface Loader - Demo Page
Swing by check it out... then go crank your own custom apps that do all sorts of neat things!
-Maurice