|
|
-
I've mentioned before that I became somewhat of a Web standards zealot several years ago. Consequently, regardless of whether I'm building Web sites using the core ASP.NET platform or Microsoft Office SharePoint Server (MOSS) 2007, I strive to ensure that minimal, semantic HTML markup is used to render the site. I then leverage CSS rules to define the presentational aspects of the content. Note that this can sometimes seem like an epic battle between the Web developer and the underlying platform.
Anyone who has worked with SharePoint in sufficient depth knows all too well that the underlying markup generated by the out-of-the-box features can be very complex -- and definitely not "semantic." Table-based layouts are used virtually everywhere in MOSS 2007, and while CSS rules are utilized for much of the styling, you'll quickly encounter limitations when trying to customize the look-and-feel of your site.
However, it's important to understand that you can create Web sites using MOSS 2007 that are mostly Web standards compliant -- without abandoning a large amount of the "out-of-the-box goodness" that you get with MOSS 2007.
For example, I'm not willing to forgo using Web Parts entirely on a MOSS 2007 Web site just for the sake of achieving strict XHTML compliance. Others may disagree, but in my opinion, this isn't an acceptable tradeoff.
Some specific examples where OOTB Web Parts provide incredible value -- at the cost of some less-than-ideal markup -- are the search pages for the Agilent Technolgies and KPMG sites (both of which I originally built). Sure, you could choose to create your own search Web Parts that create the exact look-and-feel originally specified by your client, but that's not how a good SharePoint developer should approach the problem. As I've said numerous times before, I often find myself ripping out large amounts of customization and replacing the functionality with out-of-the-box features. This isn't always easy, but usually you can convince your client that changing the user experience a little (e.g. paging through search results) in order to eliminate large amounts of custom code is inevitably a very good thing.
If at this point you are thinking something along the lines of "but, Jeremy, that means all the Web sites built on SharePoint will essentially look the same," then I encourage you to compare the search result pages on the Agilent and KPMG sites. Yes there are clearly similarities between the two. For example, both sites provide a faceted search feature to allow users to quickly narrow their search results based on one or more property filters. However, neither one looks much like the out-of-the-box Search Center in MOSS 2007 (even though both of them are based on that site template).
On my most recent project, we are building an Internet-facing customer portal based on MOSS 2007. One of the first things I did shortly after joining the project was convert the existing proof-of-concept to use a Web standards design, stripping out large amounts of markup and simplifying the CSS dramatically.
Rather than defining all of the CSS layout from scratch, I chose to utilize the 960 Grid System. For those of you that are still deeply rooted in using table-based layout, a grid is simply a combination of vertical columns, horizontal fields, and white space gutters (in other words, just say "no" to spacer GIFs and an ungodly number of nested tables).
The 960 Grid System provides two variants: 12 columns and 16 columns. However, we are currently only using the 12 column layout for this site. I should also note that we are using a fixed-width design (although there are “fluid” alternatives available based on the 960 Grid System). Bear in mind that things get considerably more complex when trying to support arbitrary sizing of the browser window (which is actually something that SharePoint does very well out-of-the-box with its table-based layout).
Tip
If you choose to use the 960 Grid System, I also highly recommend leveraging the 960 Gridder as well. This combination makes the task of creating great looking Web pages much easier (even for someone, like me, who is much more of a developer than a designer).
To understand how the 960 Grid System is used on our SharePoint site, consider the following master page:
<%@ Master Language="C#" CodeBehind="Fabrikam.master.cs" Inherits="Fabrikam.Portal.Web.UI.FabrikamMasterPage, Fabrikam.Portal.Web,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=c8cdcbca6f69701f" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register TagPrefix="SharePoint"
Namespace="Microsoft.SharePoint.WebControls"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages"
Namespace="Microsoft.SharePoint.WebPartPages"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="PublishingWebControls"
Namespace="Microsoft.SharePoint.Publishing.WebControls"
Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="FabrikamWebControls"
Namespace="Fabrikam.Portal.Web.UI.WebControls"
Assembly="Fabrikam.Portal.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c8cdcbca6f69701f" %>
<%@ Register TagPrefix="PublishingConsole"
TagName="Console"
Src="~/_controltemplates/PublishingConsole.ascx" %>
<%@ Register TagPrefix="PublishingSiteAction"
TagName="SiteActionMenu"
Src="~/_controltemplates/PublishingActionMenu.ascx" %>
<%@ Register TagPrefix="Fabrikam"
TagName="Welcome"
Src="~/_controltemplates/Fabrikam/Welcome.ascx" %>
<html dir="<%$Resources:wss, multipages_direction_dir_value %>"
runat="server" __expr-val-dir="ltr">
<head runat="server">
<meta name="GENERATOR" content="Microsoft SharePoint" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<SharePoint:RobotsMetaTag runat="server" />
<title id="onetidTitle"><asp:ContentPlaceHolder ID="PlaceHolderPageTitle" runat="server" /></title>
<SharePoint:CssLink runat="server" />
<!--Styles used for positioning, font and spacing definitions-->
<SharePoint:CssRegistration
runat="server"
Name="<% $SPUrl:~sitecollection/Style Library/~language/Core Styles/controls.css %>" />
<link rel="stylesheet" type="text/css"
href="<% $SPUrl:~SiteCollection/Style Library/Fabrikam/Themes/Theme1/Fabrikam-Main.css%>" />
<SharePoint:ScriptLink Name="init.js" runat="server" />
<!--Placeholder for additional overrides-->
<asp:ContentPlaceHolder ID="PlaceHolderAdditionalPageHead" runat="server" />
</head>
<body>
<form runat="server" onsubmit="return _spFormOnSubmitWrapper();">
<asp:ScriptManager runat="server" />
<WebPartPages:SPWebPartManager ID="mainWPManager" runat="server" />
<div class="container_12">
<div id="masthead">
<div id="logo">
<a href="/" title="Go to Start Page">
<img src="/Style Library/Images/Fabrikam/fabrikam-logo.png" />
</a>
</div>
<div id="welcomearea">
<%--
Generally speaking, style attributes should be avoided in
HTML. Styling should instead be applied exclusively through
CSS.
However the SharePoint SiteActionMenu control emits HTML
similar to the following:
<table height="100%" class="ms-siteaction" ...>
...
</table>
When CSS is disabled, the height of the SiteActionMenu is
ridiculous (which makes it more difficult to troubleshoot
other styling issues). Consequently, limit the height of the
container <div> to avoid this issue.
--%>
<div id="siteactionmenu" style="height: 18px">
<PublishingSiteAction:SiteActionMenu runat="server" />
</div>
<div class="welcome">
<Fabrikam:Welcome id="WelcomeUserControl" runat="server" />
</div>
</div>
<PublishingWebControls:AuthoringContainer runat="server">
<PublishingConsole:Console runat="server" />
</PublishingWebControls:AuthoringContainer>
</div>
<asp:ContentPlaceHolder ID="PlaceHolderMain" runat="server" />
</div>
<asp:Panel ID="Panel1" Visible="false" runat="server">
<asp:ContentPlaceHolder ID="PlaceHolderSearchArea" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderTitleBreadcrumb" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderLeftNavBar" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderPageImage" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderBodyLeftBorder" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderNavSpacer" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderTitleLeftBorder" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderTitleAreaSeparator" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderMiniConsole" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderCalendarNavigator" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderLeftActions" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderPageDescription" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderBodyAreaClass" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderTitleAreaClass" runat="server" />
<asp:ContentPlaceHolder ID="PlaceHolderBodyRightMargin" runat="server" />
</asp:Panel>
</form>
</body>
</html>
This is very similar to the minimal master page example on MSDN -- but with some important changes. I'll cover the important similarities as well as the differences in the remainder of this post.
Ignoring the differences in the page directives (which are irrelevant when discussing Web standards), the first thing you'll notice is that the master page specifies a DOCTYPE:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
You should always specify a DOCTYPE in your Web pages in order to avoid the dreaded quirks mode in Web browsers. Since, as I noted earlier, the markup emitted by SharePoint isn't always ideal, avoid the temptation to specify a strict DOCTYPE, such as:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
When I attended some training last summer on SharePoint 2010, I remember hearing that the new version is supposed to be compliant with XHTML strict. However, I don't know if that will actually hold true, but regardless, the current version -- as they say -- "is what it is."
In the MSDN minimal master page sample, the following items immediately follow the <html> element:
<WebPartPages:SPWebPartManager runat="server"/>
<SharePoint:RobotsMetaTag runat="server"/>
Since the <html> element should only contain <head> and <body> elements, the SPWebPartManager control should be placed within the <form> element inside the <body> element, and the RobotsMetaTag control should be placed within the <head> element (since it emits <meta> elements).
The next important difference between the sample MSDN master page and the one shown above is with regards to the links to CSS files. The MSDN sample only specifies the following:
<SharePoint:CssLink runat="server"/>
My master page, however, specifies the following:
<SharePoint:CssLink runat="server" />
<!--Styles used for positioning, font and spacing definitions-->
<SharePoint:CssRegistration
runat="server"
Name="<% $SPUrl:~sitecollection/Style Library/~language/Core Styles/controls.css %>" />
<link rel="stylesheet" type="text/css"
href="<% $SPUrl:~SiteCollection/Style Library/Fabrikam/Themes/Theme1/Fabrikam-Main.css%>" />
Note that the controls.css file is referenced in the out-of-the-box MOSS 2007 sample master pages (e.g. BlueBand.master) but for some reason was omitted from the MSDN sample page. I've seen some blog posts that say this CSS file should either be omitted entirely, or only rendered for content authors (by enclosing the declaration in an AuthoringContainer element). However, if you try to leverage as many OOTB SharePoint features as possible -- including the SummaryLinkFieldControl for displaying lists of links -- then you should reference this CSS file (or be prepared to recreate numerous CSS rules that SharePoint would otherwise provide for you).
Also note that I use a regular HTML <link> element to link in my custom CSS file (Fabrikam-Main.css). This is because I want it to always appear last in the list of linked CSS files and, unfortunately, the SharePoint CssLink control has a nasty habit of rendering CSS links using its own ordering scheme -- rather than how you specify items with the CssRegistration control (hence why some of the sample CSS files included with SharePoint include the "zz1_" prefix). Consequently I just use a regular HTML element to ensure I can override any CSS rules specified by SharePoint.
Before diving into the contents of my custom CSS file, let's first cover the purpose of my Themes and Theme1 folders.
I have to admit that I initially liked the idea of ASP.NET themes and skins. However, that was before I became "enlightened" by the CSS Zen Garden. Consequently, when I refer to a theme, I'm typically talking about a collection of CSS files that define the visual structure of site at a particular point in time. Since I want to provide the ability for a company to quickly revamp its Web site with a new look-and-feel, I utilize a Themes folder with individual subfolders that contain the CSS files (and related images) that define a specific look-and-feel.
While you can tell that I'm not really creative when it comes to names, you can easily imagine how we could add a Theme2 folder -- along with a new set of CSS files, images, etc. -- and subsequently completely overhaul the visual appearance of a site -- simply by referencing the new Fabrikam-Main.css file in the Theme2 folder. This is, after all, one of the compelling reasons for implementing a Web standards design.
The reason why the custom CSS file contains the "-Main" suffix is because it typically refers to other CSS files. For example, the first two lines of Fabrikam-Main.css are:
@import url('Fabrikam-Basic.css');
@import url('960.css');
At the risk of stating the obvious, the 960.css file is the one that comes with the 960 Grid System (without any changes whatsoever).
Depending on the need to support various browser versions, we might also need to include other CSS files (such as Fabrikam-IE.css or, Heaven forbid, Fabrikam-IE6.css). Fortunately, at least on this particular project, we have the privilege of playing the "unsupported" trump card if people complain that they don't like the way the site looks in Internet Explorer 6. Consequently, the number of CSS files is very small for this project.
Here are the contents of Fabrikam-Basic.css:
/* Reset styles to standardize formatting across various browsers (refer to
* http://meyerweb.com/eric/tools/css/reset/ and
* http://developer.yahoo.com/yui/reset/ for more info).
*
* Note that Eric's original approach is a little too "aggressive" for
* SharePoint sites. For example, the "vertical-align: baseline;" reset rule
* subsequently breaks many different areas of the site. Consequently, we base
* our reset rules on the simpler set from the Yahoo! YUI team.
------------------------------------------------------------------------------*/
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset,
input, textarea, p, blockquote, th, td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset, img {
border: 0;
}
address, caption, cite, code, dfn, em, strong, th, var {
font-style: normal;
font-weight: normal;
}
ol, ul {
list-style: none;
}
caption, th {
text-align: left;
}
h1, h2, h3, h4, h5, h6 {
font-size: 100%;
font-weight: normal;
}
q:before, q:after {
content: '';
}
abbr, acronym {
border: 0;
}
hr {
border: 0;
background: #000;
color: black;
height: 1px;
margin: 0;
}
/* HTML elements (Ideally, this section should not contain any CSS classes,
* but rather only contain generic CSS rules for HTML elements. These rules
* essentially undo the "reset" rules above in order to achieve the desired
* formatting across various "A-grade" browsers.
------------------------------------------------------------------------------*/
body {
background: #fff;
color: #58595b;
font-family: Arial, Helvetica, Sans-Serif;
font-size: small;
}
h1, h2, h3, h4, h5, h6, strong {
font-weight: bold;
}
h1 {
color: #868686;
font-size: 2.3em;
font-style: italic;
font-weight: bold;
margin: .67em 0;
}
h2 {
color: #3a3b3c;
font-size: 1.5em;
font-weight: bold;
margin: .83em 0;
}
h3 {
color: #303435;
font-size: 1.17em;
font-weight: bold;
margin: 1em 0;
}
h4 {
font-weight: bold;
margin: 1.33em 0;
}
h5 {
font-size: 0.83em;
font-weight: bold;
margin: 1.67em 0;
}
h6 {
font-size: 0.67em;
font-weight: bold;
margin: 2.33em 0;
}
abbr, acronym {
border-bottom: 1px dotted #000;
cursor: help;
}
blockquote {
margin: 1em 40px;
}
em {
font-style: italic;
}
dl, ol, ul {
margin: 1em 1em 1em 2em;
}
dl dd {
margin-left: 1em;
}
input[type="checkbox"] {
margin-right: 5px;
}
input[type="submit"] {
padding: 1px 4px;
}
ol li {
list-style: decimal outside;
}
ul li {
list-style: disc outside;
}
/* HACK: As noted above, this section should not contain rules for specific CSS
* classes, but rather generic CSS rules for all HTML elements. However,
* table-based layout is so rampant in MOSS 2007 that caution must be used when
* applying rules to tables -- or else the page layout becomes atrocious.
* Consequently, we use a CSS class to explicitly distinguish tables that are
* intended to be shown to users ("display tables") from the generic tables used
* for layout (i.e. "layout tables").
*/
th, table.displayTable td {
border: 1px solid #000;
padding: .5em;
}
th {
font-weight: bold;
text-align: center;
}
caption {
margin-bottom: .5em;
text-align: center;
}
fieldset {
margin-left: 2px;
margin-right: 2px;
padding: 0.35em 0.625em 0.75em;
border: 2px groove ThreeDFace;
}
/* HACK: See note above regarding use of "displayTable" class in this section */
p, fieldset, table.displayTable {
margin-bottom: 1em;
}
hr {
background: #fff;
border-top: solid 1px #c3c3c3;
margin: 1em 0;
}
The purpose of the "basic" CSS file -- as noted in the comments at the top of the file -- is to reset the default styling that frequently varies across different Web browsers and define the core styles of various HTML elements.
For the most part, these rules are based on the YUI Reset CSS. However, you can see that I had to make some minor changes in order to avoid fundamentally "breaking" SharePoint (due to the rampant table-based layout that I referred to earlier).
It's important to understand that changes to the Fabrikam-Basic.css file are not expected to occur very frequently. Rather, as new features are added to the SharePoint site, CSS rules are added or updated in Fabrikam-Main.css.
While most of the CSS rules in Fabrikam-Main.css wouldn't be of interest to most people (since they are specific to one particular Web site), it is worth highlighting a few of the rules:
/* =core (SharePoint core.css overrides)
------------------------------------------------------------------------------*/
.ms-pagebreadcrumb {
border: 0;
}
.ms-pagebreadcrumb a {
background-color: inherit;
color: inherit;
}
.ms-PartSpacingHorizontal {
width: 0px !important;
}
.ms-MenuUIPopupBody table {
color: #4C4C4C;
}
/* Override .ms-WPBody rules from core.css so that content within Web Parts
* (e.g. a Content Editor Web Part) appears similar to other text on the page
* (for example, as defined in the CSS rules for <body>) */
.ms-WPBody {
font-family: inherit;
font-size: inherit;
}
.ms-WPBody a:link, .ms-WPBody a:visited,
.ms-WPBody a:hover, .ms-WPBody a:active {
color: #003399;
}
.ms-WPBody td {
font-size: inherit;
font-family: inherit;
}
.ms-WPBody span {
font-size: inherit;
}
.ms-WPHeader td {
border-bottom: 1px solid #303435;
}
.ms-WPTitle {
color: #303435;
font-family: inherit;
}
As noted in the comments above, these CSS rules are used to override rules specified in the out-of-the-box SharePoint CSS files.
Returning to the contents of the master page, the following element is used to encapsulate all of the page content:
<div class="container_12">
The container_12 class refers to the 12-column template provided by the 960 Grid System.
Next comes the masthead, which contains the company logo, "welcome" area, and page editing toolbar (i.e. <PublishingConsole:Console runat="server" />). The main content for the page (i.e. <asp:ContentPlaceHolder ID="PlaceHolderMain" runat="server" />) comes after the masthead.
Finally, the master page includes a hidden panel that encapsulates all of the various content placeholders that are required by SharePoint, but should not be rendered on the page. As you can see, there are a number of placeholders in MOSS 2007 that are unfortunately used strictly for presentational markup (e.g. PlaceHolderNavSpacer and PlaceHolderBodyLeftBorder). When implementing a site based on Web standards, you should avoid these like the H1N1 virus. [For more information on the content placeholders specified in MOSS 2007, refer to my previous blog post (MOSS 2007 Master Page Comparison).]
This is getting to be a very long post, and there's still much I want to cover. Therefore, I'll tack "Part 1" onto the title, publish it, and get on with more fun things on a Saturday afternoon.
Don't fret, I'll cover many more details of Web standards design and SharePoint in the very near future.
|
-
Yesterday I continued building upon part 1 in a series of posts regarding the use of Silverlight in an Internet-facing customer portal built on Microsoft Office SharePoint Server (MOSS) 2007.
As I mentioned in the previous posts, the Silverlight application is hosted inside of the SharePoint site by a user control (ServiceWheel.ascx) which itself is hosted within a generic User Control Web Part (similar to SmartPart for SharePoint).
I also mentioned that the user control originally contained the following code:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="100%" height="100%">
<param name="source" value="_Layouts/Fabrikam/Wheel.xap" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40624.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
In addition to the two problems with this code that I covered in my previous post, I mentioned there was another issue with the original declaration of the <object> element for the Silverlight control.
Here's a partial screenshot of the home page of the portal, after clicking the Site Actions menu and then clicking Edit Page.
Figure 1: No menus open
While the above screenshot doesn't illustrate any problem, the caption gives you a hint as to what's coming next.
The following screenshot shows what happens when you click the Page menu.
Figure 2: Page menu items obscured by Silverlight control
Good luck trying to click the Delete Page, Add Web Parts, and Modify Web Parts menu items!
Note that menu items on the Workflow and Tools menus are similarly obscured.
Fortunately, once I discovered this problem, it didn't take long to find a solution. The trick is to set the Windowless property to true in the <object> element, as illustrated in the following screenshot:
Figure 3: Page menu items no longer obscured by windowless Silverlight control
Here's a comment from Karl Erickson on his blog post entitled "Limitations of Windowless mode for Silverlight":
Windowless mode is the only way to create user interfaces that blend HTML and Silverlight, regardless of which one is on top. Without Windowless mode, the Silverlight plug-in has its own window, which is always on top, and cannot blend in HTML UI from underneath.
The latest version of the markup in the user control that hosts our Silverlight application is shown below:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="380px" height="410px" onFocus="this.style.outline='none';">
<param name="source" value="<%= serviceWheelPackageUrl %>" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40624.0" />
<param name="autoUpgrade" value="true" />
<param name="windowless" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="/_layouts/Images/Fabrikam/InstallSilverlight.png" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
|
-
This is a continuation of yesterday's post detailing some lessons learned while integrating a Silverlight application into an Internet-facing customer service portal built on Microsoft Office SharePoint Server (MOSS) 2007.
As I mentioned in the previous post, the Fabrikam.Portal.Web.ServiceWheel project is the Silverlight application (a XAP file) which is deployed via a SharePoint Web Solution Package (WSP). The WSP is generated by the Fabrikam.Portal.Web project. The Silverlight application is hosted inside of the SharePoint site by a user control (ServiceWheel.ascx) which itself is hosted within a generic User Control Web Part (similar to SmartPart for SharePoint).
I also mentioned that the user control originally contained the following code:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="100%" height="100%">
<param name="source" value="_Layouts/Fabrikam/Wheel.xap" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40624.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
There are two problems with this code.
The first is that the Silverlight XAP file is served from the _layouts folder by specifying a simple path to the XAP file. This works great until you need to make a change to the Silverlight application -- for example, to fix a bug or implement an enhancement.
Regardless of whether or not you enable the BlobCache in SharePoint (and you should always enable it on your TEST and PROD environments), content served from _layouts includes a Cache-Control HTTP header that specifies the content should be cached by the client (max-age=31536000). Using IIS Manager, you can verify that SharePoint enables the setting on _layouts to expire Web content after 365 days (31,536,000 seconds).
This is actually a very good thing. For example, you certainly wouldn't want clients attempting to download the out-of-the-box SharePoint core.js file on each and every page request -- even if all the Web server does is send a small HTTP 304 (Not Modified) response. Likewise, you really don't want them requesting your Silverlight XAP file on every request.
Fortunately, there's an easy way to specify the version of a file in the URL using the (rather poorly documented) SPUtility.MakeBrowserCacheSafeLayoutsUrl method. This method actually specifies a hash of the file -- not a version -- but you get the point. This is covered in more detail in one of Stefan Goßner's blog posts.
To ensure that clients always have the latest version of the Silverlight XAP file, I modified the user control that hosts the Silverlight application as follows:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="380px" height="410px" onFocus="this.style.outline='none';">
<param name="source" value="<%= serviceWheelPackageUrl %>" />
...
</object>
The serviceWheelPackageUrl variable is defined and set in the code-behind for the ASCX file:
public partial class ServiceWheel : System.Web.UI.UserControl
{
protected string serviceWheelPackageUrl;
protected void Page_Load(
object sender,
EventArgs e)
{
serviceWheelPackageUrl = SPUtility.MakeBrowserCacheSafeLayoutsUrl(
"Fabrikam/Fabrikam.Portal.Web.ServiceWheel.xap",
false);
...
}
...
}
Consequently, whenever a page is requested with the Silverlight control the generated URL is something like:
/_layouts/Fabrikam/Fabrikam.Portal.Web.ServiceWheel.xap?rev=H3EKfK%2FcQ5VV%2FpRcJ5MzwA%3D%3D
Note that I had previously changed the name of the XAP file generated by the Fabrikam.Portal.Web.ServiceWheel project from Wheel.xap (specified by the original Silverlight developer) to Fabrikam.Portal.Web.ServiceWheel.xap. Personally, I always like to see fully-qualified names used for assemblies, WSPs, etc. Assuming you begin your namespaces with your company name -- and you always should -- this makes it much easier to quickly identify "your stuff" amidst a bunch of "other stuff."
I mentioned before that there were two problems with the original HTML <object> tag shown above. Actually there are more than two, but for the sake of keeping this post reasonably short, I'll only cover two of them here.
The second problem is a rather obscure issue in the <img> element:
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
The intent of this markup is to show an image link to "Get Microsoft Silveright" whenever Silverlight is not installed. It seems relatively simple, doesn't it?
The problem is that Firefox attempts to download the image (at least in version 3.5), regardless of whether or not Silverlight is installed (unlike Internet Explorer, which ignores the image when Silverlight is installed). This typically doesn't pose a problem except for the fact that this site is an Internet-facing customer service portal which is configured to use Forms-Based Authentication. Consequently we seamlessly switch from HTTP to HTTPS when showing the login page.
Firefox subsequently complains that the page with the Silverlight control contains a mix of secured and unsecured content. In other words, the status bar shows a small lock icon with a bright red warning symbol, along with the following tooltip:
Warning: Contains unauthenticated content
To avoid this issue in Firefox, you might be tempted to simply change the image source to specify https://go.microsoft.com/fwlink/?LinkId=108181. However, if (like we did) you fire up Fiddler and request this URL, you will find the response to be an HTTP 302 (redirect) with the following header:
Location: http://silverlight.dlservice.microsoft.com/download/d/2/9/d29e5571-4b68-4d95-b43a-4e81ba178455/2.0/ENU/InstallSilverlight.png
In other words, while the initial request for the image is HTTPS, the subsequent request will be HTTP. [Don't ask me to explain or make any sense of why go.microsoft.com does this. It seems like a bug, in my opinion.]
In order to avoid the switch from HTTPS to HTTP (and thus potentially any warnings in Firefox), we decided to simply host the InstallSilverlight.png image locally on our SharePoint servers (deployed via Fabrikam.Portal.Web.wsp):
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="380px" height="410px" onFocus="this.style.outline='none';">
...
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="/_layouts/Images/Fabrikam/InstallSilverlight.png" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
In hindsight, I suppose for consistency we could have used the SPUtility.MakeBrowserCacheSafeLayoutsUrl method for the "Get Microsoft Silverlight" image, but honestly, I don't think there is sufficient justification in this scenario.
So, there you have it, two more important lessons learned while integrating Silverlight into SharePoint.
Stay tuned, there's more to come! Same bat time, same bat channel ;-)
|
-
The November 2008 edition of MSDN magazine contained the following article:
Light Up SharePoint With Silverlight 2 Web Parts
While the article provides a good introduction to integrating a Silverlight application into a SharePoint site, it definitely left me wanting more.
We've been using Silverlight on my current project for almost four months now, and we've definitely learned a few things the hard way when it comes to Silverlight and SharePoint. Note that this is an Internet-facing customer service portal built on Microsoft Office SharePoint Server (MOSS) 2007.
It's also worth noting that I didn't develop the original Silverlight application we are using on the site, but I've made a number of changes to it in order to resolve a few issues and improve the overall implementation.
Before diving into the issues and improvements, it is helpful to first understand the structure of the solution. If you've seen my walkthrough of the "DR.DADA" approach to SharePoint (or if you've worked with me on any SharePoint projects), then the following solution structure should seem very familiar:
- Fabrikam.Portal.sln
- Fabrikam.Portal.CoreServices.csproj
- Fabrikam.Portal.CoreServices.DeveloperTests.csproj
- Fabrikam.Portal.Web.csproj
- Fabrikam.Portal.Web.DeveloperTests.csproj
- Fabrikam.Portal.Web.ServiceWheel.csproj
- Fabrikam.Portal.Web.ServiceWheel.Test.csproj
For the sake of brevity, I've intentionally omitted a number of other projects in the solution that are not relevant to this post.
The Fabrikam.Portal.CoreServices project is where we put code for core services, such as logging.
The Fabrikam.Portal.Web project contains the bulk of our solution and is comprised of various SharePoint features. For example, we have a feature for publishing layouts (including master pages and page layouts), another feature for our custom Web Parts, an Announcements feature, etc. This project contains a folder structure that mimics the hierarchy under %ProgramFiles%\Common Files\Microsoft Shared\web server extensions:
- 12
- TEMPLATE
- CONTROLTEMPLATES
- FEATURES
- ...
- LAYOUTS
The Fabrikam.Portal.Web.ServiceWheel project is the Silverlight application that provides a rather slick menu of the various features provided by the portal, while also promoting value differentiation and branding for the company.
The Fabrikam.Portal.Web.ServiceWheel.Test project is a simple ASP.NET application that allows developers to quickly and easily debug the Silverlight application outside of the SharePoint site. Making changes to the Silverlight application itself is much faster if you don't have to deploy it to your SharePoint site to debug or see the results of your changes.
Note that the output of the Fabrikam.Portal.Web.ServiceWheel project is an XAP file, which is subsequently deployed via the WSP file generated by the Fabrikam.Portal.Web project. The Silverlight application is hosted inside of the SharePoint site by a user control (ServiceWheel.ascx) which itself is hosted within a generic User Control Web Part (similar to SmartPart for SharePoint).
One of the first issues that I discovered in the Silverlight piece was the way the original developer integrated the XAP file into the WSP file.
Originally, the user control contained the following code:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="100%" height="100%">
<param name="source" value="_Layouts/Fabrikam/Wheel.xap" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40624.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
Notice that the XAP file is deployed to the _layouts folder -- and in accordance with best practices -- placed under a new folder corresponding to the company name (to isolate it from the Microsoft files installed by SharePoint). So far, so good.
In order to make the XAP file available to the project that creates the WSP, the Silverlight developer simply changed the output path in the Fabrikam.Portal.Web.ServiceWheel project to be:
..\..\..\Portal\Web\12\TEMPLATE\LAYOUTS\Fabrikam\
The DDF file (wsp_structure.ddf) used to create the WSP was updated to include the XAP file...
.Set DestinationDir=Layouts\Fabrikam
...
..\..\12\TEMPLATE\Layouts\Fabrikam\Wheel.xap
...and the manifest.xml file was updated to deploy the XAP file from the WSP:
<TemplateFiles>
...
<TemplateFile Location="Layouts\Fabrikam\Wheel.xap" />
</TemplateFiles>
Note that the developer also had to manually configure the build order to ensure the Silverlight project gets compiled before the Fabrikam.Portal.Web project -- or else the build could potentially break (if the XAP file was not found in the expected location because it had never been built before) or, even worse, the WSP could include an old version of the XAP file.
While everything appeared to work as expected, there was a fundamental problem with this approach. The developer only changed the output path for the Debug configuration of the Fabrikam.Portal.Web.ServiceWheel project. In other words, when it came time to create our Release builds, the build would either break (if the Debug build hadn't copied the XAP file to the expected location yet) or the Release build would include the Debug build of the XAP file in the Release build of the WSP.
While it probably isn't the worst thing to include a Debug version of an assembly in a Release build, it's definitely not a best practice. However, more importantly, changing the output path in order to copy the XAP file into the expected location is not compatible with Team Foundation Build.
As I noted in a previous post:
The problem with relative paths is that Team Foundation Build uses a different folder structure when compiling your projects.
In other words, whenever you find yourself about to specify ".." in the output path inside Visual Studio for a project, stop what you're doing, think about my blog posts, take a deep breath, and find another solution to the problem you are trying to solve.
In order to resolve the issues around integrating the Silverlight XAP file into the WSP, I made the following changes:
- Reverted the output path for the Debug configuration of the Fabrikam.Portal.Web.ServiceWheel project to the default (i.e. Bin\Debug instead of ..\..\..\Portal\Web\12\TEMPLATE\LAYOUTS\Fabrikam\).
- Added a reference from the Fabrikam.Portal.Web project to the Fabrikam.Portal.Web.ServiceWheel project. Note that while this is not explicitly required by the code within the Fabrikam.Portal.Web project, there really is a dependency between the two projects. Establishing this reference ensures the projects are built in the correct order (without having to manually tweak the build order of the projects in the solution).
- Modified the Fabrikam.Portal.Web project (by unloading the project and then editing the MSBuild file directly) to include the following:
<PropertyGroup>
<!-- Add the file extension for Silverlight application packages (.xap) to
the list of extensions that reference resolution considers when looking for
files related to resolved references (i.e. project references). This ensures
that .xap files are copied to the output folder for this project and
subsequently added to the SharePoint Web solution package (WSP). -->
<AllowedReferenceRelatedFileExtensions>
$(AllowedReferenceRelatedFileExtensions);
.xap
</AllowedReferenceRelatedFileExtensions>
</PropertyGroup>
This -- along with the project reference mentioned above -- ensures that the WSP generated by the Fabrikam.Portal.Web project is recompiled, even if the only thing that changed since the last build is something the Silverlight project.
I can see that this post is already turning out to be much longer than I originally anticipated, so I'm going to append "Part 1" to the title and detail some of the other gotchas I've found with Silverlight and SharePoint in a subsequent post.
Note that in the time since I originally started working with Silverlight and SharePoint, I've found other resources in addition to the MSDN article, such as the Silverlight Blueprint for SharePoint. However, from what I've seen so far (which, honestly, is just a cursory examination) most of these still seem to lack much depth.
Stay tuned and I promise I'll cover some more of the "juicy stuff" ;-)
|
-
In a previous post, I provided sample "DR.DADA" scripts that I use for deploying solutions based on Microsoft Office SharePoint Server (MOSS) 2007.
If you've read that post, you might recall seeing the following lines in, for example, the Deploy Solutions.cmd script:
REM Sometimes it is necessary to force the deployment to circumvent errors REM set FORCE_OPTION=-force
The FORCE_OPTION environment variable is subsequently included in the line that invokes StsAdm.exe:
%SPDIR%\bin\stsadm.exe -o deploysolution -name "%SOLUTION_NAME%.wsp" -url %FABRIKAM_PORTAL_URL% %DEPLOY_METHOD% -allowGacDeployment %FORCE_OPTION%
What's all this nonsense about forcing the deployment to "circumvent errors"? Yes, it's admittedly a hack (although for some reason when I originally created these scripts years ago, I didn't label it as such in the comment).
To understand the reason for specifying the "-force" option, consider the following example where the deployment failed:
C:\NotBackedUp\Fabrikam\Builds\1.0.39.0\Portal\DeploymentFiles\Scripts>"Deploy Solutions.cmd" 23:46 - Deploying solution: Fabrikam.Portal.StsAdm.Commands... Deploying Fabrikam.Portal.StsAdm.Commands...
Timer job successfully created.
Executing . Operation completed successfully.
Done 23:46 - Deploying solution: Fabrikam.Portal.Web... Deploying Fabrikam.Portal.Web on http://fabrikam-test...
Timer job successfully created.
Executing . Executing solution-deployment-fabrikam.portal.web.wsp-0. The solution-deployment-fabrikam.portal.web.wsp-0 job completed successfully, but could not be properly cleaned up. This job may execute again on this server. Operation completed successfully.
Note that the top-level Deploy Solutions.cmd script simply calls each Deploy Solution.cmd script for the various WSPs. For this particular project, we have a number of custom StsAdm.exe commands, in addition to a "Portal.Web" solution that contains all of our various features.
You might be wondering why this failed, since the output clearly states "Operation completed successfully" for both WSPs.
Browsing to the Solution Management page in SharePoint Central Administration, the Status column for fabrikam.portal.web.wsp showed Error. Clicking the link to view more details showed an error stating that the solution is already deployed. Well, duh, that's because the timer job that was created to deploy fabrikam.portal.web.wsp ran twice!
If the timer job had been "properly cleaned up" then everything would be fine.
Note that this problem doesn't occur each and every time the solutions are deployed. Unfortunately, it's one of those sporadic problems that tend to cause much heartburn for anyone who works in the world of software (think deadlocks in SQL Server or race conditions in a multithreaded application).
Also note that the specified WSP was actually deployed, which means the features can be subsequently activated (i.e. the second "A" in the "DR.DADA" process). However, I never like to leave the Solution Management page showing any items with Error status (even if the error is benign, like this scenario).
Therefore, whenever I encounter this error, I simply set the FORCE_OPTION environment variable and then redeploy the solutions:
C:\NotBackedUp\Fabrikam\Builds\1.0.39.0\Portal\DeploymentFiles\Scripts>set FORCE_OPTION=-force C:\NotBackedUp\Fabrikam\Builds\1.0.39.0\Portal\DeploymentFiles\Scripts>"Retract Solutions.cmd" ... C:\NotBackedUp\Fabrikam\Builds\1.0.39.0\Portal\DeploymentFiles\Scripts>"Deploy Solutions.cmd" ...
Fortunately, when the "-force" option is specified, even if the deployment timer job isn't properly cleaned up -- in other words, it isn't deleted -- no error occurs when the solution is deployed the second time, and the Status column on the Solution Management page shows Deployed.
If you find this problem occurs frequently in your environment, you might consider always specifying the "-force" option when deploying SharePoint solutions (which is what we recently did on my latest project).
|
-
This week I am wrapping up the third sprint (a.k.a. iteration or milestone) on my current Microsoft Office SharePoint Server (MOSS) 2007 project. Although, honestly, I wasn't involved all that much in Sprint-3, since I was on vacation for the vast majority of the iteration.
One of the surprises that I discovered upon my return was that the size of our builds had increased substantially since the previous iteration.
Here's a summary of the various builds for the iterations so far:
- Sprint-1: 13.4 MB uncompressed (3.2 MB compressed)
- Sprint-2: 79 MB uncompressed (18 MB compressed)
- Sprint-3: 127 MB uncompressed (48 MB compressed)
As you can see, while the size of the Sprint-1 build was rather paltry, the Sprint-3 build was almost 10 times larger (uncompressed). The compressed version is nearly 16 times larger.
Not suprisingly, I refer to this issue as "build bloat."
This is painfully apparent when copying a build across the Internet (for example, across the globe to our Test environment in a datacenter in Singapore, or even merely across the country to our Production environment).
It really isn't surprising that the size of the builds for subsequent sprints are signficantly larger than the Sprint-1 build. After all, in Sprint-2, I introduced a "modal popup framework" based on the Microsoft AJAX Control Toolkit in order to show announcements to people browsing the site, and the AjaxControlToolkit.dll assembly is approximately 4 MB.
Similarly, for Sprint-3 we started using additional third-party controls for handling some of the more sophisticated user interface elements, and the Telerik.Web.UI.dll assembly is currently a whopping 14 MB.
At this point, you might be wondering how the addition of a 4 MB assembly and a 14 MB assembly increased the size of the build approximately 113 MB.
As I noted at the beginning of this post, this is a MOSS 2007 project. Consequently, we deploy our solution using Web Solution Packages (WSPs). Note that a WSP is really just a CAB file with a different file extension. Consequently, when we build our WSPs, we include not only our custom SharePoint features in the CAB file, but also any dependencies (e.g. referenced assemblies like AjaxControlToolkit.dll).
One solution to the problem of build bloat would be to simply remove the referenced assemblies and deploy those separately. However, I really don't like the idea of breaking the nice encapsulation of deploying a WSP and having the corresponding referenced assemblies automatically deployed as well. Sure, one could argue that deploying third-party controls like the AJAX Control Toolkit and the Telerik controls should be done similar to the .NET Framework itself (i.e. independent of our custom code), but my preference is to simplify the deployment -- from a Release Management perspective -- as much as possible.
In addition, it is also important to note that the build bloat isn't caused entirely by the UI control assemblies. As I've noted in the past, SharePoint has a bad habit of copying extraneous assemblies into your project.
In this particular case, Microsoft.Office.Server.Search.dll and Microsoft.SharePoint.Server.Search.dll are now included in each build -- even though we really don't want them to be (since we would never install or update these files as part of our deployment process for our solution).
Note that the build bloat is exacerbated by compiling both Debug and Release builds, as well as the fact that the default build configuration for Team Foundation Server (TFS) copies the files for any Web site projects to a separate folder (_PublishedWebsites).
When I started investigating the build bloat issue, I quickly discovered that there were actually five copies each of AjaxControlToolkit.dll and Telerik.Web.UI.dll.
To resolve the build bloat issue, I modified our TFSBuild.proj file to override the BeforeDropBuild target:
<Target Name="BeforeDropBuild">
<Message Importance="high"
Text="Removing extraneous files from the build..." />
<!-- We do not currently deploy anything from the _PublishedWebsites folders -->
<RemoveDir
Directories="$(BinariesRoot)\Debug\_PublishedWebsites;
$(BinariesRoot)\Release\_PublishedWebsites;" />
<!--
The following assemblies are either included in the WSPs
or else should not be included in the build
(e.g. Microsoft.Office.Server.Search.dll).
-->
<CreateItem
Include="$(BinariesRoot)\**\AjaxControlToolkit.dll;
$(BinariesRoot)\**\Microsoft.Office.Server.Search.dll;
$(BinariesRoot)\**\Microsoft.SharePoint.Server.Search.dll;
$(BinariesRoot)\**\Telerik.Web.UI.dll" >
<Output TaskParameter="Include" ItemName="FilesToDelete"/>
</CreateItem>
<Delete Files="@(FilesToDelete)" />
</Target>
Note that by default, any Web projects in your solution are automatically copied to the _PublishedWebsites folder (so that you can do an XCOPY deployment of the Web sites, if you wish). However, since this solution is based on SharePoint, we obviously are not deploying the Web site in that way. Consequently, I chose to remove those folders completely.
Next, I recursively delete any extraneous assemblies (either because they are included in a WSP or because we would never install them from the build).
After overriding the BeforeDropBuild target, the Sprint-3 build is now 28 MB uncompressed (17 MB compressed) -- compared to 127 MB uncompressed (48 MB compressed) before.
Buh-bye, build bloat!
|
-
We recently encountered a bug when trying to access a SharePoint site configured with Forms-Based Authentication from Internet Explorer.
The root site is restricted to authenticated users, whereas the /Public site is configured to allow anonymous users. In other words, when users attempt to browse to the root site (/), they should automatically be redirected to /Public/Pages/default.aspx (which contains a login form).
Everything was working great for awhile, but then we discovered that certain clients would encounter HTTP 403 (Forbidden) errors when attempting to browse to the root site before logging in. In other words, instead of redirecting to the /Public site as expected, IE would simply display the HTTP 403 error page (which, at first glance, actually makes it seem like the site is down).
We observed the site worked just fine from the same clients when using Firefox, and it only occurred on a couple of laptops when using IE8.
We also noticed that we could workaround the problem by explicitly browsing to the /Public site instead of relying on the redirect -- hence we "punted" the issue for a short period, hoping that the issue could only be reproduced on our Microsoft laptops.
At first we suspected the problem was due to specific IE options, but resetting the IE configuration didn't resolve the issue.
Next, we suspected a specific version of IE8, since we noticed it happened on Windows 7 but not on Windows Server 2008.
However, then we discovered that it worked on one laptop but not another -- even though both laptops were running Windows 7 (x64) and had the exact same version of Internet Explorer.
Fortunately, Ranjiv Sharma -- one of my colleagues on the project -- discovered the following blog post:
403 Forbidden and Forms Authentication with MOSS. 2009-05-21.
It turns out the problem is caused by the Microsoft Office Live Add-in 1.3. Once you remove the add-in, close all instances of Internet Explorer, and restart IE, the seamless redirect works as expected.
Update (2010-01-15)
This is a known bug:
You cannot view a forms-based authentication Windows SharePoint Services 3.0 site if you have Office Live Update 1.2 for Microsoft Office Live Workspace installed http://support.microsoft.com/kb/972535
According to the KB article, it is fixed in the June 2009 Cumulative Update (CU). From my experience, it also appears to be fixed by installing the 1.4 of the Microsoft Office Live Add-in.
Note that I tried reinstalling the Office Live Add-in on my VM to try to repro the error again prior to installing the June 2009 CU. Unfortunately, I couldn’t find the 1.3 version available for download anymore from microsoft.com (the download page is no longer available). When I downloaded and installed the 1.4 version, I was unable to repro the error when browsing locally from my VM.
I subsequently downloaded the June 2009 CU and proceeded to install it on my local VM (since I could still test with the 1.3 version of the Office Live Add-in from my laptop). Unfortunately, I could only install the WSS part of this CU – not the MOSS part. (The MOSS patch is giving me the dreaded “expected version of the product was not found on the system” error.)
However, even with just the WSS portion of the June 2009 CU, I am no longer seeing the error when browsing to my local VM from my laptop.
|
-
First of all, Happy New Year!
This morning I'm back from a not-so-relaxing four weeks off -- although I have to admit, there's something quite nice about putting technology aside for a few weeks and laying travertine and building cabinets instead (I am remodeling our master bathroom). [I've done several tile projects in the past, but working with natural stone is definitely a very different experience. It makes using ceramic and porcelain seem somewhat trivial by comparison.]
Since the "gears" are a little rusty this morning, I thought I would start the new year off with a blog post that, while not very deep from a technical perspective, should nevertheless prove helpful to people like me.
Like many of you out there, I receive a lot of email on a daily basis. Now imagine how quickly the messages pile up when returning from a nice long vacation! I'm sure many of you can relate to the deluge of email in your inbox.
As a consultant, I find the key to effectively managing my inbox is separating the important messages from the lower priority items. For example, email specific to the particular project I am working on at any given time is definitely at the top of the "important" list, whereas the numerous messages that I receive simply by being a member of a DL (distribution list) are lower priority.
Most of you are probably already using the E-mail Rules feature in Outlook to help manage your inboxes. If not, then open Outlook, and on the Tools menu, click Rules and Alerts.
This post details the rules that I use for ensuring my Outlook Inbox folder only contains the really good stuff (and how everything else gets shuffled off to a different location).
It is also important to note that my Inbox folder is synchronized to my Windows Smartphone -- and obviously I don't want my phone "chirping" each and every time a receive a message is sent to one of the DLs that I belong to.
For reference purposes, here's what my Outlook folder looked like first thing this morning (again, note that this is not a typical snapshot since I just returned from a long vacation).
Figure 1: Outlook folders and archive structure
As you can see, I received literally thousands of email messages during my hiatus, but only 66 of them stayed in my Inbox folder.
Before explaining the various Outlook rules that I have configured, let's first cover the fundamentals of the various folders and archives illustrated above.
I have created folders under Inbox corresponding to various distribution lists. I have also created an "archive" -- which is really just a .pst file (Outlook Personal Folders) -- for any DL where I want to preserve some history. Periodically, I move the contents of the DL folders under Inbox into the corresponding archive files. [Note that I originally used a single archive file, but found that over time these folders could grow to be very large and subsequently split the archive into multiple files.]
I configure rules within Outlook to "preprocess" messages sent either directly to me or to DLs that I belong to.
Important
The reason for using DL folders under the Inbox folder -- instead of having the Outlook rules route directly to the archive (.pst) files -- is to ensure the rules are processed on the Exchange server.
If you configure an Outlook rule to move messages to a .pst file, then obviously that rule can only be processed on the client (where the .pst is available). While that alternate approach might work for some people, anyone with a smartphone configured to receive email will quickly find it to be rather useless.
I just inspected my email rules in Outlook and found that I am now up to 32 rules. Note that most of these rules follow the same basic structure:
Apply this rule after the message arrives sent to people or distribution list move it to the specified folder except if my name is in the To or Cc box stop processing more rules
For example, I have a rule named SharePoint 2010 discussions configured as follows:
Apply this rule after the message arrives sent to SharePoint 2010 Discussion move it to the SharePoint 2010 folder except if my name is in the To or Cc box stop processing more rules
As you can see from the previous screenshot, this rule automatically routed 1,837 messages sent to the SharePoint 2010 Discussion DL to the corresponding SharePoint 2010 folder.
I include the exception where my name is in the To or Cc box in order to ensure that any responses to messages that I send to a DL remain in the Inbox folder (since I am likely looking for help or providing assistance to someone else and therefore want to view the responses as soon as possible).
Of the 32 rules that I currently have configured, most are for DL processing. However, there are a couple of interesting variants.
If you use Visual Studio Team Foundation Server (TFS) then you probably know that you can subscribe to alerts -- such as when somebody checks in a changeset for a project. I find these alerts to be helpful at times (to quickly scan and review team progress) but obviously I don't want them clogging my inbox (or making my smartphone "chirp" incessantly). Consequently I configured my TFS Notifications rule as follows:
Apply this rule after the message arrives from xxx@microsoft.com move it to the TFS Notifications folder
Note that xxx@microsoft.com is the email address of the TFS service account (which I've replaced for obvious reasons).
Further down the list of email rules, I have one named High Priority Items:
Apply this rule after the message arrives from Ron Stutz or Scott Krebs or Sid Hayutin or Kit Ambrose or John MacCatherine stop processing more rules
While somewhat serving as a list of various managers I've had during my tenure with Microsoft, this rule is really used to ensure that email from these individuals doesn't mistakenly "slip through the cracks" and get processed by the very last rule.
Note that I also have a Project Mail rule configured for similar purposes:
Apply this rule after the message arrives sent to FrontierV3Dev or KPMG-COM Project Team or ... stop processing more rules
The very last rule that I have configured in Outlook is named Low Priority Items and is configured as follows:
Apply this rule after the message arrives move it to the Low Priority Items folder except if my name is in the To or Cc box
In other words, any email message that somehow makes its way into my inbox, but is neither processed by a previous email rule nor sent directly to me, is summarily routed to my Low Priority Items folder. While I strive to catch up on this folder every couple of days, in all honesty, it is sometimes a week or more before I read these messages.
While I'd like to take all of the credit for this system of managing email, I must confess that the concept of the "Low Priority" folder is one I picked up from someone else a long, long time ago. I wish I could remember whose Webcast that was, but it's been so long that all I remember is that it was someone from Microsoft. Heck, for all I know, he could have pilfered the concept from somebody else.
Lastly, I should also point out that another key element of my system for managing email is using Thread Compressor. This add-in is very useful for greatly reducing the number of email messages you need to wade through (especially when returning from vacation).
Here's my updated screenshot from Outlook this morning after running Thread Compressor:
Figure 2: Outlook folders after running Thread Compressor
Compare the numbers in this screenshot with the corresponding numbers in the previous screenshot -- or just trust me when I say it deleted 1,758 messages -- and you'll quickly see why I consider Thread Compressor to be an essential part of my toolbox.
I hope you find these tips useful for managing your ever-increasing amount of email.
|
-
Yesterday we encountered a bug while trying to deploy a new SQL Server database from a Visual Studio database project using the VSDBCMD.EXE utility, following the prescriptive guidance on MSDN:
How to: Prepare a Database for Deployment From a Command Prompt by Using VSDBCMD
According to this MSDN article, all you need to do is copy some files to the server running Microsoft SQL Server, and then run the VSDBCMD.EXE utility (specifying your .dbschema or .deploymanifest file).
Unfortunately, when we attempted to deploy our database to our Test environment, we encountered a NullReferenceException:
Object reference not set to an instance of an object.
Fortunately, it didn't take long to find Gert's recommendation to add the following registry key:
reg add HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0
The error occurred since Visual Studio has never been installed on the SQL Server in TEST (unlike our local development VMs).
It certainly would be nice if the above MSDN article were updated to note the bug and associated workaround.
Note
As pointed out in a comment by Ramkumar Perumal, Microsoft.SqlServer.BatchParser.dll does not exist in any of the specified folders. This should also be reflected in the MSDN article.
You will also find instructions to add the registry key -- as well as an explanation for why you more than likely already have Microsoft.SqlServer.BatchParser.dll installed on your SQL Server -- in the following blog post:
Deploying your Database Project without VSTSDB installed. 2009-02-21
|
-
In last night's post, I discussed the solution for an issue I encountered installing KB 970892 on one of my servers. Thankfully, this morning I confirmed the server no longer increments the Computers with Errors column in the daily report I receive from Windows Server Update Services (WSUS).
I should also mention that in order to get my WSUS report to come up completely clean, I had to decline the the following updates:
To be perfectly frank, I am actually rather disappointed that there doesn't seem to be any alternative to declining these updates altogether.
Up until I declined these two updates, my WSUS server complained each morning that four of my servers were not 100% patched (even though I have configured a Group Policy to automatically download and install updates every morning). The four servers that it complained about are all running Windows Server 2008.
Apparently there are known issues with these two updates -- both of which are for WSUS -- that cause them to be detected for install even on servers that are not running WSUS. [I suppose to be perfectly fair, the whole point of KB972493 is to allow you to more easily install WSUS by simply enabling a server role.]
The problem with KB 972493 started after installing Windows Server 2008 Service Pack 2 (SP2), because that includes the prerequisite patch (KB 940518).
My initial attempt at resolving the warnings in WSUS was to create a separate computer group under my existing Technology Toolbox group called WSUS Servers. I then moved COLOSSUS (my one and only WSUS server) to this new group and approved the aforementioned patches on that group alone.
Sadly, the current version of WSUS does not provide a way to decline updates for a specific group only. Instead the approval field is simply Not Approved for all computers groups except WSUS Servers.
Consequently, Windows Update would detect these two patches for install, but the subsequent install would refuse to actually apply the patches -- thus perpetually leaving all of my Windows Server 2008 SP2 servers that are not running WSUS with a "99%" Installed/Not Applicable Percentage (and also causing them to appear in the Computers Needing Updates columns in the daily WSUS report).
I really wish there were some other way to resolve the issue without declining the updates altogether -- but I certainly couldn't find one, and based on a number of support cases I read regarding similar issues, this seems to be the recommended solution.
|
-
For a little over a month, Windows Update was failing on one of the servers in the "Jameson Datacenter" (a.k.a. my home lab). Specifically, KB 970892 simply would not install on JUBILEE -- my Systems Center Operations Manager (SCOM) 2007 VM, that I use to monitor a number of physical and virtual machines.
Here's the first event I found regarding this error:
Log Name: Application Source: MsiInstaller Date: 10/27/2009 3:03:49 AM Event ID: 10005 Task Category: None Level: Error Keywords: Classic User: SYSTEM Computer: jubilee.corp.technologytoolbox.com Description: Product: Microsoft SQL Server 2005 Reporting Services (64-bit) -- Error 29528. The setup has encountered an unexpected error while Setting reporting service and share point exclusion path. The error is: Fatal error during installation.
This was quickly followed by another error in the event log:
Log Name: Application Source: MsiInstaller Date: 10/27/2009 3:04:55 AM Event ID: 1023 Task Category: None Level: Error Keywords: Classic User: SYSTEM Computer: jubilee.corp.technologytoolbox.com Description: Product: Microsoft SQL Server 2005 Reporting Services (64-bit) - Update 'GDR 4053 for SQL Server Reporting Services 2005 (64-bit) ENU (KB970892)' could not be installed. Error code 1603. Additional information is available in the log file C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Hotfix\RS9_Hotfix_KB970892_sqlrun_rs.msp.log.
Shortly thereafter, I started seeing the following error once every minute:
Log Name: Application Source: Report Server (MSSQLSERVER) Date: 10/27/2009 3:06:52 AM Event ID: 107 Task Category: Management Level: Error Keywords: Classic User: N/A Computer: jubilee.corp.technologytoolbox.com Description: Report Server (MSSQLSERVER) cannot connect to the report server database.
Since I have Windows Update configured to automatically download and install updates every morning, the patch attempted to install each day -- but failed each and every time.
I have to admit that I've spent a fair amount of time troubleshooting this error over the past month, but since it wasn't a blocking issue -- just a particularly irritating annoyance -- I kept putting it off. [Honestly, I rarely look at the SCOM reports and instead rely mostly on email notifications and the Operations Manager Console.]
Fortunately, I finally managed to determine the root cause tonight and resolve the issue.
After downloading and installing the standalone patch installation, I discovered the following in the installation log:
MSI (s) (F0:54) [21:09:49:565]: Invoking remote custom action. DLL: C:\Windows\Installer\MSIAC31.tmp, Entrypoint: Do_RSSetSharePointExclusionPath <Func Name='LaunchFunction'> Function=Do_RSSetSharePointExclusionPath <Func Name='GetCAContext'> <EndFunc Name='GetCAContext' Return='T' GetLastError='203'> Doing Action: Do_RSSetSharePointExclusionPath PerfTime Start: Do_RSSetSharePointExclusionPath : Mon Dec 07 21:09:49 2009 <Func Name='Do_RSSetSharePointExclusionPath'> The application pool /s already exists. Error Code: 0x80077374 (29556) Windows Error Text: Source File Name: sqlca\sqliisca.cpp Compiler Timestamp: Mon Nov 17 17:05:40 2008 Function Name: Do_RSSetSharePointExclusionPath Source Line Number: 914
As noted in KB 917826, there appears to be a known issue when Reporting Services is configured to run using a domain account. For JUBILEE, the ReportServer application pool was configured to run as TECHTOOLBOX\svc-mom-das (the SCOM data access service account). After changing the app pool to run as NetworkService instead, I ran the standalone install of KB 970892 and it completed successfully.
I then changed the app pool identity back to TECHTOOLBOX\svc-mom-das (since that appears to be how SCOM 2007 wants it configured) and verified that a couple of reports run successfully. Woohoo!
I'm crossing my fingers that tomorrow morning, Windows Update detects that KB 970892 is installed and no errors occur.
|
-
I've mentioned before the importance of using multiple "spindles" when working with large SQL Server databases.
Generally speaking, the recommendation is to use different RAID 1+0 arrays for data and log files -- and depending on the size and load of your database, you may also need to isolate data files on individual RAID 1+0 arrays.
For really large OLTP databases that require lots of IOPS, additional arrays might also be required to split the log files (although, honestly, I haven't worked on any projects where this has been necessary).
One of the configuration steps that I always recommend for any Production SQL Server environment is to configure the default database locations to ensure your "data I/O" is isolated from your "log I/O" (both of which should be isolated from other I/O -- specifically, the paging file).
For example, suppose you have the following disk configuration on your SQL Server cluster:
- C: (Local Disk) - RAID 1 array for operating system files, program files, and paging file
- D: (DATA01) - RAID 1+0 array for data files
- L: (LOG01) - RAID 1+0 array for log files
- Q: (Quorum)
In this simplistic example, the D:, L:, and Q: drives are LUNs on the backend SAN, whereas the C: drive is DAS (Direct Attached Storage). Hopefully, the Storage team responsible for managing your SAN provides dedicated physical drives for D: and L: -- although, honestly, I've seen several enterprise organizations that don't dedicate these drives and subsequently experience performance issues later on.
Given the above disk configuration, SQL Server Management Studio should be used to configure the default database locations similar to the following:
- Server Properties
- Database Settings
- Default database locations
- Data: D:\NotBackedUp\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\DATA
- Log: L:\NotBackedUp\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\DATA
You certainly don't have to use the NotBackedUp folder if you don't want to (that's just the standard that I've been using for years).
Once you've configured the default database locations, anytime you create a new database -- for example, when you create a new Web application or content database in Microsoft Office SharePoint Server (MOSS) 2007 -- the data and log files will be placed on the desired drives (i.e. D: and L:, respectively).
Of course, when creating MOSS 2007 databases for a Production (or Test) environment, you still need to resize them appropriately in order to avoid having them simply auto-grow from a very small initial size (in order to avoid fragmentation and optimize performance).
Suppose you need to determine the default database locations through SQL (perhaps because you are scripting the process to create your databases). If you fire up SQL Server Profiler and start a new trace (the default trace options are fine), you will find that SQL Server Management Studio executes the following SQL statements when viewing the Database Settings page in the Server Properties window:
declare @RegPathParams sysname
declare @Arg sysname
declare @Param sysname
declare @MasterPath nvarchar(512)
declare @LogPath nvarchar(512)
declare @ErrorLogPath nvarchar(512)
declare @n int
select @n=0
select @RegPathParams=N'Software\Microsoft\MSSQLServer\MSSQLServer'+'\Parameters'
select @Param='dummy'
while(not @Param is null)
begin
select @Param=null
select @Arg='SqlArg'+convert(nvarchar,@n)
exec master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE',
@RegPathParams,
@Arg,
@Param OUTPUT
if(@Param like '-d%')
begin
select @Param=substring(@Param, 3, 255)
select @MasterPath=substring(
@Param,
1,
len(@Param) - charindex('\', reverse(@Param)))
end
else if(@Param like '-l%')
begin
select @Param=substring(@Param, 3, 255)
select @LogPath=substring(
@Param,
1,
len(@Param) - charindex('\', reverse(@Param)))
end
else if(@Param like '-e%')
begin
select @Param=substring(@Param, 3, 255)
select @ErrorLogPath=substring(
@Param,
1,
len(@Param) - charindex('\', reverse(@Param)))
end
select @n=@n+1
end
print 'LogPath = ' + @LogPath
print 'MasterPath = ' + @MasterPath
Note that I truncated the actual SQL batch and added the print statements.
|
-
I encountered a rather nasty bug in Microsoft Office SharePoint Server (MOSS) 2007 yesterday that occurs when a custom field (i.e. site column) has the same name as an existing field. Note that this issue will also occur in Windows SharePoint Services (WSS) v3.
I initially created the following Fields.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field ID="{11F7E97D-6282-44b1-8222-2E6E377BCDFC}"
Name="AnnouncementEndDate"
Type="DateTime"
Format="DateOnly"
Group="Fabrikam Custom Columns"
DisplayName="Announcement End Date">
</Field>
<Field ID="{9D1F7E52-EFBF-431e-9331-7DCA31D9CB7A}"
Name="AnnouncementStartDate"
Type="DateTime"
Format="DateOnly"
Group="Fabrikam Custom Columns"
DisplayName="Announcement Start Date">
</Field>
</Elements>
Next, I created the ContentTypes.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType
ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390064DEA0F50FC8C147B0B6EA0636C4A7D400EF37EB6F40C54a21A3872B1E6CA5BC0A"
Name="Announcement Page"
Description="Announcement Page is a custom content type ..."
Group="Fabrikam Content Types">
<FieldRefs>
<FieldRef ID="{9D1F7E52-EFBF-431e-9331-7DCA31D9CB7A}" Name="AnnouncementStartDate" />
<FieldRef ID="{11F7E97D-6282-44b1-8222-2E6E377BCDFC}" Name="AnnouncementEndDate" />
</FieldRefs>
<DocumentTemplate TargetName="/_layouts/CreatePage.aspx" />
</ContentType>
</Elements>
Note
The really long ContentType ID above specifies that Announcement Page inherits from the out-of-the-box Welcome Page. I vaguely recall reading somebody's blog where he or she stated that you shouldn't inherit from the OOTB page types. However, I don't recall the justification -- if there even was one. I haven't encountered any problems with this approach and thus haven't seen any compelling reason to stop doing so.
Then I created a page layout for the new Announcement Page content type.
After building and deploying the corresponding WSP -- and activating the feature -- I verified that I could create a new Announcement Page and specify field values, including Announcement Start Date and Announcement End Date. Consequently I checked in my changes to TFS.
At that point, I started thinking that perhaps AnnouncementStartDate should simply be StartDate and AnnouncementEndDate should simply be EndDate (thinking that we might want to use these fields for more than just announcements at some point in the future). Consequently, I changed the Fields.xml file as follows:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field ID="{11F7E97D-6282-44b1-8222-2E6E377BCDFC}"
Name="EndDate"
Type="DateTime"
Format="DateOnly"
Group="Fabrikam Custom Columns"
DisplayName="End Date">
</Field>
<Field ID="{9D1F7E52-EFBF-431e-9331-7DCA31D9CB7A}"
Name="StartDate"
Type="DateTime"
Format="DateOnly"
Group="Fabrikam Custom Columns"
DisplayName="Start Date">
</Field>
</Elements>
I propagated the changes to the ContentTypes.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType
ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390064DEA0F50FC8C147B0B6EA0636C4A7D400EF37EB6F40C54a21A3872B1E6CA5BC0A"
Name="Announcement Page"
Description="Announcement Page is a custom content type ..."
Group="Fabrikam Content Types">
<FieldRefs>
<FieldRef ID="{9D1F7E52-EFBF-431e-9331-7DCA31D9CB7A}" Name="StartDate" />
<FieldRef ID="{11F7E97D-6282-44b1-8222-2E6E377BCDFC}" Name="EndDate" />
</FieldRefs>
<DocumentTemplate TargetName="/_layouts/CreatePage.aspx" />
</ContentType>
</Elements>
After rebuilding my Web application, adding and deploying the updated WSP, and activating the feature, I discovered that the Announcement Page content type did not have the Start Date and End Date fields.
Since there were no errors deploying the WSP or activating the feature, I was rather baffled by the issue. Then I discovered that by renaming AnnouncementStartDate to StartDate, I introduced a duplicate field name. Note the following field definition from the OOTB fieldswss.xml file:
<Field ID="{64cd368d-2f95-4bfc-a1f9-8d4324ecb007}"
Name="StartDate"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="StartDate"
Group="$Resources:Base_Columns"
Type="DateTime"
Format="DateOnly"
DisplayName="$Resources:core,Start_Date;"><!-- _locID@DisplayName="camlidT6" _locComment=" " -->
<Default>[today]</Default>
</Field>
Similarly, my custom EndDate field conflicted with the OOTB EndDate field.
My memory might be incorrect, but I vaguely recall getting an error a few years ago when trying to define a second field with the same name as an existing field. Obviously that was long before MOSS 2007 Service Pack 2 (which I am currently running) so it's possible the behavior has changed. [Honestly, while I could try to repro this using the MOSS 2007 RTM version, I'm not going to bother. It would take me about an hour to build out a vanilla MOSS 2007 RTM environment, and that is far longer than I am willing to invest in this issue. It's also possible that I am simply remembering a slightly different issue -- perhaps a duplicate content type name.]
Regardless of whether this is a new problem, or has been around since the original MOSS 2007 release isn't the point. What is important is that you get no warnings or errors when your custom feature mistakenly attempts to define new StartDate and EndDate fields.
Once I identified the crux of the issue, I decided to simply rip out my custom StartDate and EndDate fields and instead use the OOTB fields. After all, as my old mantra goes, always try to use something out-of-the-box instead of customization whenever possible.
In other words, I updated my ContentTypes.xml file to the following:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType
ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390064DEA0F50FC8C147B0B6EA0636C4A7D400EF37EB6F40C54a21A3872B1E6CA5BC0A"
Name="Announcement Page"
Description="Announcement Page is a custom content type ..."
Group="Fabrikam Content Types">
<FieldRefs>
<FieldRef ID="{64cd368d-2f95-4bfc-a1f9-8d4324ecb007}" Name="StartDate" />
<FieldRef ID="{2684F9F2-54BE-429f-BA06-76754FC056BF}" Name="EndDate" />
</FieldRefs>
<DocumentTemplate TargetName="/_layouts/CreatePage.aspx" />
</ContentType>
</Elements>
Unfortunately, I then discovered that the OOTB EndDate field does not parallel the definition of the OOTB StartDate field:
<Field ID="{2684F9F2-54BE-429f-BA06-76754FC056BF}"
Name="EndDate"
Type="DateTime"
DisplayName="$Resources:core,End_Time;"
Format="DateTime"
FromBaseType="TRUE"
Group="_Hidden"
SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
StaticName="EndDate" ><!--DisplayName=$Resources:camlid3;-->
<FieldRefs>
<FieldRef ID="{7D95D1F4-F5FD-4a70-90CD-B35ABC9B5BC8}" Name="fAllDayEvent" RefType="AllDayEvent" />
</FieldRefs>
<Default>[today]</Default>
</Field>
Notice that with EndDate, Format="DateTime" whereas with StartDate, Format="DateOnly". Also note that the display name (in English - U.S.) is "End Time" -- instead of the expected "End Date". So much for consistency ;-)
Lastly, note that the OOTB fieldswss.xml also contains the following:
<Field ID="{8A121252-85A9-443d-8217-A1B57020FADF}"
Name="_EndDate"
Group="$Resources:Base_Columns"
Type="DateTime"
DisplayName="$Resources:End_Date"
Format="DateTime"
SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
StaticName="_EndDate" >
<Default>[today]</Default>
</Field>
Unfortunately, this field definition doesn't specify Format="DateOnly" either -- otherwise I would have just used it instead.
Thus I reverted back to using my original AnnouncementStartDate and AnnouncementEndDate custom fields. It's not ideal, but it works.
|
-
A few weeks ago I started experiencing an incredibly frustrating problem on my laptop. When I clicked the mouse, instead of responding with a single-click (e.g. to select an item), it acted as if I double-clicked the mouse button.
At first I thought that I was simply losing control of my motor reflexes (in other words, I was mistakenly double-clicking when I meant to single-click). However, over time I began to experience the issue more and more frequently -- almost to the point where it was endangering my sanity. No matter how carefully I clicked the mouse, a double-click response would occur.
You can imagine how frustrating this would be when you are trying to drag-and-drop mail messages from your inbox to another folder, or even worse, when you click the delete button in the Outlook toolbar and suddenly two messages are deleted instead of just the one you intended.
It got so bad that while I was performing a demo for the CIO on my current project, I had to use the integrated touchpad on the laptop instead of the external mouse, for fear of embarrassing myself. [If you've ever seen my laptop, you would know that I actually cover the touchpad (with a slightly trimmed business card) in order to avoid mistakenly triggering a mouse click while I am typing. I considered disabling it altogether, but there are rare occasions when I actually use it -- since long ago my daughter tore off the little rubber "cap" on the pointer device in the middle of the keyboard. There was just something about that little "blue dot" that she couldn't resist.]
I did a quick Internet search to see if anyone else had experienced similar issues and I discovered that I certainly wasn't the only one to endure this nightmare. Several sources -- including KB 266738 -- suggested that the problem was due to the mouse drivers being defective. Some people also stated that the IntelliPoint software was "crap" and should be avoided altogether. Hmmm...that didn't seem right, because the software was working fine for me for several years.
Nevertheless, I tried removing the IntelliPoint software altogether, but the problem still occurred. I then reinstalled IntelliPoint, desperately hoping the problem would miraculously go away.
It didn't.
At that point, I trekked down to the basement and dug out an old wireless mouse that I used on my desktop before switching to my current trackball. After inserting a couple of fresh AA batteries, I plugged the receiver for the wireless mouse into my laptop and, voilà...problem solved!
I've been using the replacement mouse for almost two weeks now and have yet to experience even one unintended double-click.
So, the lesson here is that while I love my IntelliMouse, it certainly has a strange way of failing after years of use and thousands (or perhaps tens of thousands) of clicks.
Hopefully this post saves at least one person the days of anguish that I experienced in Double-Click Purgatory.
|
-
As I noted in my previous post, I recently discovered that my approach for building Web Solution Packages (WSPs) in Microsoft Office SharePoint Server (MOSS) 2007 isn't compatible with Team Foundation Build.
I'm actually a little embarrassed to say this, but when I created the original "DR.DADA" approach for MOSS 2007 development back on the Agilent Technologies project, we were using Visual SourceSafe -- not Team Foundation Server (TFS) -- and a "manual" build process.
I'd used VSS and automated builds on other projects before (using NAnt), but never got around to automating our MOSS 2007 builds on the Agilent project because, honestly, there were just too many other higher priority items. Besides, each build only required a couple of minutes of actual human effort because most of the build was scripted.
Still, an automated daily build (and deployment to DEV) is a really, really good thing to have.
I've been fortunate to be on a few projects since then that have leveraged TFS.
However, up until about a month ago, I hadn't used Team Foundation Build (outside of the Jameson Datacenter, of course) due to the fact that we are leveraging the extranet TFS instance hosted by Microsoft.
Note that Microsoft IT makes it very easy for us to provision new TFS projects on either the extranet or one of several internal TFS instances. Configuring builds using Team Foundation Build on one of the intranet TFS instances is very easy (from what I hear), but I strongly prefer working off the extranet TFS instance because then I don't have to VPN into CorpNet in order to have access to source control.
However, choosing the extranet TFS instance also means we can't configure builds using the out-of-the-box functionality in Team Foundation Server (at least not without setting up a build server on the extranet). Fortunately, I've found a way to schedule "manual" builds that look a lot like automated builds performed using Team Foundation Build.
So, if you are building SharePoint WSPs -- regardless of whether you use the real Team Foundation Build or my "imitation Team Foundation Build" -- you need a way to build the WSPs without referring to referenced assemblies using relative paths.
As I first mentioned in my previous post, relative paths work just fine when compiling from within Visual Studio or using MSBuild from the command line. However, they don't work at all when queuing the builds through Team Foundation Build.
The problem with relative paths is that Team Foundation Build uses a different folder structure when compiling your projects. Specifically, it changes the output folder for all compiled items to be under a new Binaries folder -- not the location specified in the project settings within Visual Studio.
In other words, if you refer to a referenced assembly using something like:
..\..\..\CoreServices\bin\%BUILD_CONFIGURATION%\Fabrikam.Demo.CoreServices.dll
then you will find that this works just fine when building through Visual Studio -- or even when compiling using TFSBuild.proj from the command line (a.k.a. a "Desktop Build"). However, if you then queue the build through Team Foundation Server, you'll find your build fails because the referenced assembly was actually output to a different folder.
If you dive into the log file for the build, you will find that Team Foundation Build modifies the OutDir variable and sets it to something like:
C:\Users\svc-build\AppData\Local\Temp\Demo\Daily Build - Main\Binaries\Debug\
So the trick to building WSPs with Team Foundation Build is to leverage the OutDir variable instead of relying on relative paths to referenced assemblies.
Here is the updated DDF file based on my earlier sample:
;
; This ddf specifies the structure of the .wsp solution cab file.
;
; HACK: OPTION EXPLICIT cannot be used when specifying a variable with the /D option,
; otherwise MakeCAB aborts with an error similar to the following:
;
; ERROR: Option Explicit and variable not defined: OUT_DIR
;
;.OPTION EXPLICIT ; Generate errors for undefined variables
.Set CabinetNameTemplate=Fabrikam.Demo.Publishing.wsp
; The following variable must be set when calling MakeCAB (using the /D option)
;.Define OUT_DIR=
.Set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory
.Set CompressionType=MSZIP ; All files are compressed in cabinet files
.Set UniqueFiles=ON
.Set Cabinet=ON
.Set DiskDirectory1=%OUT_DIR%\Package
DeploymentFiles\PackageFiles\manifest.xml
.Set SourceDir=%OUT_DIR% ; Copy assemblies from %OUT_DIR% folder
Fabrikam.Demo.Publishing.dll
Fabrikam.Demo.CoreServices.dll
.Set SourceDir= ; Copy files relative to project folder
.Set DestinationDir=Fabrikam.Demo.Publishing.DefaultSiteConfiguration
DefaultSiteConfiguration\FeatureFiles\Feature.xml
.Set DestinationDir=Fabrikam.Demo.Publishing.Layouts
Layouts\FeatureFiles\Feature.xml
Layouts\FeatureFiles\ProvisionedFiles.xml
.Set DestinationDir=Fabrikam.Demo.Publishing.Layouts\MasterPages
Layouts\MasterPages\FabrikamMinimal.master
;.Set DestinationDir=Fabrikam.Demo.Publishing.Layouts\PageLayouts
;Layouts\PageLayouts\MinimalPage.aspx
.Set DestinationDir=Fabrikam.Demo.Publishing.Layouts\Images
Layouts\Images\FabrikamLogo_32x32.png
.Set DestinationDir=Fabrikam.Demo.Publishing.Layouts\Themes\Theme1
Layouts\Themes\Theme1\BreadcrumbBullet.gif
Layouts\Themes\Theme1\FauxColumn-Fixed-2Col.png
Layouts\Themes\Theme1\FauxColumn-Fixed-3Col.png
Layouts\Themes\Theme1\Fabrikam-Basic.css
Layouts\Themes\Theme1\Fabrikam-Core.css
Layouts\Themes\Theme1\Fabrikam-FixedLayout.css
Layouts\Themes\Theme1\Fabrikam-IE.css
Layouts\Themes\Theme1\Fabrikam-IE6.css
Layouts\Themes\Theme1\Fabrikam-QuirksMode.css
Layouts\Themes\Theme1\Tab-LeftSide.jpg
Layouts\Themes\Theme1\Tab-RightSide.jpg
.Set DestinationDir=ControlTemplates\Fabrikam\Demo\Publishing\Layouts
Layouts\Web\UI\WebControls\GlobalNavigation.ascx
Layouts\Web\UI\WebControls\StyleDeclarations.ascx
.Set DestinationDir=Layouts\Fabrikam
Layouts\MasterPages\FabrikamMinimal.master
Note how I've replaced the BUILD_CONFIGURATION variable with the OUT_DIR variable. Not surprisingly, the OUT_DIR variable in the DDF is specified similar to how BUILD_CONFIGURATION was previously specified when calling makecab.exe. However, unlike the build configuration the OutDir variable will likely contain spaces as well as a trailing slash (which makecab.exe apparently doesn't like). Therefore we must quote the OutDir variable and append with "." if a trailing slash is found.
Here is the corresponding update to the project file:
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
CreateSharePointSolutionPackage
</BuildDependsOn>
<QuotedOutDir>"$(OutDir)"</QuotedOutDir>
<QuotedOutDir Condition="HasTrailingSlash($(OutDir))">"$(OutDir)."</QuotedOutDir>
</PropertyGroup>
<Target Name="CreateSharePointSolutionPackage" Inputs="@(None);@(Content);$(OutDir)$(TargetFileName);" Outputs="$(ProjectDir)$(OutDir)Package\Fabrikam.Demo.Publishing.wsp">
<Message Text="Creating SharePoint solution package..." />
<Exec Command="makecab /D OUT_DIR=$(QuotedOutDir) /F "$(ProjectDir)DeploymentFiles\PackageFiles\wsp_structure.ddf"" />
</Target>
With these changes, the SharePoint WSP is successfully built regardless of whether it is compiled through Visual Studio, from the command line using MSBuild and the TFSBuild.proj file, or as an automated build using a Team Foundation Build server.
|
|
|
|