DevExpertise

Practical tips and tricks for all things .NET, SharePoint, Silverlight, InfoPath, and general application development.

SharePoint Tip/Trick: Specifying a Relative Portal Site Connection Link

Posted by DevExpert on 5th May 2009

There are a variety of reasons why you’d want to create multiple site collections – to avoid recommended capacity limits, to provide a logical site structure, etc.  One of the drawbacks with creating multiple site collections is the lack of out-of-the-box functionality to access and share content across site collections.  While portal site connections don’t do anything to access or pull content, it does allow you to specify a “connection” to another site collection, which appears in the global breadcrumb.  This makes logical navigation easier.  Consider the following example:  a company needs to have separate site collections for each department (HR, accounting, IT, etc.), but there is also a “top-level”, shared area of the environment, which will be a separate site collection.  When you’re in each of the departmental site collections, it would make sense to have a link back to the top-level site collection, to insinuate a logical hierarchy. Simply put, a portal site connection gives you a breadcrumb link to another site collection.

Let’s take a look at the above example.  I created a root site collection (http://intranet.devexpertise.com), and also an accounting site collection (http://intranet.devexpertise.com/sites/accounting). When you’re on the accounting site collection, there’s no visual indication or link back to the “root” site collection:

image

By simply adding a portal site connection, we can provide a visual indication of the logical hierarchy and link back to the root site collection.  To add a portal site connection, navigate to Site Settings > Site Collection Administration > Portal site connection, and specify a URL and a friendly name for the link:

image

Now, the breadcrumb will show a link to the root site collection:

image

Great! …as long as you’re always going to access your sites from a single URL.  What if you have multiple URLs set up, such as the situation if you allow external access to your environment and have a separate external URL (http://extranet.devexpertise.com).  The logical thing would be to just specify a relative link for the portal web address, however SharePoint won’t let you, and pops up a nice “Please enter a URL for the portal site” error:

image

Why is this really a problem?  Well, let’s say you left it as http://intranet.devexpertise.com, and accessed it from http://extranet.devexpertise.com.  The portal site connection link would still point to the intranet!

image

Lovely, huh? Fortunately, you are able to easily set this to a relative link using a few lines of code:

using (SPSite siteCollection = new SPSite("http://intranet.devexpertise.com/sites/accounting"))
{
    siteCollection.PortalUrl = "/";
}


That’s it!  Now, no matter what URL you access the accounting site from, the portal site connection link will jump you back to the correct root site collection:

image

Tags: , ,
Posted in .NET, Object Model, SharePoint | 7 Comments »

Integrating a Custom ASP.NET Application into SharePoint (Part 4)

Posted by DevExpert on 1st April 2009

At the end of my last post in the Integrating a Custom Application Into SharePoint series, I had completely forgotten that I promised to describe how to package everything up into features and solution packages, and how to deploy that to SharePoint in a simple and streamlined fashion.  I received a couple comments asking me to make good on my promise, so here it is! Ask and ye shall receive :)

In my last three posts here, here, and here, I began describing how to integrate a custom ASP.NET application into SharePoint.  The first post focused on the essentials, and detailed how to get your application into the SharePoint LAYOUTS folder structure, specifically where to place your files and how to inherit SharePoint’s look and feel by using its master page.  The second post focused on configuring permissions for your application and also demonstrated a few handy built-in controls that you can leverage to give your application that true SharePoint-like integrated look and feel.  The third post focused on navigation and how to integrate that with the out-of-the-box navigation that is provided with SharePoint.

The concept of SharePoint features and solutions packages is not a secret, and there is no shortage of articles, blogs, and how-to’s that document them.  Instead of reinventing the wheel and writing something up that describes what each of these are, here are a few excerpts I stole…err…borrowed from other sources:


Features

From MSDN:

“Features allow you to test, deploy, and activate custom functionality inside Office SharePoint Server 2007 and provide a way to make functionality available across your server farm. This functionality can be custom workflows, content types, modifications to the user interface, or new templates for lists and document libraries.”

From the SharePoint Developer Blog:

“A SharePoint feature is a module of functionality that can be enabled at specific scopes within a SharePoint farm, namely the farm level, web application level, site collection level, and site level.  SharePoint itself uses features for nearly everything it provides out of the box – the standard list definitions, the built-in site columns & content types, built-in web parts, and they are even used to define what is displayed on the Site Settings page and the Site Actions menu.  Each of the features, both built-in and custom ones, live within the “12” hive at 12\TEMPLATE\FEATURES, and each is contained within its own unique folder, whose name reflects the feature’s purpose.  Within this folder there is one and only one required file, whose name must be feature.xml.  This file defines the basic characteristics of the feature including its ID, name, description, activation scope, and visibility.  Additionally this XML file can define additional information that is specific to the feature itself, such as a receiving assembly, and potentially one or more element manifests.  Each element manifest that is defined is represented by an additional XML file that is also contained within the feature’s folder, and within these files is where the uniqueness of the feature comes out, as they can be used to define the set of files that the feature is going to inject into a given site, or the custom actions the feature will add to the Site Actions menu, or the site columns and/or content types that the feature will add to its targeted site, just to name a few.  Finally, in addition to the element manifest XML files, the feature’s folder can contain any number of other files and folders that are needed for the feature itself, based upon its intended purposes.”


Solution Packages

From MSDN:

“Microsoft Windows SharePoint Services 3.0 introduces a deployment mechanism named "solution packages." A solution package is a CAB file with a .wsp file-name extension that contains all the files that must be deployed on the front-end Web server and a set of XML-based installation instructions. Windows SharePoint Services provides a rich infrastructure that simplifies deployment of solution packages in a Web farm environment.”

From Bill Baer:

“Solution packages are designed to provide the ability to develop and deploy reusable  site and feature definitions, web part files, templates, assemblies, and code access security policies across one or more server farms.  A solution package is a cabinet file that can contain, site and feature definitions, web part files, templates, assemblies, and code access security policies.  A solution package contains a web manifest that that defines the list of features, site definitions, resource files, Web Part files, and assemblies to process when the solution is deployed.  The directory structure within the cabinet file dictates the resulting structure on the web front-end computer when the solution is deployed.”

 
In the simplest terms, a feature is used to deploy something to SharePoint.  One or more features can then be packaged up into a solution package, and that solution package can then be deployed to SharePoint.  Got it?

So, what can you use a SharePoint features to deploy?  Pretty much any custom development artifact.  Here are just a few of the items that come to mind:

  • Web Parts
  • Event Handlers
  • User Controls
  • Visual Studio-authored Workflows
  • Site Columns
  • Content Types
  • Site Definitions
  • List Templates
  • CSS style sheets
  • JavaScript files
  • LAYOUTS application pages
  • THEMES (I describe how here)
  • Custom Actions
  • Delegate Controls
  • Master Pages/Page Layouts/Files/Documents/List Items
  • Site Definitions
  • Web Services
  • WCF Services
  • HTTP Modules
  • HTTP Handlers
  • Executing custom code when the feature is activated/deactivated/installed/uninstalled
  • Staple other features onto existing site definitions

If you’ve developed any of these items, then you should know that many of these artifacts are deployed to somewhere in SharePoint’s “12 Hive” folder structure.  Certain types of files belong in certain places in the 12 Hive.  For example, images belong in the ~12\TEMPLATE\IMAGES folder, application pages belong in teh ~12\TEMPLATE\LAYOUTS folder, etc.  If you’re building a custom web part, you will be placing your web part assembly either in the GAC or in SharePoint’s bin directory.  If you’re deploying style sheets, then you need to place that in a different location.  What am I getting at here?  9 times out of 10 when we’re deploying custom development artifacts to SharePoint, we will be placing many files in multiple locations.  This is very troublesome and error-prone, because 1.) it’s very easy to miss one, or put a file in the wrong directory, 2.) it’s a tedious manual process, and 3.) it’s not the right way to do it.

To illustrate what I’m talking about, let’s examine the Visual Studio project I put together for this blog series.  If you take a look at the following screenshot of how I have my solution set up, the important thing to notice is my folder structure, which mimics that of the 12 Hive.  Inside these standard SharePoint folders, I place custom folders specific to the project I’m working on.  There’s a very good reason for this.  Let’s say you’re deploying a custom image to the IMAGES directory.  There are over 2,000 images in that folder, and adding yours into that folder makes it hard to find and is much less maintainable.  A better practice is to add your own sub-folder, then add your images to that which will isolate your files that belong to their respective solution.

image

As you can see, I’m deploying a lot of different files. I have some images, a script file, a style sheet, etc.  I also have a feature that will be used to provision Custom Actions, as I describe here.  Remember, a feature is just an XML file that describes what the feature does and what should be included with it.  My feature for the above solution is pretty simple, and only includes the CustomActions.xml manifest file reference:

<?xml version="1.0" encoding="utf-8"?>
<Feature
  Id="5856617E-BED2-4705-B030-735F7483225E"
  Title="DevExpertise Layouts Application"
  Description="Contains the necessary components for the DevExpertise custom LAYOUTS application."
  Version="1.0.0.0"
  Scope="Web"
  Hidden="false"
  ImageUrl="DevExpertise\devexpertiseLogo.png"
  ReceiverAssembly="DevExpertise.LayoutsApp, Version=1.0.0.0, culture=neutral, PublicKeyToken=d39eedb6cff9b1c8"
  ReceiverClass="DevExpertise.LayoutsApp.FeatureReceiver"
  xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="CustomActions.xml" />
  </ElementManifests>
</Feature>


Now it’s time to deploy everything.  While I could just copy all of these files to the file system manually, then install the Custom Actions feature on the SharePoint site, I instead bundle everything up in a single deployable solution package. Enter WSPBuilder.

Without getting into the nasty details of actually how to create a solution package from scratch, I will say that it’s a nightmare.  A solution package is a .WSP file, which is really a .CAB file.  To create .CAB files, you use MakeCab.exe, which involves creating your own .DDF file and XML manifest.  It’s ugly, trust me.  WSPBuilder eliminates the need to manually build these files, and offers a simple command-line interface to build the package, which traverses a 12 Hive folder structure and creates the solution automatically.

Most anything I do frequently, I have a script for.  Creating solution packages is no exception.  First, in my VS solution folder on the file system, I created a Solution folder that my script will generate the package in.  In addition, I included a 12 folder and a GAC folder.  The 12 folder will obviously contain the folder structure for the 12 Hive, and the GAC folder will contain all assemblies that will need to be deployed to the GAC.  WSPBuilder automatically builds this into the solution package for us.  To manage my solution creation and deployment, I use 2 scripts: wsp.bat to build it, and install.bat to deploy it.

My wsp.bat is as follows (NOTE, the last command that builds the package should all be on one line.  I had to break it up to fit into this post):

@SET WSPPBUILDER="C:\Tools\WspBuilder\WspBuilder.exe"
@SET SOLUTIONNAME=DevExpertise.LayoutsApp.wsp
@SET URL=http://server
@SET BUILD=Debug

ECHO Copying Files to Temporary Solution Directory
  xcopy bin\DevExpertise.LayoutsApp.dll Solution\GAC\ /y /r

ECHO Building Solution Package
  %WSPPBUILDER% -CreateWSPFileList wspfiles.txt -outputpath solution
    -12path Solution\12 -gacpath Solution\GAC -Excludepaths bin
    -createfolder true -wspname %SOLUTIONNAME%


This script only generates the .WSP file; I still need a script to install it.  My install.bat file is as follows:

@SET STSADM="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM"
@SET SOLUTIONNAME=DevExpertise.LayoutsApp.wsp
@SET URL=http://server/

ECHO Removing Existing Solution
  %STSADM% -o retractsolution -name %SOLUTIONNAME% -url %URL% -immediate
  %STSADM% -o execadmsvcjobs
  %STSADM% -o deletesolution -name %SOLUTIONNAME%

ECHO Installing Solution
  %STSADM% -o addsolution -filename Solution\%SOLUTIONNAME%
  %STSADM% -o deploysolution -name %SOLUTIONNAME% -url %URL%  -immediate -allowGacDeployment -force
  %STSADM% -o execadmsvcjobs 

ECHO Recycling App Pool
  iisapp /a "Sharepoint - 80" /r

 
Now, you could just as easily fold both of these scripts into 1, but I like to be able to generate the solution package without actually deploying it sometimes.  Whatever floats your boat I suppose.

Once your feature is installed, you will see it appear in Central Administration > Operations > Solution Management:

image

Remember, this particular solution package is essentially responsible for 3 things: deploying files to the file system, adding an assembly to the GAC, and creating a web-scoped feature.  Once the solution is deployed, these will automatically be done for you:

Some of the files that were deployed to the LAYOUTS folder:

image

The assembly installed in the GAC:

image

And finally the web-scoped feature:

image


Good stuff, huh?  Hopefully you can see how easy it is to create features and solution packages, and understand why it is Microsoft’s recommended best practice for deploying custom SharePoint artifacts.  I know it seems like a lot of work, but once you do this once or twice and realize the benefits of what features and solution packages provide, I guarantee you’ll never look back.

 

Here are a few tools that may help:

Tags: , , ,
Posted in .NET, SharePoint, Visual Studio | 8 Comments »

Integrating a Custom ASP.NET Application into SharePoint (Part 3)

Posted by DevExpert on 4th March 2009

In my last two posts here and here I began describing how to integrate a custom ASP.NET application into SharePoint.  The first post focused on the essentials, and detailed how to get your application into the SharePoint LAYOUTS folder structure, specifically where to place your files and how to inherit SharePoint’s look and feel by using its master page.  The second post focused on configuring permissions for your application and also demonstrated a few handy built-in controls that you can leverage to give your application that true SharePoint-like integrated look and feel.

This post will explain how to add custom navigation for your application.  There are a few different approaches that I like to take depending on the application, and I’ll demonstrate a couple of them for you.  These can be used in any combination in order to achieve the navigation you’re aiming for.  In fact, I recommend a combination of these to achieve a fully integrated navigation structure.

Approach #1: Quick Launch Navigation
Now, modifying the Quick Launch navigation menu is a trivial task.  Simply go to Site Actions > Site Settings and find the settings to modify the navigation (Quick Launch on team sites, and Navigation on publishing sites), and add and remove nodes to your heart’s content.  While this manual method works just fine, it’s just that: manual.  I always have the mindset of if I’m already deploying an application somewhere, I might as well automate as much of the setup steps as I can.  Luckily through the use of features we are able to accomplish this easily.

For the sake of brevity, I am going to assume you know what a SharePoint feature is, what a solution package is, and how to create and deploy them.  If not, there are plenty of great resources out there that will help you out.  Anyways, the first step is to create the feature and associated feature receiver that will execute when the feature is activated on the site.  The feature receiver is going to utilized the SharePoint Object Model to create navigation items.

The first step is to create the feature receiver, which must inherit from the SPFeatureReceiver class and must implement the standard 4 feature operations: FeatureActivated, FeatureDeactivated, FeatureInstalled, and FeatureUninstalled.  For this post I’m only adding the items in the FeatureActivated event, but it’s probably a good idea to clean these up in the FeatureDeactivated event in case the feature is ever deactivated.  My feature receiver looks like this:

namespace DevExpertise.LayoutsApp {
    public class FeatureReceiver: SPFeatureReceiver {
        public override void FeatureActivated(SPFeatureReceiverProperties properties) {
            using (SPWeb site = (properties.Feature.Parent as SPWeb)) {
                // create the nodes
                SPNavigationNode widgetManagementNode = new SPNavigationNode("Widget Management",
                    SPUrlUtility.CombineUrl(site.Url, "DevExpertise.LayoutsApp/WidgetMgmt.aspx"), true);
                SPNavigationNode viewWidgetsNode = new SPNavigationNode("View Widgets",
                    SPUrlUtility.CombineUrl(site.Url, "DevExpertise.LayoutsApp/WidgetList.aspx"), true);
                SPNavigationNode addWidgetNode = new SPNavigationNode("Add Widget",
                    SPUrlUtility.CombineUrl(site.Url, "DevExpertise.LayoutsApp/AddWidget.aspx"), true);
                SPNavigationNode widgetSettingsNode = new SPNavigationNode("Modify Widget Settings",
                    SPUrlUtility.CombineUrl(site.Url, "DevExpertise.LayoutsApp/WidgetSettings.aspx"), true);

                // add the Widget management node to the menu (must be done first)
                site.Navigation.QuickLaunch.AddAsLast(widgetManagementNode);

                // add the sub-items to the Widget Management node
                widgetManagementNode.Children.AddAsLast(viewWidgetsNode);
                widgetManagementNode.Children.AddAsLast(addWidgetNode);
                widgetManagementNode.Children.AddAsLast(widgetSettingsNode);

                // update the site
                site.Update();
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
            // do nothing
        }

        public override void FeatureInstalled(SPFeatureReceiverProperties properties) {
            // do nothing
        }

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {
            // do nothing
        }
    }
}


As you can see, it’s not complicated at all.  Simply add as many SPNavigationNodes as you like!  The next step is to tell the feature to execute the custom feature receiver.  For that just add the ReceiverAssembly and ReceiverClass elements to your feature definition file:

<?xml version="1.0" encoding="utf-8"?>
<Feature
  Id="5856617E-BED2-4705-B030-735F7483225E"
  Title="DevExpertise Layouts Application"
  Description="Contains the necessary components for the DevExpertise custom LAYOUTS application."
  Version="1.0.0.0"
  Scope="Web"
  Hidden="false"
  ImageUrl="DevExpertise\devexpertiseLogo.png"
  ReceiverAssembly="DevExpertise.LayoutsApp, Version=1.0.0.0, culture=neutral, PublicKeyToken=d39eedb6cff9b1c8"
  ReceiverClass="DevExpertise.LayoutsApp.FeatureReceiver"
  xmlns="http://schemas.microsoft.com/sharepoint/">
</Feature>


Once the feature is properly installed, it will show up under Site Features (note that this is scoped at the site level, and will need to be activated on each site):

image


Cross your fingers, activate it, and you should the new navigation items:

image


Not too shabby for a few lines of code, huh?  You’re just as able to add items to the top navigation if you so desire too. 


Approach #2: Custom Actions

One of my favorite things about SharePoint is the ability to extend just about anything, making it a true application development.  Menu items are no exception, and they’re painfully simple to implement.  You are able to add a custom link to the Site Actions menu, Site Settings menus, list menus, individual item menus, Central Administration menus, etc.  If SharePoint has a menu somewhere, chances are you’re able to add your own item to it.  There are 2 ways to do this and I’ll only be demonstrating it one way.  In a future blog post I’ll show how to do all this stuff programmatically for an even more robust navigation structure.

For this sample Widgets application, I would like to take the navigation a bit further and add an item to the Site Actions menu and also create a group in Site Settings that will allow me to manage my application.  To accomplish this, I added a new element manifest to my feature called CustomActions.xml, which will define our custom actions.  Per MSDN, custom action files are included as part of a feature and deployed as XML element descriptions, and structured with a CustomAction element, which serves as the core definition for a single action of a link or toolbar item. The following is my CustomActions.xml file, which defines a single action for the Site Actions menu:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="ManageWidgetAction"
    GroupId="SiteActions"
    Location="Microsoft.SharePoint.StandardMenu"
    Sequence="1"
    Title="Manage Widgets"
    Description="Manage your company's widget catalog."
    ImageUrl ="/_layouts/images/actionssettings.gif"
    Rights="ManageWeb,ManageLists">
    <UrlAction Url="~site/_layouts/DevExpertise.LayoutsApp/ManageWidgets.aspx" />
  </CustomAction>
</Elements>


Let’s break this down a little bit:

  • Id.  The ID of your custom action.
  • GroupId. A pre-defined or custom that determines what group it will be placed into.  A comprehensive list of built-in values can be found here.
  • Location.  The location at which your custom action will be applied.  A comprehensive list of built-in values can be found here.
  • Sequence.  The order in which your custom action will appear in relation to other custom actions.
  • Title.  The display title of the action.
  • Description.  The description of the action, if applicable.
  • ImageUrl. The image that is displayed next to the action, if applicable.
  • Rights.  A comma delimited list of SPBasePermission enumeration items.  A comprehensive list of values can be found here.
  • RequireSiteAdministrator.  A true/false value indicating if this option is only visible to the site collection administrator.
  • UrlAction: This element defines where the link will take you.  Be sure to specify either the ~site or ~sitecollection token which tells SharePoint to build the URL relative to either the site or site collection, respectively.

Simply modify the feature to include this element manifest, redeploy it, reactivate it, and you should see the following in your Site Actions menu:

image


Easy, huh?  Let’s take this a step further and add a few items to the Site Setting page.  You can either add items to existing groups on that page, or create your own.  I created my own by specifying CustomActionGroup in my CustomActions.xml file, as well as a few CustomAction elements that will be a part of my custom group.  The XML is fairly straightforward:

<CustomActionGroup
  Id="LayoutsAppCustomActionGroup"
  Title="Widget Application Settings"
  Sequence="1"
  Location="Microsoft.SharePoint.SiteSettings">
</CustomActionGroup>

<CustomAction
  Id="UserGroupAdminLinkForSettings"
  GroupId="LayoutsAppCustomActionGroup"
  Location="Microsoft.SharePoint.SiteSettings"
  Rights="ManageWeb,ManageLists"
  Sequence="1"
  Title="Manage Widget Categories">
  <UrlAction Url="~site/_layouts/DevExpertise.LayoutsApp/Categories.aspx" />
</CustomAction>

<CustomAction
  Id="UserGroupAdminLinkForSettings"
  GroupId="LayoutsAppCustomActionGroup"
  Location="Microsoft.SharePoint.SiteSettings"
  RequireSiteAdministrator="TRUE"
  Sequence="2"
  Title="Modify Widget Permissions">
  <UrlAction Url="~site/_layouts/DevExpertise.LayoutsApp/Permissions.aspx" />
</CustomAction>


The only notable thing to point out here is for the CustomAction elements, the GroupId is that of the CustomActionGroup I specified first.  This tells SharePoint to put these actions in the custom group.  Redeploy and reactivate your feature, and you will now have this in your Site Settings page:

image


Hopefully you can see from this post that it’s pretty easy to build navigation for your custom application and have that created when your application is deployed via a custom feature.  In the next and final post in this series, I will show my approach to packaging everything up into features and solution packages, and how to deploy that to SharePoint in a simple and streamlined fashion.  Stay tuned!

Tags: , , , , ,
Posted in .NET, Object Model, SharePoint, SharePoint UI, XML | 10 Comments »

Integrating a Custom ASP.NET Application into SharePoint (Part 2)

Posted by DevExpert on 25th February 2009

In my last post, I began describing how to integrate a custom ASP.NET application into SharePoint.  SharePoint is a fantastic platform for building applications, and being able to create your own pages and application structure is a huge win when you need to add missing functionality, or to integrate a non-SharePoint application into SharePoint.

My previous post covered the basics – where to place your custom artifacts, how to inherit the master page and navigation, and how the custom application runs in the context specified in the URL.  This post will briefly cover securing your application pages and will also cover some useful design and UI techniques to give your application a truly integrated look and feel.

If you took at a look at the code I provided, you may have noticed that my custom base class that sets the master page is inheriting from LayoutsPageBase.  This is a page in the object model that is specifically meant to be inherited, and provides us the means to check the users’ rights.  Since Natnael Gebremariam of Bamboo Solutions already did a fantastic job of explaining this and some of the nuances in his post here, I will skip that and just provide a high-level overview.  Basically there are 3 properties that can be overridden to customize the required permissions for your page:

  • AllowAnonymousAccess: A boolean value indicating if the page is accessible by anonymous users.
  • RequireSiteAdministrator: A boolean value indicating if the page is only accessible by site collection administrators.
  • RightsRequired: A list of SPBasePermissions that specify the granular permissions that are necessary to access the page.

For the purposes of this blog series I kept it simple, and I’m denying anonymous access, not requiring users to be site collection administrators, but requiring the user to have at least ManageLists and ManageWeb permissions:

protected override bool AllowAnonymousAccess {
    get {
        return false;
    }
}

protected override bool RequireSiteAdministrator {
    get {
        // only allow site collection administrators access?
        return false;
    }
}

protected override SPBasePermissions RightsRequired {
    get {
        SPBasePermissions permissions = base.RightsRequired
            | SPBasePermissions.ManageLists
            | SPBasePermissions.ManageWeb;

        return permissions;
    }
}

What I don’t particularly like is only being able to specify the permissions that are available through SharePoint, and not being able to specify my own. What if I wanted to check against Active Directory, or the users’ presence in a group, or validate against a line-of-business application?  It’s not built-in, but Natnael describes a pretty good approach that will allow you to accomplish this.

Alright, now that I have the security in place, I can begin building the application.  I’m going to pretend this application is a front-end to a line-of-business database that manages my Widget inventory.  The focus of the rest of this post is going to be on utilizing some out-of-the-box SharePoint web controls.  Don’t pay too much attention to the implementation of these, as this will serve as an overview of some of the server and user controls that you can leverage.  In future posts I’ll elaborate a little on some of these and provide the nitty-gritty details, but for the sake of brevity I’ll just be covering the basics here.

First, we need to register the controls that we are going to use at the top of the ASPX pages.  This is not a comprehensive list, but should give you the idea:

<%@ Register TagPrefix="wssuc" TagName="InputFormSection" Src="/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" Src="/_controltemplates/ButtonSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBar" Src="/_controltemplates/ToolBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" Src="/_controltemplates/ToolBarButton.ascx" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"
    Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>


Toolbar/SPToolBarButton

Many applications have a need for a toolbar, and even SharePoint is littered with them.  You’re able to build one of your own by using the Toolbar.ascx user control (inside the CONTROLTEMPLATES folder), and the SPToolBarButton controls (found within the Microsoft.SharePoint.WebControls namespace):

<wssuc:Toolbar id="tb" runat="server">
    <Template_Buttons>
        <SharePoint:SPToolBarButton
            ID="btnAdd" runat="server" Text="Add Widget"
            ImageUrl="Images/add.gif" NavigateUrl="AddWidget.aspx" />
        <SharePoint:SPToolBarButton
            ID="btnRefresh" runat="server" Text="Refresh List"
            ImageUrl="Images/refresh1.ico" OnClick="btnRefresh_Click" />
    </Template_Buttons>
    <Template_RightButtons>
        <SharePoint:SPToolBarButton
            ID="btnHelp" runat="server" Text="Help"
            ImageUrl="Images/help.gif" NavigateUrl="Help.aspx"  />
    </Template_RightButtons>
</wssuc:Toolbar>


The above markup will render:

image 

SPGridView

The next control worth mentioning is the all-powerful SPGridView control.  This control is inherited from the ASP.NET GridView control which is already a great control, but the SPGridView provides a ton more functionality.  In supports grouping, it automatically inherits the styles of SharePoint, and you are able to add drop-down menus to your items, like users are already used to in lists and libraries.  The markup syntax is pretty straightforward:

<SharePoint:SPGridView
    ID="grid" runat="server" AutoGenerateColumns="false" AllowSorting="true"
    AllowGrouping="true" GroupField="Category" AllowGroupCollapse="true">

    <Columns>
        <asp:BoundField HeaderText="ID" DataField="ID" SortExpression="ID" />
         <asp:BoundField
            HeaderText="Name" DataField="Name"
            SortExpression="Name" />
        <asp:BoundField
            HeaderText="Price" DataField="Price"
            SortExpression="Price" />
        <asp:BoundField
            HeaderText="Quantity on Hand" DataField="QuantityOnHand"
            SortExpression="QuantityOnHand" />
        <asp:BoundField
            HeaderText="Date Added" DataField="DateAdded"
            SortExpression="DateAdded" />
    </Columns>
</SharePoint:SPGridView>


The above markup will render

image

Let’s take this a step further and add the familiar drop-down list to the Name column.  There are a ton of examples on how to do this in code-behind, most notably Powlo’s posts here and here, but here’s a little preview on how to do this in the markup.  First, we need to create our MenuTemplate, which defines the items in the drop-down list:

<SharePoint:MenuTemplate ID="menuTemplate" runat="server">
    <SharePoint:MenuItemTemplate ID="menuEdit" runat="server"
        Text="Edit Widget" ImageUrl="Images/gear.gif"
        ClientOnClickScript="javascript:editSomething('%ID%');" />
    <SharePoint:MenuItemTemplate ID="menuDelete" runat="server"
        Text="Delete Widget" ImageUrl="Images/delete.gif"
        ClientOnClickScript="javascript:deleteSomething('%ID%');" />
</SharePoint:MenuTemplate>


Next, replace the BoundField with an SPMenuField, and specify the menu this is bound to by assigning the MenuTemplateID property to the ID of the menu template we just created.  The TokenNameAndValueFields property assigns a token to a field in the data source.  In this example, I’m declaring two tokens, one for ID and another for Name, which can then be used elsewhere.  The NavigateUrlFields specifies the fields that can be used in the URL set in the NavigateUrlFormat property, and in the same order (it works like the String.Format() method):

<SharePoint:SPMenuField
    HeaderText="Name" TextFields="Name" MenuTemplateId="menuTemplate"
    TokenNameAndValueFields="ID=ID,NAME=Name" NavigateUrlFields="ID"
    NavigateUrlFormat="EditWidget.aspx?id={0}" /> 


The above markup will render:

image 


Form Fields

For creating form fields, there are a ton of ways to skin this cat, but I typically choose one of the following two approaches.  Typically your data-entry forms should either look like the forms used for new list items, or the forms for new lists, sites, or pages.  The simplest and arguably cleanest approach is to just use the ASP.NET controls you’re used to, such as a TextBox, DropDownList, etc., and place them in a table that have the SharePoint styles applied to them.  The end result will look something like this:

image

The markup is pretty simple; the important part is is the style classes applied to the elements, specifically ms-formlabel, ms-formbody, and ms-input.  For the sake of brevity, here is a portion of the markup for the above form:

<tr>
  <td class="ms-formlabel">
      Date Added:
  </td>
  <td class="ms-formbody">
      <asp:TextBox id="txtDateAdded" runat="server" CssClass="ms-input" width="200px" />
  </td>
</tr>
<tr>
  <td class="ms-formlabel">
      Category:
  </td>
  <td class="ms-formbody">
      <asp:DropDownList id="txtCategory" runat="server" CssClass="ms-input" width="200px">
          <asp:ListItem>Cheap Widgets</asp:ListItem>
          <asp:ListItem>Expensive Widgets</asp:ListItem>
      </asp:DropDownList>
  </td>
</tr>


The second approach is to make the form look like the new list/site pages in SharePoint.  This is perfectly fine too, but takes up a little more space:

image

The markup for this is a little more complex, and involves the use of InputFormSection and InputFormControl sections.  A sample of the markup used for the above form is as follows:

<wssuc:InputFormSection runat="server" Title="" id="dateSection">
    <template_description>
       <b>Date</b><br />Please specify a date.
    </template_description>
    <template_inputformcontrols>
        <wssuc:InputFormControl runat="server" LabelText="Date:">
            <Template_Control>
                <wssawc:DateTimeControl  ID="txtDate" runat="server"
                    DateOnly="true" /><BR />
            </Template_Control>
        </wssuc:InputFormControl>
    </template_inputformcontrols>
</wssuc:InputFormSection>  

<wssuc:InputFormSection runat="server" Title="" id="categorySection">
    <template_description>
       <b>Category</b><br />Please specify a category
    </template_description>
    <template_inputformcontrols>
        <wssuc:InputFormControl runat="server" LabelText="Category:">
            <Template_Control>
                <wssawc:InputFormTextBox ID="txtCategory" runat="server"
                    Columns="40" class="ms-input"  /><BR />
            </Template_Control>
        </wssuc:InputFormControl>
    </template_inputformcontrols>
</wssuc:InputFormSection> 

<wssuc:ButtonSection runat="server" ShowStandardCancelButton="false">
    <Template_Buttons>
        <asp:Button runat="server" class="ms-ButtonHeightWidth"
            Text="Save" id="btnSave" />
        <asp:Button runat="server" class="ms-ButtonHeightWidth"
            Text="Cancel" id="btnCancel" CausesValidation="false" />
    </Template_Buttons>
</wssuc:ButtonSection>


As you can see, it’s lot more code, but you’re given a few extra things to work with, like the label and label description to the left, and the extra space to the right.  You’ll also notice I’m using built-in SharePoint controls for the textboxes and the date picker.  There are a bunch of controls that you can leverage, many of which I’ll cover in a future post.  If you’re ambitious though, feel free to poke around the assembly with Reflector or the Object Browser in Visual Studio.  Here’s a screenshot of what you’ll find:

image

There’s all kinds of goodies that are available for us to use in our applications.  Stay tuned for a future post that will dive into a few more of them!

Tags: , , , ,
Posted in .NET, ASP.NET, Object Model, SharePoint, SharePoint UI | 25 Comments »

Integrating a Custom ASP.NET Application into SharePoint (Part 1)

Posted by DevExpert on 18th February 2009

One of the great things about SharePoint is in addition to all cool stuff it does out-of-the-box, you can add on functionality.  More importantly though, SharePoint can be a great platform to build your own application on top of.  In this series, I will show you how to build a custom ASP.NET application and integrate it seamlessly into SharePoint.

The first thing to understand is the location at which we will deploy our custom artifacts.  Since the application will run under the context of a SharePoint site, the files will be deployed to the LAYOUTS folder within the ~12 directory.  There isn’t a need to create a new IIS web site or virtual directory, as it’s using the SharePoint site.

Now, there are different opinions on where certain artifacts should go.  I really don’t think it matters; just personal preference.  One option is to stick with the folder structure that SharePoint uses, and just place a custom folder in each of the destinations that contain your custom artifacts.  Typically this involves placing your files in the following directories:

Type Destination  Reference Path 
.aspx 12\TEMPLATE\LAYOUTS\<ProjectName>\ ~/_layouts/<ProjectName>/Page.aspx
.ascx 12\TEMPLATE\CONTROLTEMPLATES\<ProjectName>\ ~/_controltemplates/<ProjectName>/control.ascx
web.config 12\TEMPLATE\LAYOUTS\<ProjectName>\ (none)
.css 12\TEMPLATE\1033\Styles\<ProjectName>\ /_layouts/1033/styles/<ProjectName>/style.css
.js 12\TEMPLATE\LAYOUTS\1033\<ProjectName>\ /_layouts/1033/<ProjectName>/script.js
.dll Either web app’s BIN directory or GAC (none)
Resource DLLs GAC (none)
Images 12\TEMPLATE\IMAGES\<ProjectName>\ /_layouts/images/<ProjectName>/image.gif
Custom Folders 12\TEMPLATE\LAYOUTS\<ProjectName>\ ~/_layouts/<ProjectName>/MyFolder/…

 

The other option (and my personal preference) is to put everything within a custom folder in the LAYOUTS directory, and only put those files that would require other changes in their respective places.  For example, since a SafeControls entry is required in the web.config for user controls, it makes sense to keep your user controls within that folder.  You could definitely put them inside the LAYOUTS folder with everything else, but then you’d have to create another SafeControls entry.

Type Destination  Reference Path 
.aspx 12\TEMPLATE\LAYOUTS\<ProjectName>\ Page.aspx
.ascx 12\TEMPLATE\CONTROLTEMPLATES\<ProjectName>\ ~/_controltemplates/<ProjectName>/control.ascx
web.config 12\TEMPLATE\LAYOUTS\<ProjectName>\ (none)
.css 12\TEMPLATE\LAYOUTS\<ProjectName>\Styles\ Styles/style.css
.js 12\TEMPLATE\LAYOUTS\<ProjectName>\Scripts\ Scripts/script.js
.dll Either web app’s BIN directory or GAC (none)
Resource DLLs GAC (none)
Images 12\TEMPLATE\LAYOUTS\<ProjectName>\Images Images/image.gif
Custom Folders 12\TEMPLATE\LAYOUTS\<ProjectName>\ MyFolder/…

 

Now that the file locations are ironed out, let’s start getting into how to develop the pages.  I will dive into utilizing built-in SharePoint controls, permissions, and some of the fancier stuff in later posts, and will focus on just getting a page to show up within SharePoint.  Your approach to this may differ, but this has proven very effective for me.  First, I create a new web application in Visual Studio, and create a folder structure that mimics SharePoint’s 12 directory:

image

You’ll notice that I have 2 web.config files – one is created when the project is created and can be used to test the project locally, and the other is the one that will be put into SharePoint.  My web.config that goes into SharePoint is very simple and looks like the following.  For testing purposes, I added a test application setting which we’ll retrieve shortly:

<?xml version="1.0"?>
<configuration>
  <system.web />
  <appSettings>
    <add key="customKey" value="Sample Value" />
  </appSettings>
</configuration>


The next part is probably the most important part of this whole process – setting up the ASPX markup correctly.  Since this page will be integrated into SharePoint’s master page, the same master page/content page principles apply.  The master page contains content place holders which define where page content will go, and the pages themselves define the content that gets inserted in these areas.  SharePoint master pages have a ton of content place holders, most of which we don’t need in a custom application.  The ones that are important are:

  • PlaceHolderAdditionalPageHead: The content area where custom scripts and styles will be referenced.
  • PlaceHolderPageTitle: The title of the page.
  • PlaceHolderPageTitleInTitleArea: The text that shows up right above the main content area.
  • PlaceHolderMain: The main content area.
  • PlaceHolderLeftNavBar: If you want to define your own QuickLaunch or left navigation, you could place it here.

Since it’s up to us to define the content within these place holders, all we need to do is add content areas to our ASPX page and put in what we want.  I only used the top 4 aforementioned areas, as I want to utilize the existing quick launch navigation menu:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs"
         Inherits="DevExpertise.LayoutsApp.Home, DevExpertise.LayoutsApp,
                   Version=1.0.0.0, Culture=neutral, PublicKeyToken=d39eedb6cff9b1c8" %>

<asp:Content contentplaceholderid="PlaceHolderAdditionalPageHead" runat="server">
    <link rel="Stylesheet" type="text/css" href="Styles/style.css" />
    <script src="Scripts/script.js" type="text/javascript" />
</asp:Content>

<asp:Content ContentPlaceHolderID="PlaceHolderPageTitle" runat="Server">
    Page Title - Custom Application
</asp:Content>

<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
    Title Area - Custom Application
</asp:Content>

<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <h1>This is a custom application!</h1>
    <asp:TextBox id="txtValue" runat="server" />
    <asp:Button id="btnSetValue" runat="server" Text="Click Me!" OnClick="btnSetValue_Click" />
</asp:Content>


This doesn’t do much, but will prove the concept.  You can see that I added a custom style sheet (style.css), and also a custom script file (script.js), just so you could see where they go.  In addition, I added a textbox and a button and attached an event handler for the Click event of that button.  In this event handler, I’ll retrieve the web.config setting I mentioned above and set the textbox to this value. The code-behind for this page looks like the following:

namespace DevExpertise.LayoutsApp {
    public partial class Home :System.Web.UI.Page  {

        protected void Page_Load(object sender, EventArgs e) {
        }

        protected void btnSetValue_Click(object sender, EventArgs e) {
            txtValue.Text = WebConfigurationManager.AppSettings["customKey"].ToString();
        }
    }
}

 
Since this code will be executed under the context of SharePoint, the same code access security restrictions apply here as with web parts and custom web services.  You basically have 3 options: adding the assembly to the web application’s BIN directory and setting the trust level to at least WSS_Medium in the web.config, creating a custom code access security policy for your application, or adding the assembly to the GAC.  There are plenty of resources regarding the advantages and disadvantages to each approach out there so I’ll spare you here.  For the sake of simplicity, I added the assembly to the GAC.

Next, I deployed my files to the SharePoint 12 directory.  Since I’m doing this in a development/test environment, I created a handy copy.bat script that uses XCOPY to copy the files to the respective directories.  As soon as this project is ready to be deployed, I’ll run my solution through WSPBuilder and will generate a deployable solution package (.WSP).

After the files are deployed, it’s as simple as typing in the correct URL.  The syntax for this is as follows:

http://server/site/_layouts/<ProjectFolder>/<PageName>.aspx

The URL is extremely important when accessing your application pages, as your application runs under the context of the SharePoint site specified in the URL.  What does this mean?  Well, if you access your page at http://server/_layouts/MyProject/MyPage.aspx, then it’s running under the context of the site collection’s root site, and accessing SPContext.Current.Web will return that site.  If you access your page at http://server/sites/it/blog/_layouts/MyProject/MyPage.aspx, then it’s running under the context of the blog site under the IT site collection, and SPContext.Current.Web will reflect that.  Why is this important?  Well, since the application pages live in the 12 directory on the farm, they’re globally accessible, and not limited to a single site collection or site.  You could even get to your application at http://CentralAdminUrl/_layouts/MyProject/MyPage.aspx, and it will be running under Central Administration’s context.  Now do you see the importance?  I will show you in a later post how to implement safeguards to mitigate this, but be aware for now that your pages are out there for everyone to access.

For my development machine, I will be accessing this at the following URL:

http://server/sites/devexpertise/_layouts/DevExpertise.LayoutsApp/Home.aspx

However, when I try to access this, I get the following:

image


No worries — all this is telling me is that we forgot to specify the master page.  Since this will “inherit” the master page and styles of whichever site it’s accessed from, we must set the master page to that of the current site.  To accomplish this easily for each of my application pages, I create a base LayoutsAppPage that sets the master page:

namespace DevExpertise.LayoutsApp {
    public class LayoutsAppPage : Microsoft.SharePoint.WebControls.LayoutsPageBase {

        protected override void OnPreInit(EventArgs e) {
            base.OnPreInit(e);

            try {
                this.MasterPageFile = SPContext.Current.Web.MasterUrl;
            }
            catch { }
        }
    }
}


You’ll notice that I’m inheriting this from LayoutsPageBase – this is a base class defined in the Microsoft.SharePoint.WebControls namespace that provides us functionality for creating these types of pages.  That’s beyond the scope of this first post, but I will touch on this later in the series.  Next, I inherit each of my application pages from my custom LayoutsAppPage base class:

public partial class Home : LayoutsAppPage {

}


Now if we access the page in the browser, we should get a functioning page.  Clicking the button should retrieve the application setting from the web.config as well:

image


Pretty slick, huh?  Stay tuned for the next posts in this series where we’ll look at how to secure our application and utilize existing SharePoint controls to provide a rich and familiar user interface.

Tags: , , , ,
Posted in .NET, ASP.NET, Object Model, SharePoint, SharePoint UI | 27 Comments »