Thursday, July 23, 2009

How to deploy a SharePoint web part to bin

As I was learning how to deploy my web parts as features in a solution package .wsp (I used WSPBuilder to create the .wsp), I realized I needed to make sure they were deployed not to the GAC (which is the default), but rather to the web application's /bin folder (under the web application in IIS). Bin is preferred over GAC because of security reasons: deploying to GAC unnecessarily exposes the web part's assembly .dll to every application on the server.

This post will show you how to deploy a web part to the /bin folder (using WSPBuilder) AND how to configure it using CAS (Code Access Security) so your web part can actually run from that folder too! I kept finding articles that discuss one or the other, but not both. Those articles that did help me quite a bit have been referenced in the last part of this post.

So, from the top, when you create a web part in Visual Studio 2008 and hit F5, the web part will deploy to the site you selected (right-click Project's Properties, click Debug, then whatever site you typed in Start browser with URL). In your Solution Explorer, you will notice a folder called /bin with another folder called /debug under that, and the compiled code will sit under there. This means its being deployed into the GAC.

At a high level, there's really two steps we must do to get our web part to run from the web application's (your SharePoint site's) /bin folder. (You can get to /bin either by going into IIS and choosing the web application you want it deployed to, or by going to c:\inetpub\wwwroot\wss\VirtualDirectories\<web application>\bin.)

The first step is actually deploying to the above-mentioned location. Once this is done, sure, the web part is right where you want it to be, but it won't run until we do step two, which involves setting the proper permissions to allow it to run from that spot. This second step involves modifying the web.config to look at a custom policy file where we will make changes to the CAS policies.


First, deploy the web part assembly to the /bin:



1. In Solution Explorer, delete the /bin folder. It will reappear in a few seconds with a Debug subfolder, but it will be empty other than that.

2. In Solution Explorer, create a folder called 80, and under that, create a folder called bin. So you will have /80/bin.

3. Right-click the project in Solution Explorer, and on the Compile tab, change Build Output Path from bin\Debug\ to 80\bin\.

4. In Solution Explorer, in AssemblyInfo.vb, you must add two lines:
a. below the other Imports statements, add the following:
Imports System.Security
(Note: this will be a Using statement if you are doing C#)

b. Below one of the other Assembly statements, add the following:
<assembly: AllowPartiallyTrustedCallers()>
(Note: This step doesn't affect the deployment of the web part, but comes into play when you try to actually use the web part on a page after its been deployed to /bin.)

5. Now, when you compile the application you will notice the assembly (the dll) appears in /80/bin instead of where it was being put before, /bin/debug. If you hit F5 to deploy it, you will notice all the other deployment files Visual Studio creates is also placed in this folder. This leads me to my next point.

Visual Studio creating deployment files automatically is no good. Sure, it will get everything up and running on the server, but maybe not the way you want. For instance, even though I have created a /12/TEMPLATE/FEATURES/AddContactFromAD folder structure in my Solution Explorer, and put in my feature.xml, elements.xml (mine is actually called AddContactFromAD.xml), and a web part definitions file AddContactFromAD.webpart, Visual Studio generates its own feature.xml (which doesn't have my pretty description I put in the real feature.xml). It also tends to write its own manifest.xml, wanting to deploy to GAC! So, let's deploy a much better way, using WSPBuilder! You can download WSPBuilder here. Once installed, it will be added to Visual Studio's Tools menu.

6. Assuming you have the above-mentioned 12 hive folder structure with a folder for each web part under FEATURES, and with that, a feature.xml, elements.xml, and .webpart file under each web part's folder, you can simply click Tools -> WSPBuilder -> Build WSP. WSP Builder will create the resulting .wsp and drop it in your project folder. Renaming it to .cab and opening it you will see each web part's feature.xml, elements.xml, .webpart, plus the assembly .dll and an autogenerated manifest.xml. Looking inside the manifest.xml will reveal two things: 1. the DeploymentTarget is WebApplication (no longer GlobalAssemblyCache as it was before), and 2. CAS policy permissions were added.

7. Ok, so let's deploy this bad boy. To do this, I add the solution, deploy the solution, then activate the features (1 feature per web part). Below is my own batch file add.bat:
stsadm -o addsolution -filename contactuswebpart.wsp

stsadm -o deploysolution -name contactuswebpart.wsp -url http://sharepoint2007:1001/ -immediate -allowCasPolicies

stsadm -o activatefeature -name contactlist -url http://sharepoint2007:1001

stsadm -o activatefeature -name emailcontact -url http://sharepoint2007:1001

stsadm -o activatefeature -name addcontactfromad -url http://sharepoint2007:1001


Also, my remove.bat comes in handy when testing:
stsadm -o deactivatefeature -name contactlist -url http://sharepoint2007:1001

stsadm -o deactivatefeature -name emailcontact -url http://sharepoint2007:1001

stsadm -o deactivatefeature -name addcontactfromad -url http://sharepoint2007:1001

stsadm -o retractsolution -name contactuswebpart.wsp -url http://sharepoint2007:1001/ -immediate

stsadm -o deletesolution -name contactuswebpart.wsp


At this point, if you check your web application's /bin, you should see the assembly .dll there. If you go to the Web Part Gallery and click the web part, or if you attempt to add the web part to a page, you will get an error. This is because we still need to do Part 2 of this grand adventure: modify the permissions.


For the second and final step, set the CAS policy permissions to enable the web part to run from /bin:



1. We are going to copy the existing wss_minimaltrust.config, rename it, then make our policy changes in that file. Go to c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG, copy wss_minimaltrust.config and rename it wss_webpartbintrust.config.


2. In our new CAS policy file, wss_webpartbintrust.config, we have three changes.

a. First, add a reference to SharePointPermission in the <SecurityClasses> section. Add the following:
<SecurityClass Name="SharePointPermission" Description="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


b. Next, add a new custom permission set. Find the NamedPermissionSet that is named SPRestricted and copy and paste this below it:
<PermissionSet class="NamedPermissionSet" version="1" Name="WebPartBinTrust">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Flags="Execution" />
<IPermission class="WebPartPermission" version="1" Connections="True" />
<IPermission class="SharePointPermission" version="1" ObjectModel="True" />
</PermissionSet>


c. Finally, add the code group. This will indicate when the permissions are to be set. It should be noted this can be done one of two ways, either using a strong name membership (assuming your assembly has been strongnamed) or a url membership, which would give you the option of having the permissions apply to all assemblies in the specified bin or the specific assembly itself.

Under <CodeGroup class="FirstMatchCodeGroup"...> you will see several <CodeGroup class="UnionCodeGroup"...> tags. Place the following code as the first one of these.
<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="WebPartBinTrust">
<IMembershipCondition class="StrongNameMembershipCondition" version="1" PublicKeyBlob="00240000048000009400000006020000002400005253413100040000010001000DAF8ED8D945CD2ABB2EE7953A6039B791A725F11B4588AC6D70B3E0648F955E9ED4C3C43CB044B8B0E8A6FF4D4FFBE9E3B9297D45F688A7264534E12414E17539305207EC961DA94DF294E7722CCD9BDBFC95A896E996F57156705D281EC39280BD604E87724556AF5807D146963F19F5B43DB69E1F22695463153A553260D2" Name="ContactUsWebPart" />
</CodeGroup>
(Note: The PublicKeyBlob must be the public key blob of your assembly. You may see the PublicKeyBlob in another tag referencing your assembly, in which case, just copy and paste it. Otherwise, you will need to run the command: secutil -hex -s contactuswebpart.dll > publickeyblob.txt, then copy and paste it from the output file publickeyblob.txt.)


3. Almost there... Just two more changes, but this time in your site's web.config. Go to c:\inetpub\wwwroot\wss\VirtualDirectories\<your site>\web.config.

a. In the SecurityPolicy tag, make sure there is an entry for a custom TrustLevel that points to the policy file we just edited:
<trustLevel name="WSS_Custom" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_webpartbintrust.config" />

b. Scroll down a bit and make sure the trust level is set like this:
<trust level="WSS_Custom" originUrl="" />


That's it! Restart IIS and have fun!


Some good pages that helped me figure this all out:

WSPBuilder - Walkthrough of the Visual Studio Add-in

MOSS 2007 and Code Access Security

Gaurav Taneja article on bin and GAC deployment

Deploy dlls to the webApplications bin rather than GAC

Code Access Security in SharePoint 2007 for Administrators

11 comments:

  1. Nice post, but how can you deploy this solution? Especially "part 2", modify the permissions. If I'm reading it correctly you need to add a custom wss_webpartbintrust.config and refer to tath in teh web.config. Is it possible to deploy that custom wss_webpartbintrust.config and modify the web.config file. If not the only option left IMHO is editing these file on each front-end server.

    ReplyDelete
  2. Hey Ramon,
    I am not sure I follow. I made a custom policy file in the 12 hive, then changed the web application's web.config to look at that new custom policy file. Are you wanting to know how I would deploy on separate servers or multiple sites on one server? On the one server, if I have 3 web applications that need these permissions for web parts to run from their /bin folders, I would just have to update their web.configs to look at the new custom policy file. They all see the same 12 hive, so the new policy file would be easily referenced by any web application on the server.

    ReplyDelete
  3. D-J
    Nice post. When I run this step
    stsadm -o deploysolution -name contactuswebpart.wsp -url http://sharepoint2007:1001/ -immediate -allowCasPolicies
    for my solution I get "The solution "web par name.wsp" needs to install assemblies in the Global Assembly Cache GAC. If you fully trust this solution.

    I did have it installed in the GAC before but I ran the remove.bat steps and I don't see the web part in the GAC WINDOWS/assemby folder. Also I have a vb.net Class as part of my web part solution and it gets put in my wsp.

    So I am not sure why my add.bat file thinks I need to deploy to the GAC and not to the BIN.

    Another point is, I deploy successfuly to the BIN on my development machine using WSPBuilder. This deployment I have trouble with is from DEV to PROD. I copy over the WSP and run the add.bat.

    Any thoughts would be welcomed.

    Guy

    ReplyDelete
  4. How do you give permission to the event log? I completed your example for the core of my app but I would like to include eventlog access. Also, how can I keep my dll in the bin but load an ascx control using LoadControl? I keep receiving a file not found error exception.

    ReplyDelete
  5. On the first part, can you be more specific? I am not sure I follow what you mean by wanting to include eventlog access. On the second part, I haven't done that yet, so I don't know! I will try to look into this and let you know. Any more information you can give on that example might help me as well, but I believe I understand what you are asking.

    ReplyDelete
  6. I am trying to send exceptions to hte event log

    ie. EventLog.SourceExists("MyLogEntry"))
    EventLog.WriteEntry("Log something")

    this attempt always blows up and throws Securtiy Exception. I have even attempted to place this class inside its own dll and place it in the GAC and include it as a reference in my project and i still get Security Exception. If I do not include Event loggging I get your example working just fine

    ReplyDelete
  7. D-J

    I am coming up to another problem.

    When I install my webpart with the above settings you laid; I get a SecurityException
    when i try to perform System.IO.StreamREader.ReadToEnd() on a SPFile.OpenBinaryStream().

    In short,
    When I try to get the contents of a SPFile as a string I get an exception.

    Any Ideas?

    ReplyDelete
  8. Also, I thought I would update a question I had; I eliminated using usercontrols and loading them dynamically. I instead inject all the html from inside the webpart class.

    This was the end result of not finding an answer and everyone stating that ultimately you have to give your dll full trust.

    cheers,

    Michael

    ReplyDelete
  9. Michael,
    Is there any info you can give me on the user account running the code? I think that might be the best place to look considering you have a couple of issues throwing security exceptions.

    ReplyDelete
  10. It is a development virtual machine and i have been running these test as administrator

    ReplyDelete
  11. I have been visiting various blogs for my term papers writing research. I have found your blog to be quite useful. Keep updating your blog with valuable information... Regards

    ReplyDelete