Speaking New Jersey SharePoint User Group - SharePoint for ASP.NET Developers, Wednesday June 18th

Next week I'll be heading up to New Jersey to speak at the New Jersey SharePoint User Group. My topic will be SharePoint for ASP.NET Developers. Checkout the the NJ User Group Site for registration and more details. If you're interested in leveraging some of your asp.net skills in the SharePoint context or you curious on how to get started on SharePoint development I think you'll find some good stuff in this presentation. I look forward to seeing everyone there.

Back from Orlando

So as most people were arriving for the IT week at TechEd I was heading home. Hopefully PDC will be worth the wait lol. I did hang out for an extra evening though and it was well worth it. I haven't seen so many drunk SharePoint guys in one place. Had a great time hanging out until the wee hours with the  B&R gang (Chris Regan, Jay Medero, Bob Fox, Michael Lotter) along with some other hardcore SharePoint geeks like Joel Olesen and Mike Watson.  Great fun guys, maybe I'll stick around next time... looks like I missed some fun Tuesday night :)

 

Joel, Chris, and Mike posing with the cops.

Joel_Chris_Mike

 

Creating a Enhanced MOSS 404 Feature - Part 1

SharePoint out of the box provides only the default 404 page when users attempt to navigate to a page or site that doesn't exist. It has become very common place for users to expect some assistance when entering an invalid URL and also to be forwarded to the proper site when hitting vanity subsite names. For example if you were to have a product called widget whose physical location is referenced at http://www.contoso.com/products/widgets but you would like users who access http://www.contoso.com/widget to be directed to the proper site.

For complete sourcecode and solution package check out the CodePlex project at http://www.codeplex.com/sharepointsmart404 . Special thanks to Chris Regan for some creative assistance on this project.

Lets go ahead and summarize the goals of this feature:

  • Provide a friendly 404 message to the user
  • Provide some possible best bets on what they may have been attempting to look for based on the URL
  • Provide a vanity redirect mechanism
  • Implement these features with as little overhead on the general page lifecycle

Before we get started on the implementation lets take a quick peak at the end results:

image

 

To implement this feature we'll want to create a new feature that provisions several elements and assemblies to support this functionality.

1) Create a new Feature that will support a simple activation to add this functionality to a site. This feature will include elements to support a custom 404 page, receivers to register the custom 404 page, and finally the support infrastructure for the vanity management:

<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="625D428C-1191-425D-9855-D68335173195"
         Title="SharePoint Smart 404"
         Description="Provides smart Page Not Found handling."
         Scope="Site"
         Hidden="False"
         Version="1.0.0.0"
         ReceiverAssembly="SharePointSmart404, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eaf58ff25bb059f9"
         ReceiverClass="SharePointSmart404.SharePointSmartReceiver">

  <ElementManifests>
    <ElementManifest Location="pages.xml" />
    <ElementManifest Location="vanity.xml"/>
    <ElementFile Location="Pages\Smart404.aspx"/>
  </ElementManifests>

</Feature>

 

2) Central to the custom 404 functionality is the implementation of a custom 404 html file. Unfortunately the landing page for 404 errors within SharePoint do not support asp.net pages. Instead a HTML page receives the initial 404 error and forwards to a designated site. The custom 404 html file must be registered through the sharepoint API. As you can se we are parsing the url in javascript to obtain the subsite  details. We then create a new query string that the SharePoint search webparts will then use to provide some best bets. 

<html>
<head>
    <meta HTTP-EQUIV="Content-Type" content="text/html; charset=utf-8" />
    <meta HTTP-EQUIV="Expires" content="0" /> 
    <noscript>
        <meta http-equiv="refresh" content="0; url=/_layouts/spsredirect.aspx?noscript=1" />
    </noscript> 
    <script language="javascript" src="/_layouts/1033/init.js"></script>
   1:  
   2:     <script language="javascript" src="/_layouts/1033/core.js">
   1: </script>
   2:     <script language="javascript">
   3:         var requestedUrl = escapeProperly(window.location.href);
   4:         var url_array = window.location.href.split("/");
   5:         var lastelementindex = url_array.length -1
   6:         var pagename = url_array[lastelementindex];
   7:  
   8:         STSNavigate("/pagenotfound/smart404.aspx?oldUrl=" + requestedUrl +"&k=" + pagename);
   9:     
</script> </head> <body> </body> </html>

 

3) Create a custom 404 page. In our case this page will require a code behind because we need to support some vanity redirect logic. Additionally this page should contain the supporting webparts to provide search results based on query string parameters passed in from the custom 404 page. This page is being provisioned as a site page as opposed to an application page in the layouts folder so that it can support customization per web application. Because this page will need to support cusomtization we do not want to do inline code.

 

<asp:Content ID="Content1" ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
    404 - Page Not Found
</asp:Content>

<asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    </asp:Content>

<asp:Content ID="Content2" ContentPlaceholderID="PlaceHolderMain" runat="server">
    <div class="smart404">
        <h2>
            The page you requested could not be found.</h2>
        <div class="smart404Notice">
            We apologize, but the page you requested is unavailable. It may be temporarily offline
            due to maintenance, or it may not exist.
        </div>
        <div class="smart404Return">
            <a href="javascript:history.go(-2)">Return to Previous Page</a>
        </div>
        <div class="smart404Search">
            <h4>Best Bets</h4>
            <WebPartPages:WebPartZone runat="server" ID="mainContentZone">
            <ZoneTemplate>
                 <Search:coreresultswebpart runat="server" id="search" DisplayRSSLink="false" DisplayAlertMeLink="false"
                        ResultsPerPage="10" ChromeType="None" >
                    </Search:coreresultswebpart>
                    <br />
                    <Search:SearchPagingWebPart runat="server" id="searchpaging" ChromeType="None">
                    </Search:SearchPagingWebPart>
            </ZoneTemplate>
            </WebPartPages:WebPartZone>
        </div>
    </div>
       
</asp:Content>

 

4) Next we'll need to provision the custom 404 page and the location it will be residing in:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  

  <ListInstance TemplateType="101" RootWebOnly="false" 
              Id="PageNotFound"
              Title="Page Not Found"
              Description="Page Not Found Supporting Pages"
              Url="PageNotFound"
              OnQuickLaunch="False"
              FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101">
  </ListInstance>
          

  <Module Url="PageNotFound"
          Path="Pages"
          RootWebOnly="TRUE">
    <File Url="Smart404.aspx"
          Name="Smart404.aspx"
          Type="GhostableInLibrary"
          IgnoreIfAlreadyExists="TRUE">   
    </File>
  </Module>

</Elements>

 

5) In the feature receiver for our feature we want to make sure that the new HTML form gets registered.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{

   string webAppUrl = (properties.Feature.Parent as SPSite).Url;

   SPWebApplication webApplication = SPWebApplication.Lookup(new Uri(webAppUrl));

   webApplication.FileNotFoundPage = "smart404.html";

   webApplication.Update();

}

 

In part 2 of my next post I'll touch on the implementation of the vanity portion of the smart 404 implementation.

I felt naked without Visual Studio today...

Seriously I felt naked without Visual Studio in today's presentation at InBetween down here in Orlando. I've been giving SharePoint developer talks for a few years now but this was the first presentation I have given on SharePoint administration. The slide deck really needed some more love but I'm very comfortable with the topic so I thought that would help out things but once I got in front of the crowd I felt like I didn't have anything to do. Just PowerPoint and some admin screens. Hopefully it wasn't as painful for the crowd as it was for me lol. I think I'm going to stick with my developer discussions while I work on my technique for non-code presentations.

How do you provision a MasterPages across all SharePoint sites from a single Feature?

This has came up twice this week with completely different clients so I thought this would be a good candidate for a post.  The answer is with an intelligent feature receiver. The scenario is that a client is running Windows SharePoint Services and would like to provision a new masterpage and stylesheet through a feature and set that as the default masterpage and stylesheet for not only the current site but all sites below that as well.

Step 1: Create a new feature that will provision our masterpage and stylesheets and reference a receiver class that will apply our logic.

<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="4344610E-2C91-4ca0-B9DD-89CBA2168545"
         Title="Global MasterPage"
         Description="Global MasterPage"
         Scope="Web"
         Hidden="False"
         Version="1.0.0.0"
         ReceiverAssembly="GlobalMasterPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=[Insert your key]"
         ReceiverClass="GlobalMasterPage.MasterpageFeatureReceiver">
  <ElementManifests>
    <ElementManifest Location="elements.xml" />
    <ElementFile Location="MasterPages\custom.master" />
    <ElementFile Location="Styles\custom.css" />
  </ElementManifests>

</Feature>

 

Step 2: Create a new elements file that will provision the masterpage, create a new instance of the CSS style library, and provision the stylesheet in the new library

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <!-- add master page(s) -->
  <Module Url="_catalogs/masterpage"
          Path="MasterPages"
          RootWebOnly="false">
    <File Url="custom.master"
          Name="custom.master"
          Type="GhostableInLibrary">
    </File>  
  </Module>

 <!-- Provision the list for the style sheet-->
  <ListInstance TemplateType="101" RootWebOnly="false"
                   Id="CSSStyleLibrary"
                   Title="CSSStyleLibrary"
                   Description="Styles"
                   Url="CSSStyleLibrary"
                   OnQuickLaunch="False"
           FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101">
  </ListInstance>

  <!-- Provision the style sheet-->
  <Module Url="CSSStyleLibrary" Path="Styles" RootWebOnly="false">
    <File Url="custom.css" Name="custom.css" Type="GhostableInLibrary">
    </File>
  </Module>

</Elements>

 

Step 3: Create a new assembly that contains the feature receiver. Don't forget this assembly needs to be deployed to the GAC. This receiver sets the default masterpage and style sheet for the current site and then activates the same feature on child sites. This has the affect of recursively applying the masterpage and stylesheet to each and every site.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
     SPWeb web = properties.Feature.Parent as SPWeb;

     web.MasterUrl = "/_catalogs/masterpage/custom.master";
     web.AlternateCssUrl = "/sytles/custom.css";
     web.Update();

     foreach (SPWeb subWeb in web.Webs)
     {
         if (subWeb.Features[new Guid("4344610E-2C91-4ca0-B9DD-89CBA2168545")] == null)
         {
             subWeb.Features.Add(new Guid("4344610E-2C91-4ca0-B9DD-89CBA2168545"));
         }

     }

}

 

Naturally your feature will need to include the proper masterpages and stylesheets. And the GUID that defines the feature to be activated should match the GUID of your feature. In the end your Visual Studio solution should look something like:

image

 

From a management standpoint as long as you don't customize either the masterpage or css with SPD (meaning you manage the changes in the Feature on the file system) all changes will be visible on any site using this feature. Seeing that this came in handy twice in one week hopefully it will come in handy for some of you folks out there.

Triangle .Net User Group - SharePoint Special Focus Group Kick-Off Meeting July 1st


UPDATE: The date of this event has been changed to July 1st. Please update your calendars.

I'm very excited to announce the formal kick-off meeting of TRINUG's SharePoint Special Focus Group next month on June 24th. In this monthly special focus group we will be emphasizing the developer aspects of SharePoint but any and all topics in SharePoint and related technologies will be fair game.  From time to time I plan to have some scheduled speakers, including SharePoint MVPs drop by to give us some out of town perspectives on SharePoint topics. This will be a very interactive and social event. Bring your ideas, thoughts, questions along with you. This is open to all who have an interest in learning more about SharePoint whether you've been working with it for years, just got it thrown on your lap, or you're just curious about what the buzz is all about.

In this first session we're going to be covering the basics of Features. Features, although poorly named, are one of the basic building blocks of SharePoint and are essential for provisioning functionality in your site.  Without a solid understanding of Features you'll never move past SharePoint Designer for developing robust, reusable, and arguably more interesting SharePoint components.

I would like to give a special thanks out to Mark Weinburg over at Tek Systems for providing the facilities for this event. I walked through the conference room the other day and its going to be a great location for the meeting!

Because of the level of interest that has been expressed in the last few months we will be requiring registration for this first event to make sure we don't go over capacity. Registration should be setup at http://www.trinug.org in the next couple days and we'll provide additional information such as time and driving directions. Seating will be limited to 30. In all honesty I'm not expecting that many folks and I like to keep the group small and interactive but if we do have more folks then that I'll start working on some different outlets for the SharePoint community in Raleigh.

Drop me some comments if you have some ideas on topics you'd like to see or general questions\feedback. I look forward to seeing you all!

 

No plans for the weekend in between TechEd? Checkout ]inbetween[ !

Joe Healy has setup a great community event for the weekend in between TechEd tracks called ]inbetween[. Everything from the .Net University Tracks of SharePoint, Biztalk, and .Net 3.5 to Code Camps,SQL University, and DotNetNuke University to just name a few. If you're a geek there will be a topic of interest to you here some place!

I'll be speaking at the SharePoint .Net University track on the ever exciting topic of SharePoint Installation and Deployment.  Anyone who knows me knows I'm heavy on the development side so I got a double take from some friends when I said I was speaking on SharePoint installation and configuration. I chuckled as well but seriously any developer in the SharePoint space who isn't competent with topics related to installation and configuration has never left SharePoint Designer :).

Other SharePoint track speakers include Mikhail Dikov, Jacob Sanford, Doug Ware, and Ted Pattison. Don't have time or not heading down to TechEd?  You can download the courses, including my webcast, over at .Net University site.

Interested? Make sure to register so you can attend the evening social.

SharePoint, Forms Based Authentication (FBA), and viewing the Password Answer

Email based communications can often be problematic and a source of frustration for users. With the increased use of spam blockers both at the mail server level and email client level important communications, such as password changes, can often be blocked. One way around this is to use a combination of Question and Answer to provide verification of your user without relying on an email based workflow for password retrieval. The ASP.NET Membership Provider, specifically the SQL Membership Provider, provides a baseline set of functionality and controls for managing and storage of secret question and answer combinations within Forms Based Authenticated SharePoint (and ASP.NET for that matter).

Several settings in the membership provider section of the web.config drive some of the behavior for password retrieval. Three specifically are of concern to our scenario:

  • enablePasswordReset - Controls whether or not the password can be reset. Used in conjunction with requiresQuestionAndAnswer
  • requiresQuestionAndAnswer - Controls whether or not a question and and answer are required
  • passwordFormat - Specifies the password format including clear, encrypted, hashed.

In addition to not requiring email notifications following a successful challenge response our client requested the ability for a user to view their password answer. Unfortunately neither the Membership nor the MembershipUser classes provide an interface to be able to retrieve the password answer. To add some additional complications the password answer is stored in the same format as the password itself. If the passwordFormat setting is set to Hashed you will be unable to retrieve the answer but if it is set to either clear or encrypted you will be able to view the password answer. Considering that the password answer is just as important as the password itself its no wonder that the answer is protected in the same manner as the password. I recommend using encrypted and for my sample I assume that the password format is set to encrypted.

The key to our solution is decrypting the password answer and fortunately we have access to the MembershipProvider base class that has a protected method DecryptPassword that provides the desired functionality. Don't let the method name fool you though. It's actually a generic function that can decrypt the password answer as well. Unfortunately in order to gain access to this method we have to implement the SqlMembershipProvider class. Lastly we'll need to query the Membership SQL database directly to get the PasswordAnswer field back from the database.

 

public class CustomMembership : SqlMembershipProvider 
{        
    public string GetUserSecretAnswer(Guid providerKey)
    {
        string answer = string.Empty;
    
        MembershipUser user = Membership.GetUser(providerKey);
    
        string connectionString = ConfigurationManager.ConnectionStrings["CredentialStore"].ToString();
        SqlConnection connection = new SqlConnection(connectionString);
        connection.Open();
    
        SqlCommand command = new SqlCommand("Select CAST(PasswordAnswer as nvarchar(128)) from aspnet_membership where UserID=@UserID", connection);
        command.CommandType = CommandType.Text;
    
        SqlParameter param = new SqlParameter("@UserID", user.ProviderUserKey);
        command.Parameters.Add(param);
    
        SqlDataReader reader = command.ExecuteReader();
        string encryptedAnswer = string.Empty;
    
        if (reader.Read())
        {
           encryptedAnswer = reader.GetString(0);
        }
    
        byte[] encodedBytes= Convert.FromBase64String(encryptedAnswer);
        byte[] decodedBytes = this.DecryptPassword(encodedBytes);
    
        answer = System.Text.Encoding.Unicode.GetString(decodedBytes, 0x10, decodedBytes.Length - 0x10);
           
        return answer;
    }
}

 

You may want to put some checks in for your password format types to make the function more universal and additionally retrieve the connection string from the membership provider configuration section as opposed to a hardcoded connection string key. Lastly this solution isn't universal for just SharePoint but any ASP.NET application that is making use of the membership provider plumbing.

 
 
Officially Joining the B&R Team

I've had a very close working relationship with B&R for several years now leading many of their development heavy projects including their most recent effort with AMD over at http://developer.amd.com . I'm very happy to announce that ViewFusion Solutions is officially joining the B&R team. In addition to continuing to manage large SharePoint development efforts for B&R I will also be heading up the new products development division, keeping the name of ViewFusion but now under the B&R umbrella. I'm very excited to get some of the product offerings off the ground and I'm also planning to rope them into some of my community projects so I can actually get a few of them shipped! Although I've had the chance to work with the whole team I'm proud to finally call Chris Regan, Jay Madero, Bob Fox, Michael Lotter, and Nas among others my official co-workers now. I honestly believe that B&R is one of the best SharePoint shops out there and I'm very excited to become part of the team.

Charlotte Code Camp - SharePoint for ASP.NET Developers  Follow-up

Thanks for all who attended my session yesterday at the Charlotte Code Camp. I always look forward to a full crowd in Charlotte and I apologize for those folks who may have had to stand for a bit. Hopefully it worth the discomfort :)  As promised I've attached the demo code I used during the presentation to this post. The last couple demos which I spun through pretty quick are also included. Although this is my 4th visit to the Charlotte Code Camp I always seem to forget that the sessions are only 1 hour instead of  the typical 1 hour and 15 minutes and I could have really used that extra 10 minutes.

Many of you had some questions after the presentation on some of the extra tools I was using. As I mentioned in my presentation the developer experience in Visual Studio really isn't there yet for SharePoint so I rely on a combination of automation and community tools to fill in the gaps.

 

VS_SS

 

There are several areas of interest in my IDE

  • On the far left is Andrew Connell's Visual Studio plug-in that provides some automation on the creation of solution package supporting files (ddf & manifest) and the standardization of the Visual Studio's solution structure that comes into play with additional automation. more detail's on AC's tool can be found at http://www.andrewconnell.com/blog/archive/2008/04/10/Announcing-ACs-VS-SharePoint-Project-Utility-Tool-Window.aspx . It's still beta and has a few gotchas but its my favorite tool so far because it doesn't have any project dependencies.
  • On the top is a custom toolbar that has external tool button links located on it. Others have implemented this functionality as custom build targets within the project file but I've found I prefer the buttons. You're mileage may very. These buttons are able to work because all of my projects follow a established structure I can infer some functionality based on this structure. My toolbar includes:
  1. WSP Add Solution:  stsadm -o addsolution -filename  "$(ProjectDir)\DeploymentFiles\$(TargetName).wsp"
  2. WSP Solution Deploy: stsadm -o deploysolution -local -allowgacdeployment -allcontenturls -force -name  $(TargetName).wsp
  3. WSP Solution Upgrade: -o upgradesolution -local -allowgacdeployment -name $(TargetName).wsp  -filename "$(ProjectDir)DeploymentFiles\$(TargetName).wsp
  4. WSP Solution Retract: stsadm -o retractsolution -local -allcontenturls -name $(TargetName).wsp
  5. WSP Solution Deleted: stasadm -o deletesolution -name $(TargetName).wsp
  6. Quick Deploy: /e /y $(ProjectDir)\RootFiles\TEMPLATE\* "c:\program files\common files\microsoft shared\web server extensions\12\Template"
  7. IIS App Pools: appcmd.exe list wp ** only works with windows server 2008
  8. Public Key: sn.exe -Tp "$(TargetPath)"
  • Lastly you may have noticed that I used a custom project template. My project template is actually a simple modification to standard c# library project that includes an extra build step that creates a solution package at build time.

<Target Name="AfterBuild">
   <Exec Command="C:\Windows\System32\MAKECAB.EXE /F DeploymentFiles\BuildSharePointPackage.ddf /D CabinetNameTemplate=$(Targetname).wsp /D DiskDirectory1=DeploymentFiles " />
   <Exec Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" Command="C:\Windows\System32\MAKECAB.EXE /F DeploymentFiles\BuildSharePointPackage.ddf /D CabinetNameTemplate=$(Targetname).cab /D DiskDirectory1=DeploymentFiles" />
</Target>

 

The demo code and projects can be downloaded at SharePoint for ASP.NET Demo Code. Thanks again for attending my session. I look forward to seeing you all next time.

 

1 - 10 Next
Josh Carlisle

 ‭(Hidden)‬ Admin Links