Sunday, November 2, 2008

ASP.NET Application Code Into SharePoint: The User Control/Page Layout Technique

Getting Into The Farm

A lot of developers first introduction to SharePoint is being asked to take an ASP.NET application that they're familiar with, and integrate/port it into a WSS/MOSS instance. These same developers are often thoroughly disappointed when it comes to the application developer experience for SharePoint. The prototypical ASP.NET developer is likely to miss a lot of the everyday ASP.NET developer comforts they've become used to writing typical ASP.NET applications. Common gripes are often along the lines of:

  • No designer surface for building user interfaces with code-behinds.
  • Lack of tools making it easy to deploy/maintain ASP.NET applications across the farm.
  • Reduced trust levels for the SharePoint web applications. If you read the last post, you'll remember that unlike ASP.NET web applications (which run in full trust), SharePoint web applications start out in WSS_Minimal which has far less CAS privileges.

Other Techniques

By no means is this the first post speaking to different ways of getting ASP.NET applications into SharePoint. In fact my favorite post by Chris Johnson compares and contrasts 4 different techniques not listed here. They include:

  • _layouts directory deployments.
  • Building custom Web Parts.
  • User Controls with the Smart Part web part.
  • Embedding ASP.NET pages directly into the content database.

This Technique

This techniques is a little different. It involves 6 steps.

  1. Create a User Control that represents some functionality you'd like to use in SharePoint.
  2. Deploy the user controls to the SharePoint web application folder along with assemblies.
  3. Add Safe Control directive for you assemblies user controls to the web.config.
  4. Create a Page Layout to dictate the layout/structure of a page instance.
  5. Embed your user control into the page layout.
  6. Create an instance page based off the page layout.

It's important to note that because this technique requires Page Layouts, it's only available to developers that are working with MOSS and have turned on the Office SharePoint Publishing Infrastructure feature for their given site collection, and the Office SharePoint Publishing feature for their given site. These two features are under the Site Settings->Site Collection Features and Site Settings->Site Features respectively. Or you can simply create a publishing site which starts with these two features turned on, your choice. Onward.

Create and Deploy the User Control

  1. Open up Visual Studio 2005/2008 and create a Web Application Project. This is the kind of project that compiles all of it's contents into a single assembly (.dll). If you're running VS 2005 and you haven't already downloaded this project, you can get it here.Create an ASP.NET Web Application Project
  2. Add a new user control (HelloUser.ascx) with the following .ascx content.


    <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="HelloUser.ascx.cs" Inherits="HelloWorld.HelloUser" %>
    Enter Your Name: <asp:TextBox ID="userName" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="validator" runat="server" ErrorMessage="Enter your name." ControlToValidate="userName" />
    <br />
    <asp:Button ID="Submit" Text="Submit" runat="server" OnClick="Submit_Click"/>
    <br />
    <asp:Label ID="helloUser" runat="server"></asp:Label>

  3. Replace the code in the HelloUser.ascx.cs with the code below:

    using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;

    namespace HelloWorld
    {
    public partial class HelloUser : System.Web.UI.UserControl
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    protected void Submit_Click(object sender, EventArgs e)
    {
    helloUser.Text = string.Format("Hello, {0}!", userName.Text);
    }
    }
    }
  4. Build and test the user control to make sure it's working.
  5. Deploy the projects assemblies to C:\Inetpub\wwwroot\wss\VirtualDirectories\[ApplicationRoot]\Bin. Make a directory called PageLayoutUserControls in the [ApplicationRoot] and deploy the .ascx's to C:\Inetpub\wwwroot\wss\VirtualDirectories\[ApplicationRoot\PageLayoutUserControls.
  6. Add the following lines to the web.config to safe control the assemblies and the .ascx's.

    <SafeControl Assembly="HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Namespace="HelloWorld" TypeName="*" Safe="True" AllowRemoteDesigner="True"/>
    <SafeControl Src="~/PageLayoutUserControls/*" IncludeSubFolders="True" Safe="True" AllowRemoteDesigner="True"/>

Create a Page Layout

  1. Open up the SharePoint designer. Go File->New->SharePoint Content.
  2. Choose SharePoint Publishing and Page Layout. Base the page layout off of Page found in the Publishing Content Types group. Give it a URL Name of "HelloWorld.aspx" and a Title of "Hello World". Create a Page Layout
  3. Replace the page with the following markup.

    <%@ Page language="C#" Inherits="Microsoft.SharePoint.Publishing.PublishingLayoutPage,Microsoft.SharePoint.Publishing,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
    <%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    <%@ Register TagPrefix="ascx" TagName="HelloUser" Src="~/PageLayoutUserControls/HelloUser.ascx" %>
    <asp:Content ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
    <SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
    </asp:Content>
    <asp:Content ContentPlaceholderID="PlaceHolderMain" runat="server">
    <ascx:HelloUser runat="server"></ascx:HelloUser>
    </asp:Content>
  4. Create an instance of the page. Site Actions->Create Page->Pick the Page "(Page)Hello World" and give it a name->Create. You're done!2008.11.02 17.12.59

Costs and Benefits

I like this technique because it has a lot of the benefits from traditional ASP.NET and a lot of flexibility.
  1. Developers can develop their user controls w/out SharePoint (unless they need to call against the SharePoint object model).
  2. You get a design surface to develop these codes.
  3. You can deploy/organize your user controls to any application directory want (unlike the Smart Part).
  4. You can make use of the SharePoint object model and run in the SPContext of your given site.
  5. It's very easy to create hybrid page layouts that include both your user controls, a couple of web part zones and come SharePoint publishing controls. This lets you keep a lot of the SharePoint functionality that works well for your project, and still let you customize pages using traditional ASP.NET development tools.
  6. You can get rid of web parts should you be interested in cleaner markup. If you have some exotic layouts and don't want to fight with all the <table> markup this could be quite helpful.

There's still some overhead with this approach. This lets you leverage a lot of the stock SharePoint functionality but there's no such thing as a free lunch.

  1. You'll potentially need to create a Page Layout and page instance for each page you want in your application. This can make deployment and change management more tricky. You'll need a build manager that is savvy with tools to manage the content database (stsadm, Content Deployment Wizard)
  2. You'll need a MOSS license and access to SharePoint Designer. If you don't have these the Smart Part is your next best bet for this type of UserControl/deployment type development.
  3. You'll need to sync web.config and user controls across (potentially) many web front ends.

If anything it's another way to skin a cat. I have to say it's quickly becoming my favorite SharePoint development technique.

My Best,
Tyler

10 comments:

Andrew said...

Can the user control contain binding expressions doing things this way?

Tyler Holmes said...

Sure can.

david.simons said...

Thanks for the instructions! I've had some issues in "Create A Page Layout", step 3:
After I "Replace the page with the following markup." as you instruct, in Sharepoint Designer HelloWorld.aspx in the design window I see an error message displaying the following:

Error Rendering Control - Unnamed1The file '~/PageLayoutUserControls/HelloUser.ascx' was not found.

I confirmed I added the HelloUser into the PageLayoutUserControls\ directory under "C:\Inetpub\wwwroot\wss\VirtualDirectories\80\PageLayoutUserControls". I also could not follow the final step you list: "Create an instance of the page. Site Actions->Create Page->Pick the Page "(Page)Hello World" and give it a name->Create. You're done!"

If I go to Site Actions -> Create Page I don't see any "Hello World" under any of the Page options. I only see the following options:
Basic Page
Web Part Page
Sites and Workspaces

None of the info in any of those links links to the Hello World.

The directions so far look great, any help to resolve this problem would be greatly appreciated!

Tyler Holmes said...

Hey David,

Don't worry if SharePoint Designer doesn't find the file right away, the program is a little finicky when it comes to recognizing resources. I'd just run your page and see if the .NET runtime can find your control. This brings us to your 2nd problem.

I'd recommend practicing creating a page layout in of itself since it's a useful skill. Once you have that down circle back and try to embed a user control in it.

You can create the page layout and instances of the page layout first. After you add the user control all instances of that page layout will also have your control.

Instructions can be found here:

http://office.microsoft.com/en-us/sharepointdesigner/HA101741281033.aspx

Best,
Tyler

david.simons said...

Tyler,
Thanks for your help! It has appeared to work quite well!

For anyone else struggling with this issue, I'd replace Tyler's step of
"4. Create an instance of the page. Site Actions->Create Page->Pick the Page "(Page)Hello World" and give it a name->Create. You're done!"
With the steps under "Create a publishing page from your new page layout" from the link he mentioned above.
http://office.microsoft.com/en-us/sharepointdesigner/HA101741281033.aspx


Thanks again!

david.simons said...

Tyler,
One more question: What Sharepoint permissions are required to be able to view this application code? I've run into an issue where I can view deployed application code as a Site Collection Administrator, but cannot view it as a contributor or a user with limit access. Here's a list of the possible permissions: http://office.microsoft.com/en-us/sharepointtechnology/HA101001491033.aspx

If you could point me in the right direction for where to go to setup the permissions properly I'd appreciate it. Am I on the right track thinking to either:
1. Change the permissions the control in _catalogs/masterpages?
2. Change the permissions for the "[sharepoint homepage]/Pages" directory?

Tyler Holmes said...

Hey David,

For a user to access the page they'll need access to all server side components of it. This means they'll need read access to the MasterPage gallery for master pages and page layouts, access to the page itself (which is probably inside some Pages library), and access to the user control itself (which may be on disk).

I'd slowly document and elevate user privileges in these areas and see how far you get. Remember that out of the box a publishing site typically has special security settings on the Master Page gallery (the inheritance is often broken at the list level.).

I'd start looking for missing permissions there.

Best,
Tyler

david.simons said...

That was it! I had to change my permissions to "contribute" for this to work properly, the "read" permissions were not enough to enable me to view the web parts.

Thanks
David

Ernesto said...

Hi Tyler! Thaks, ur code was great and usefull. Now i wanna to tell you about a issue working whit the dll's:

When you put a dll into the \Bin directory of the app, the respective UserControl works fine whit simple actions and routines, like value asignations (TextBox1.Text = "Hi"), lops, varible asignations, calc...

But there is a trouble when you try to call System routines, like user query, file writting, sql conecctions, etc. And that only happend when the UserControl is published and runing from SharePoint, not in Visual Studio or Visual Web Developer Env, in these software goes fine!

However, back in SharePoint, the user control produces Security Exceptions (And only u see that if the app web.config allows thtat!, in other case u nerver knows what happen wrong!!)

Well that happen becouse the dll file must be registered in the GAC to work fine whit SharePoint. once the dlls have registered in the GAC will works fine whit MOSS.

Search info about the 'sn' and 'gacutil' commands, are part of .NET Sdk for Windows.

Tyler Holmes said...

Hey Ernesto,

Thanks for the feedback. The issue you're running into isn't really because the assembly needs to be strong named and added to the GAC (although that will fix it).

The issue you're running into is that you're trying to make calls from code which require Code Access Security levels higher than sock SharePoint allows.

Your control works out of Visual Studio and a normal website because those are running your code in FullTrust. In SharePoint you're running under WSS_Minimal and so unless your code is either:

1) GAC'd (which will allow your code to run in full trust).
2) Called out by a custom CAS policy to run in full trust (a preferred approach).
3) Or you've set all of SharePoint to run in FullTrust (dangerous and against best practices).

You'll run into CAS exceptions when you try to access the disk make web calls etc...

A good breakdown of the difference between FullTrust and WSS_Minimal and CAS can be found here:
http://blog.tylerholmes.com/2008/10/don-set-your-sharepoint-app-to-full.html

My Best,
Tyler