PAGES

20

Jan 12

Configuring log4net in a SharePoint Application



Configuring log4net in a SharePoint or ASP.NET application is pretty straightforward, but no matter how many times I do it, I always forget something small and waste time troubleshooting why logging isn’t working.

Here’s a guide to configuring logging using log4net in a SharePoint application. This post will cover:

  • Creating and deploying a log4net configuration file
  • Making the necessary web.config changes to support logging
log4net deployment is documented extensively so I’ll keep this at a high level.

Creating and Deploying a log4net Configuration File

It’s always best to put your log4net configuration in a dedicated config file, this way you get to keep your SharePoint web.config file clean and not have to deal with the SPWebConfigModification class.

The part I always forget is that when you have the log4net configuration in a separate file, you need to tell your assembly the name of the log4net configuration file. You do that by adding the following attribute to the AssemblyInfo class of the SharePoint project or class library where you have your logger.

According to the documentation from Apache, this attribute is read and processed after the first time you invoke log4net. Your logger will call LogManager.GetLogger and look for this attribute and then read the configuration information.

[assembly: log4net.Config.XmlConfigurator(
 ConfigFile = "log4Net.config", Watch = true)]

You can examine properties of the logger such as isDebugEnabled and isErrorEnabled to check if everything is working properly.

You can then use a SharePoint Timer job to deploy the log4net.config file to the root of your SharePoint web application.

In a Web Application scoped feature, you can run a SharePoint Timer job in the FeactureActivated handler:

var log4NetDeployJob = new log4NetConfigDeploymentJob(
    _jobName, webApplication);
var schedule = new SPOneTimeSchedule(DateTime.Now);
log4NetDeployJob.Schedule = schedule;
log4NetDeployJob.Update();
webApplication.JobDefinitions.Add(log4NetDeployJob);
webApplication.Update();
log4NetDeployJob.Rungetdate();

The Execute method of the Timer Job extracts the log4net.config file from where it was deployed with your feature and copies it to the virtual directory:

public override void Execute(Guid targetInstanceId)
{
    var webApp = this.Parent as SPWebApplication;
    foreach (KeyValuePair<SPUrlZone, SPIisSettings> setting in webApp.IisSettings)
    {
        var deployTo = setting.Value.Path.FullName + @"\log4Net.config";
        var filePath = SPUtility.GetGenericSetupPath(
            @"TEMPLATE\FEATURES\log4Net\Configuration\log4Net.config");
        File.Copy(filePath, deployTo, true);
    }

    base.Execute(targetInstanceId);
}

SPUtility.GetGenericSetupPath returns the full path to the 14 hive, and “TEMPLATE\FEATURES\log4Net\Configuration\log4Net.config” is the path to the log4Net.config file inside the feature.

Web.config Modifications

You now need to make some changes to the web application’s web.config file.

Add log4Net configuration section in configuration/configSections:

<section name="log4net"
  type="log4net.Config.Log4NetConfigurationSectionHandler, Log4net"/>

Add the following before the end of the configuration section:

<log4net configSource="log4Net.config" />

That’s it! Assuming you created a logger class in your application, you can start logging away. Check out the documentation from Apache for information about how to write a logger.

 

1 comment , permalink


15

Jan 12

SharePoint – Timing of Master Page JavaScript



Our base SharePoint 2010 Publishing master page calls a JavaScript function that does things such as:

  • Initialize jQuery plugins
  • Configure some top-navigation tweaks
  • Miscellaneous items such as setting the date in the copyright footer
  • Initialize Google Analytics

We call this function right before the end of the head section of the master page. The contents of the JavaScript function are enclosed in a jQuery $(document).ready block, meaning that the function will run after the DOM is ready.

A drawback to this approach is that JavaScript that manipulate elements on the screen only executes after the DOM is ready. This isn’t a great user experience because the user can see the “jumpiness” on screen as the script executes.

I ran this by our resident web ninja Jacob Gable and he suggested breaking up the JavaScript function into smaller chunks and calling those chunks directly after the HTML content that they affect; for example:

<div id="footer">
    <span id="footer-year"></span>&nbsp;All rights reserved.
    <script type="text/javascript">
        SetCopyrightDate();
    </script>
</div>

where SetCopyrightDate uses jQuery to set the date to the current year:

function SetCopyrightDate()
{
    var today = new Date();
    var yr = today.getFullYear();
    $("#footer-year").text("© " + yr);
}

This is pretty obvious in retrospect; your JavaScript can be sprinkled throughout the page as needed. People are scared away by this, wanting to have a super-clean master page.

However, in this case, pulling out some JavaScript functionality into separate functions and calling them from the master page does the trick.

The remaining JavaScript can stay in a global function and run in a $(document).ready block; for us, all this does now is initialize jQuery plugins we’re using throughout the site.

Comments Off , permalink


9

Dec 11

Adding Margins Dynamically to SharePoint Rich HTML Fields in a Publishing Page Layout



I often find that my SharePoint publishing page layouts contain several Rich HTML fields after one another, for example:

<asp:Content
  ContentPlaceholderID="PlaceHolderTopContentArea" runat="server">
	<div>
		<PublishingWebControls:RichHtmlField id="pageContent1"
		  FieldName="PageContent1" runat="server" />
	</div>

    <div>
		<PublishingWebControls:RichHtmlField id="pageContent2"
		  FieldName="PageContent2" runat="server" />
	</div>
</asp:Content>

Unless you explicitly set a margin between the div elements containing the instance of PublishingWebControls:RichHtmlField, they’re going to be scrunched together.

However, if you explicitly set a bottom margin on the containing div it will have a bottom margin whether or not there is actually content in the RichHTMLField.

jQuery to the rescue!

I use this snippet of JavaScript in my base master page to dynamically add a bottom margin to a RichHTMLField only when it has content:

$.each(["pageContent1", "pageContent2"],
	function (index, value) {
		if ($("div[id*='_" + value +
			"__ControlWrapper_RichHtmlField']").children()
            .length > 0)
		{
			$("div[id*='_" + value +
			  "__ControlWrapper_RichHtmlField']").parent()
			  .addClass("rich-text-field-margin");
		}
	});

The RichHTMLField is rendered as a div with an Id such as:

ctl00_ctl00_PlaceHolderMain_PlaceHolderTopContentArea_pageContent1__ControlWrapper_RichHtmlField

You can use a selector to find the element, check if it has any content, and then add a class to it that applies the margin.

Props to Clarity’s Dan Mueller for his jQuery kung fu!

Comments Off , permalink


7

Dec 11

Provisioning SharePoint Publishing Pages and Content



In addition to provisioning the structure of the SharePoint Publishing site as part of build and deployment, I also like to deploy pages within those sites and sometimes the content and web parts in them.

The Template Redirection Page

SharePoint provides a nice mechanism for doing this called the Template Redirection page. This is a page found in SiteTemplates\SPS\default.aspx:

<%@ Page Inherits="Microsoft.SharePoint.Publishing.TemplateRedirectionPage, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" %> 

<%@ Reference VirtualPath="~TemplatePageUrl" %>

<%@ Reference VirtualPath="~masterurl/custom.master" %>

You don’t have to understand what this page does in order to harness its power …

Creating a Feature to Provision Publishing Pages

Assuming that you have a Publishing content type and page layout already created, you can create a feature to provision pages as follows:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="Root-Pages"
    SetupPath="SiteTemplates\SPS"
    Url="$Resources:cmscore,List_Pages_UrlName;">
    <File Url="welcome.aspx"
     Path="default.aspx"
     Type="GhostableInLibrary">
      <Property Name="Title" Value="Welcome!"/>
      <Property Name="PublishingPageLayout"
        Value="~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx,
                 My Page Layout" />
      <Property Name="ContentType"
        Value="My Content Type" />
    </File>
  </Module>
</Elements>

The SetupPath attribute of the Module element refers to the Template Redirection page, and Url refers to the location in which the pages are created (the Pages library name).

You can see where you specify the Title, page layout to use, and corresponding content type.

Adding Content to the Provisioned Pages

It’s also useful to populate Html fields within the pages that are being provisioned.

That’s pretty simple to do using the following syntax:

<Property Name="PageContent1"
  Value="&lt;h1&gt;Page Not Found&lt;/h1&gt;&lt;p&gt;Sorry, that page doesn't exist.&lt;/p&gt;"/>

In this case, PageContent1 is an Html field on the underlying Publishing content type.

Also note that the Html in Value has to be encoded; you can use any Html encoder out there such as http://www.opinionatedgeek.com/dotnet/tools/htmlencode/encode.aspx.

I tried doing something more elegant such as the following but it didn’t work

<Property Name="PageContent1">
    <![CDATA[<h1>Page Not Found</h1><p>Sorry, that page doesn't exist.</p>]]>
</Property>

Activating the Provisioning Feature

These are all web-scoped features, so the pages are created in whatever SPWeb that a feature is activated on.

You want certain pages provisioned in certain webs, so you need to separate the pages into different modules and features and activate them only on the appropriate web.

I mark all these features as hidden and activate them using PowerShell on the appropriate SPWeb.

Setting Home Pages

If you’re using a site definition to create your sub-sites, you most likely have the site definition create a really simple home page that content authors can later populate with content.

You can include a new home page in the page provisioning process (call it welcome.aspx or something) and use the feature receiver to set it as the site’s homepage and delete the old (and blank) one.

This example assumes that welcome.aspx is being provisioned by the feature, and that you’d like to set it as the site’s homepage and rename it to default.aspx.

using (var web = properties.Feature.Parent as SPWeb)
{
    try
    {
        var publishingWeb = PublishingWeb.GetPublishingWeb(web);

        // Set the new default page
        var pageToDelete = publishingWeb.DefaultPage;
        publishingWeb.DefaultPage = publishingWeb.GetPublishingPage(
            String.Format("{0}/{1}/{2}",
                            publishingWeb.Url,
                            publishingWeb.PagesListName,
                            "welcome.aspx")).ListItem.File;
        publishingWeb.Update();

        // Delete the old default page
        pageToDelete.Delete();

        // Rename the new page to default.aspx
        // "FileLeafRef" is the internal field name for "Name"
        var defaultPage = publishingWeb.GetPublishingPage(
            String.Format("{0}/{1}/{2}",
                publishingWeb.Url,
                publishingWeb.PagesListName,
                "welcome.aspx"));

        defaultPage.ListItem.File.CheckOut();
        defaultPage.ListItem["FileLeafRef"] = "default.aspx";
        defaultPage.Update();

        defaultPage.ListItem.File.CheckIn("Check in default page.");
        defaultPage.ListItem.File.Publish("Publish.");
        defaultPage.ListItem.File.Approve("Approve.");

        publishingWeb.Close();
    }
    catch (Exception)
    {
        throw;
    }
}

Comments Off , permalink


29

Nov 11

SharePoint – Approving all folders and items in Site Collection Images using PowerShell



When building a SharePoint 2010 publishing site, you can use the Site Collection Images library to store image assets that are used throughout your site.

There are advantages to putting the images into this library, such as built-in caching support.

I also like to organize my images in the Site Collection Images library into folders.

I was seeing a credentials challenge when accessing an anonymously-accessible version of the site.  This surprised me because #1 the site was configured to be accessible anonymously, and #2 I had published/approved everything.

However, when I looking in the Pending Approval view in Manage Content and Structure, I saw that all the folders inside Site Collection Images were still marked as Pending.

In Fiddler, I would also see a 401 error for those requests.

PowerShell to the rescue!

Here’s how to mark all the folders in Site Collection Images as Approved (for good measure, I’m also marking all the items as Approved).


$site = new-object Microsoft.SharePoint.SPSite($URL)
$web = $site.RootWeb
$list = $web.Lists["Site Collection Images"]

$folders = $list.Folders
foreach ($folder in $folders)
{
    $folder["_ModerationStatus"] = 0
    $folder.Update()
}

$items = $list.Items
foreach ($item in $items)
{
    $item["_ModerationStatus"] = 0
    $item.Update()
}

$web.Dispose();
$site.Dispose();

Comments Off , permalink


12

Aug 11

Slides and Code from Visual Studio ALM User Group Talk



This past week, Peter Walke and I presented at the Visual Studio ALM User Group here in Chicago.

The topic was Continuous Integration with SharePoint 2010 and TFS 2010. As we were getting started, I renamed it to Continuous’ish

That was a key take away from the talk; you need to decide how far to take your build process and if you really, really need to build/deploy on every check in.

Here are the slides and code from the presentation.

Comments Off , permalink


7

Jun 11

Variation-Relative Links in a SharePoint 2010 Master Page



I’m working on a SharePoint web content management project that uses the Variations feature to support multiple languages. Typically, a site like this has a footer (or header) which links to things like a privacy policy and other common pages.


So if I’m in the English variation of my site, a link to the Privacy Policy might look like http://site.domain.com/EN/Pages/PrivacyPolicy.aspx.

However, if I’m in the German variation, this link might look like: http://site.domain.com/DE/Seiten/PrivacyPolicy.aspx. (“Seiten” is the German localized name of the Pages list).

There’s a small challenge here … The footer/header of a site usually lives in the site’s master page – or in a user control in the site’s master page.

Here’s an approach which allows you to use the same master page across your variations, and still use variation-relative links.

Start with a simple link, for example:

<asp:HyperLink ID="hyperlinkPrivacyPolicy" runat="server" Text="<%$Resources:MyResources, Footer_Privacy%>" NavigateUrl="PrivacyPolicy.aspx" />

Note that the Text of the Hyperlink control is coming from a resource file, this allows me to easily localize the display text for the link. The NavigateUrl property is simply the name of the page (for now).

In the code behind of the master page or user control, you can easily get the Url of the Pages list in the variation you’re in. For example, in my English variation, these links should all be prefixed with http://site.domain.com/EN/Pages/.

I use a helper function to get that:

public static string GetVariationsSitePagesUrl()
{
    string variationUrl = String.Empty;
    string pagesUrl = String.Empty;

    var site = SPContext.Current.Site;
    using (var web = site.OpenWeb())
    {
        string currentUrl = web.Url;
        foreach (var variationLabel in
                    Variations.Current.UserAccessibleLabels)
        {
            if (currentUrl.StartsWith(variationLabel.TopWebUrl,
                StringComparison.CurrentCultureIgnoreCase))
            {
                variationUrl = String.Concat("/" + variationLabel.Title);
                pagesUrl = String.Format("{0}/{1}/",
                    variationUrl, PublishingWeb.GetPagesListName(web));
                break;
            }
        }
    }

    return pagesUrl;
}

Then it’s as simple as re-setting the NavigateUrl property of each Hyperlink control at runtime:

string pagesUrl = GetVariationsSitePagesUrl();
hyperlinkPrivacyPolicy.NavigateUrl =
    String.Concat(pagesUrl, hyperlinkPrivacyPolicy.NavigateUrl);

I’m making a couple of assumptions with this approach:

  • These common pages live at the top level Pages library of your variations site.
  • The pages have the same name, e.g. PrivacyPolicy.aspx across all your variations.  There’s an easy way around this: put the page name in a resource file and fetch it programmatically.

Shameless plug.  Check out my new book on amazon.com: Professional Unified Communications Development with Microsoft Lync Server 2010.

Comments Off , permalink


7

Jun 11

Provisioning a SharePoint 2010 Lookup Column with CAML



I’m working on a SharePoint 2010 web content management project and needed to declaratively create a lookup column and associate it to a publishing content type (and hence a publishing page).

As with many things in SharePoint development, something that should be pretty easy to do turns out to be not as obvious.

I started with this pretty helpful blog post from Becky Bertram; there is a lot of good information in the comments so I wanted to put together a small project that brings it all together.

Create the Lookup List

Let’s start by provisioning a simple custom list that will contain the lookup field values:


          Lookup Value 1

          Lookup Value 2

          Lookup Value 3

Create the Lookup Column Field Definition

Next, I’ll add an Empty Element item to the project and add the column definition to it:


If you F5 the project and look at the column in SharePoint, you should see the following:

Create a New Content Type and List Definition

To use the LookupColumn field in a list, I’ll create a new content type and then create a list definition and instance based on it.


Next, create a list definition based on this content type, and also create an instance of the list.

Browse to the list in SharePoint and add a new item to it, you’ll see a drop down list containing the values in the lookup column. Yipee!

Grab the code!

Comments Off , permalink


7

Jun 11

Lync Server 2010 Development Book



It’s finally here …

Almost two months after we got done writing, a box full of copies of our Professional Unified Communications Development with Microsoft Lync Server 2010 showed up at my house.

The whole process wasn’t nearly as difficult as I thought it would be; it helped that Michael Greenlee and I knew the content inside and out.

A word of advice to prospective authors: the copy editor is all knowing, accept their changes, smile, and move on.

Grab yourself a copy!

I already found the first typo/error.  Hint: it’s on the back cover.

Comments Off , permalink


29

Apr 11

Exchange Online Technical Articles on MSDN



I recently wrote some Exchange Online technical articles to coincide with the availability of the Office 365 Beta. My articles – and those by other authors – are now available on MSDN at: http://msdn.microsoft.com/en-us/library/gg193994(v=exchg.140).aspx.

The articles include:

Working with Message Attachments in Microsoft Exchange Online

Working with Application Impersonation in Microsoft Exchange Online

Working with Authentication in Microsoft Exchange Online

Another article that I wrote about the new Conversations feature in the Exchange Web Services Managed SDK 1.1 should be available soon.

Enjoy

Comments Off , permalink