Welcome to MSDN Blogs Sign in | Join | Help

The perfect SharePoint 2010 development environment-at least for me

While running a SharePoint 2010 machine in a Hyper-V environment is not a bad option I found an even better setup for a SharePoint 2010 development machine: a VHD Boot

If you’re running Windows 7 or Windows Server 2008 R2 you can boot into a virtual hard disk and you’re getting almoust the performance of a pure physical install.

Here’s how I configured my machine:
On a Hyper-V machine I created a new VM where I:

· installed Windows Server 2008 R2

· installed IIS

· modified the regional settings

· set timezone

· disabled loopback check: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa new DWORD Value DisableLoopbackCheck=1

· disable shutdown event tracker (http://support.microsoft.com/kb/555541/en-us)

· activated the W2K8 feature ".NET 3.5"

· installed a Loopback Adapter, assigned the fix IP address 10.10.10.1

· installed FireFox

· installed Reflector

· installed Virtual Clone Drive

· installed Office: Office Pro, SPD, Visio, SharePoint Workspace, Project

After that I executed sysprep and did a shutdown on the VM.
With that I’ve got a base VM which can be used as the base for new virtual machines or for VHD boot.

clip_image002

Now I copied this .vhd file into another folder e.g. C:\VHD\W2K8R2.vhd

To boot into that VHD file:

· open an elevated command prompt and run: bcdedit /copy {current} /d “SP2010”
(take a note of the GUID and use it in the following commands)

· bcdedit /set {GUID} device vhd=[C:]\VHD\W2K8R2.vhd

· bcdedit /set {GUID} osdevice vhd=[C:]\VHD\W2K8R2.vhd

Now if you’re rebooting this machine you should see the new boot option “SP2010”.
After the machine started I executed these tasks:

· activated the W2K8 feature "Active Directory Domain Services", Desktop Experience, Wireless LAN Service

· executed dcpromo to create my domain

· activated the “Themes” Service

· installed SQL 2008 + SP1 + CU2

· installed VS.NET 2010 Beta 2

· installed SharePoint 2010 Beta 2

· added the SharePoint “14” folder as a library

· installed SDK

With that I got a very performing – and nice looking – SharePoint 2010 development machine.

clip_image004

-thomas

Posted by thompal | 0 Comments

SPSite: The Web application could not be found

You’re trying to use the SharePoint 2010 server side API’s but you’re getting the exception:

The Web application at http://... could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

Then the root cause is probably not:

-Misconfigured AAM
-Missing App. Pool permissions

It is probably that you’re compiling for x86 platform target.

Change that to x64 and your code will probably work:

clip_image002

-thomas

Posted by thompal | 0 Comments

Hyper-V Virtual Machine Connection and Direct Access

Some days ago I just wanted to create a new virtual machine on a Windows Server 2008 R2 Hyper-V laptop. After booting up that new machine from an .iso image and trying to connect using the “Virtual Machine Connection” (vmconnect.exe) application I got the error message:

“Cannot find the physical computer that runs the virtual machine. Try to flush your DNS cache (run ipconfig /flushdns). Then try to connect again. If the problem persists, contact the administrator of the physical computer or your network administrator”

This occurred when having no connection to a network in a pure offline scenario. The connection to an existing VM on that same machine using RDP worked just fine.

After some debugging we found the root cause of this (thanks Jeromy!). The issue was related to the fact that the laptop was used for Direct Access.

When a Hyper-V enabled machine is disconnected from the network, the Hyper-V virtual switch keeps the network adapter as connected to a network.  This means that the NIC will get auto-assigned addresses etc.  Direct Access is still active in this configuration. Most importantly the NRPT configuration is still active which prevents one of the requirements for Hyper-V server from functioning properly.

The real problem:
Hyper-V server requires the ability to connect back to the “remote” client via resolving the FQDN of the connecting client and establishing its own TCP connection back to the “remote” client.  Even while managing Hyper-V locally the local tools are seen as a “remote” client and as such the Hyper-V server will extract the FQDN of the connection and attempt to do name resolution to ensure that the client that is connecting actually owns the IP address it is connecting from. With NRPT active but unable to access a Direct Access server because the Direct Access client is offline the NRPT prevents the resolution of the FQDN (it would have allowed short-name resolution to fall through and use local LLMNR except that the connection is using IPv4 and not IPv6 and thus the IP addresses would not match and the connection is denied as a “spoof” attack).  Once NRPT is taken out of the picture the Hyper-V server is able to properly do name resolution leveraging the local resolver and everything connects up just fine.

The solution:
-go into the Registry and delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig"
-in an elevated command prompt, enter: sc control dnscache paramchange

After that the “Virtual Machine Connection” was working fine.

-thomas

Posted by thompal | 0 Comments

Search Federation on Window 7

Using Windows 7 there’s an easy way to integrate search results from various search engines into the windows explorer. By default the “Favorites” group only has entries for “Desktop”, “Downloads” and “Recent Places”:

clip_image002

To add a new “Favorite” to that list e.g. to run your search queries against www.live.com just copy the following XML into a file, save it as live.osdx.

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Live Search</ShortName>
<Description>Use live.com to search the web index.</Description>
<Tags>Web</Tags>
<LongName>Live.com Web Search</LongName>
<Image width="16" height="16" type="image/gif">http://search.live.com:80/s/rsslogo.gif</Image>
<Query role="example" searchterms="seattle"/>
<Developer>Microsoft Corporation, Live Search Development Team</Developer>
<SyndicationRight>open</SyndicationRight>
<InputEncoding>UTF-8</InputEncoding>
<OutputEncoding>UTF-8</OutputEncoding>
<Url type="text/html" template="http://search.live.com/results.aspx?q={searchTerms}"/>
<Url type="application/rss+xml" template="http://api.search.live.com/rss.aspx?source=web&amp;query={searchTerms}&amp;web.count=50&amp;mkt={language?}"/>
<ms-ose:ResultsProcessing format="application/rss+xml" xmlns:ms-ose="http://schemas.microsoft.com/opensearchext/2009/">
<ms-ose:LinkIsFilePath>True</ms-ose:LinkIsFilePath>
<ms-ose:PropertyDefaultValues>
<ms-ose:Property schema="http://schemas.microsoft.com/windows/2008/propertynamespace" name="System.PropList.ContentViewModeForSearch">prop:~System.ItemNameDisplay;System.DateModified;~System.ItemPathDisplay;~System.Search.AutoSummary;System.LayoutPattern.PlaceHolder;System.LayoutPattern.PlaceHolder;System.LayoutPattern.PlaceHolder</ms-ose:Property>
</ms-ose:PropertyDefaultValues>
</ms-ose:ResultsProcessing>
</OpenSearchDescription>

After double-click that file you’ll get this message:

clip_image004

Click “Add” and you’ll get “Live Search” as a new entry in your “Favorites”:

clip_image006

Now you can run your search queries using Live Search directly from your Windows 7 explorer:

clip_image008

Enjoy,
thomas

Posted by thompal | 1 Comments

Accessible MOSS WCM Sites

Have you ever tried implementing a MOSS based public internet site which creates HTML code without <TABLES>?
Are you looking for some guidance about how to implement an AA standard compliant accessible web site which can be read by screen readers or where you can modify the font size?

Microsoft Switzerland together with our partner 1eEurope implemented such a web site. We published this solution with the code here on Codeplex:
http://www.codeplex.com/Wiki/View.aspx?ProjectName=bks
After installing this solution you get a complete starter site with master-page, content-page and content types which can be used as the base for your project.

Here's how the sample site looks like:

You can find this sample site here: http://bks.1eeurope.ch/en-US/Pages/default.aspx

Enjoy,
thomas

Posted by thompal | 1 Comments
Filed under:

Improving performance for stsadm -o export and import

Last week we've migrated content from one farm to another using stsadm –o export / import. The export of the 2GB Content DB took around 2 hours. The import of that content into the new farm took around 5 hours. During that import process we found a nice option for speeding up the import process. After executing:

Netsh int ip set chimney DISABLED

The import process was really speeding up. It seems that some servers are having issues with this TCP Offloading functionality. You can find additional information about this topic here:
http://technet.microsoft.com/en-us/library/bb878074.aspx
http://blogs.technet.com/sbs/archive/2007/04/24/common-networking-issues-after-applying-windows-server-2003-sp2-on-sbs.aspx
http://sigicom.blogspot.com/2008/03/tcp-offloading-chimney-von-sp2.html

Thanks Martin!

Posted by thompal | 1 Comments

How to overwrite styles of the core.css stylesheet

With the default behavior of a SharePoint page the core.css is rendered always as the last stylesheet. This is implemented using the control:

<Sharepoint:CssLink runat="server"/>

Using this control the style sheets will render like this:

<link rel="stylesheet" type="text/css" href="http://blogs.msdn.com/_layouts/1033/styles/controls.css?rev=EhwiQKSLiI%2F4dGDs6DyUdQ%3D%3D" mce_href="http://blogs.msdn.com/_layouts/1033/styles/controls.css?rev=EhwiQKSLiI%2F4dGDs6DyUdQ%3D%3D"/>
<link rel="stylesheet" type="text/css" href="http://blogs.msdn.com/Style%20Library/en-US/Core%20Styles/pageLayouts.css" mce_href="http://blogs.msdn.com/Style%20Library/en-US/Core%20Styles/pageLayouts.css"/>
<link rel="stylesheet" type="text/css" href="http://blogs.msdn.com/_layouts/1033/styles/core.css?rev=baCjtd5mQmxwkFlwbFgfgQ%3D%3D"/>

This is an issue when you're trying to overwrite some styles which are defined in this core.css file.

To fix this you can either implement a custom component which is derived from CssLink (as described here) or use the "AdditionalPageHead" placeholder:

<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server">

Using this placeholder you can easily overwrite the default style either inline or in a separate stylesheet.

<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server">
<style type="text/css">
          .ms-sitemapdirectional,.ms-sitemapdirectional a{
            unicode-bidi:embed;
            background:#FF0000;
            }
      </style>
</asp:ContentPlaceHolder>

 

Posted by thompal | 0 Comments

How to use MOSS output caching with custom parameters

In MOSS there are three different types of caching available:

  • Output caching
  • BLOB Cache
  • Object Cache

You can find a very detailed description about these three options here: http://blogs.msdn.com/ecm/archive/2006/11/08/how-to-make-your-moss-2007-web-site-faster-with-caching.aspx .

If you want to use output caching for the anonymous user of your site but you want to cache these pages also based on some custom parameters like cookie values or query string parameters you should follow these steps:

  1. Go to the "Site Collection Cache Profiles" on the site settings of your web application and edit the properties of the profile "Public Internet (Purely Anonymous)". In the "VARY BY CUSTOM PARAMETER" section you can enter your custom parameters e.g. "CookieValues" and "Country" (semicolon separated):

    2.    The next step is to tell your web application about your CustomCachingHttpApplication in the global.asax file:

<%@ Assembly Name="Microsoft.SharePoint"%>
<%@ Assembly Name="<assemblyfullname>" %>
<%@ Import Namespace="<your_namespace>" %>
<%@ Application Language="C#" Inherits="<your_namespace>.CustomCachingHttpApplication" %>

    3.   The last step is implementing this CustomCachingHttpApplication class:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.ApplicationRuntime;
using Microsoft.SharePoint;

namespace <your_namespace>
{
   
public class CookieCachingHttpApplication : SPHttpApplication,
                                                IVaryByCustomHandler 
    {
     
public override void Init()
     
{
       
base.Init();
        this.RegisterGetVaryByCustomStringHandler
             ((Microsoft.SharePoint.ApplicationRuntime.IVaryByCustomHandler)this);
     
}

public string GetVaryByCustomString(HttpApplication app, HttpContext context, string custom)
{
string[] strings = custom.Split(';');
string cacheKey = string.Empty;

      foreach (string arg in strings)
     
{
        
if (arg.ToLower() == "cookievalues")
        
{
           
HttpCookie countryCookie = context.Request.Cookies
              [System.Web.Configuration.WebConfigurationManager.AppSettings["CookieCountry"]];

            HttpCookie languageCookie = context.Request.Cookies
              [System.Web.Configuration.WebConfigurationManager.AppSettings["CookieLanguage"]];

            if (countryCookie != null && languageCookie != null)
           
{
               
cacheKey += countryCookie.Value + languageCookie.Value;
           
}
        
}

         if (arg.ToLower() == "country")
        
{
            
cacheKey += context.Request["Country"];
        
}

         return cacheKey;

     }
   }
}

With that your pages are now cached depending on the cookie values "CookieCountry" and "CookieLanguage" and the query string parameter "Country".

Posted by thompal | 1 Comments
Filed under:

Could not find file “C:\WINDOWS\system32\drivers\etc\HOSTS“

If you find this event:
Application Server Administration job failed for service instance Microsoft.Office.Server.Search.Administratin.SearchServiceInstant <guid>

Could not find file "C:\WINDOWS\system32\drivers\etc\HOSTS"

In your event logs of your MOSS box the "Office SharePoint Server Search" service is probably the root of this.

If the WSS_ADMIN_WPG group has 'Modify' permissions on this folder with the HOSTS file the "Office SharePoint Server Search" service will create new entries there instead of using a DNS. When enabling the "Office SharePoint Server Search" Service on the "Services on Server" page, if you chose a dedicated WFE for crawling, the search process will add entries to this HOSTS file to ensure that it crawls through that dedicated WFE.

Removing the setting for a dedicated WFE should be sufficient to remove the entry from the HOSTS file.

-thomas

Posted by thompal | 1 Comments
Filed under:

Implementing a multi-lingual internet site based on MOSS2007

One of the requirements for the implementation of www.swiss.com was supporting several countries with their languages and showing local content on the homepage for each individual country. The red box in the following screenshot marks this area for the local content where you can find the local content for "Switzerland" in "German".

Using such a concept would allow a user to select the country (and language if there are several languages within this country) and the homepage will render this content which is specific to this country on a pre-defined section of the homepage. For selecting the country and language we've implemented this control on the homepage:

Clicking on the country drop-down opens the list of supported countries:

After selecting for example "Switzerland" and "German" these controls will change into German:

All of that was implemented with a combination of variations and a hierarchy of sub-webs for managing the country specific content. The main content of the whole site is managed under the variations "DE" (German), "EN" (English), "ES" (Spanish), "FR" (French) and "IT" (Italian):

The country specific content is stored under this hierarchy of sub-webs:

For combining the main content which is managed under the variations and the local content we're simply using a web part which renders this local content on the home page.

-thomas

Posted by thompal | 1 Comments
Filed under:

Developing a custom field control

Let's start with a discussion about why you would need a custom filed control?
Well let's have a look at this page. On this page you can find several offers (in the red box). These offers are simply a list with the destination, price and a url.

The easiest solution for editing these offers would be adding a RichText field control (or two in our scenario) onto your page.

With that however the editing experience for an author is not very exciting. She would have to edit the HTML code for this RichText field control:

So we came up with the idea to implement a custom field control which takes only the necessary data from the author and which renders itself onto the page. The necessary data for our offers scenario are: Title, for each offer: destination, price, url, the text for the link "More offers" and the underlying url and a field with some additional information. The initial prototype for this custom field control looked like this:

Using these fields an author can provide the necessary information for our offers page. Once the page is published the field control renders its content like this:

The downside of this approach was that we've limited our self to create two offers only. We wanted to have a dynamic list of offers where an author can decide how many offers she wants to create on a specific page. So we've prototyped such a control using a standard ASP.NET 2.0 page with a GridView which is bound to an ObjectDataSource.

The last step was to integrate this control into the custom field control which can be used by SharePoint on a Page Layout. The following screen-shot show two instances of this new field control.

Each row with the offer details has a button for editing or deleting this offer. Switching an offer into edit mode allows the author to modify this specific offer:

So now after we finished the author's view of this custom field control let's have a look at the development side. We basically need 3 files:

-an .ascx file with the UI of the custom field control
-an .xml file with the description of the control
-the actual file where we implement the control

I'm listing these 3 files below.

  1. .ascx file for the UI. This file has to be copied into the folder: "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES"

<%@ Control Language="C#" Debug="true" %>

<%@Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>

<SharePoint:RenderingTemplate ID="OffersFieldRendering" runat="server">

<Template>

Title:

<br />

<asp:TextBox ID="txtTitle" Width="196px" runat="server"></asp:TextBox>

<asp:GridView

DataSourceID="objOffersCollection"

ID="grdOffers"

runat="server"

ShowHeader="False"

AutoGenerateColumns="False"

DataKeyNames=""

ShowFooter="True"

BorderWidth="0px"

Width="200px"

>

<Columns>

<asp:TemplateField HeaderText="" ControlStyle-Width="" FooterStyle-Width="" HeaderStyle-Width="" ItemStyle-Width="">

<ItemTemplate>

<ul class="listitem_redright_dotted" style="width: 160px; height: 14px;">

<li>

<%# String.Format("<a title='' href='{0}'><span class=right>{1}</span>{2}</a>", DataBinder.Eval(Container.DataItem, "Url"), DataBinder.Eval(Container.DataItem, "Price"), DataBinder.Eval(Container.DataItem, "Destination"))%>

</li>

</ul>

</ItemTemplate>

<EditItemTemplate>

Destination:

<br />

<asp:TextBox ID="txtDestination" Text='<% #DataBinder.Eval(Container.DataItem, "Destination") %>' Width="160px" runat="server"></asp:TextBox>

Price:

<br />

<asp:TextBox ID="txtPrice" Text='<% #DataBinder.Eval(Container.DataItem, "Price") %>' Width="160px" runat="server"></asp:TextBox>

URL:

<br />

<asp:TextBox ID="txtUrl" Text='<% #DataBinder.Eval(Container.DataItem, "Url") %>' Width="160px" runat="server"></asp:TextBox>

</EditItemTemplate>

<FooterTemplate>

Destination (for new Offer):

<br />

<asp:TextBox ID="txtAddDestination" ToolTip="please enter the destination" Width="160px" runat="server"></asp:TextBox>

Price (for new Offer):

<br />

<asp:TextBox ID="txtAddPrice" ToolTip="please enter the price" Width="160px" runat="server"></asp:TextBox>

URL (for new Offer):

<br />

<asp:TextBox ID="txtAddUrl" ToolTip="please enter the URL" Width="160px" runat="server"></asp:TextBox>

<asp:ImageButton ID="btnAdd" AlternateText="Add" runat="server" CommandName="AddOffer" Text="Add" ImageUrl='<%# "~/Style%20Library/images/buttons/button_add.gif" %>' />

</FooterTemplate>

</asp:TemplateField>

<asp:CommandField ButtonType="Link" ShowEditButton="True">

<itemstyle width="8px" />

</asp:CommandField>

<asp:CommandField ButtonType="Link" ShowDeleteButton="True" >

<itemstyle width="8px" />

</asp:CommandField>

</Columns>

</asp:GridView>

<asp:ObjectDataSource

DeleteMethod="DeleteOffer"

InsertMethod="AddOffer"

ID="objOffersCollection"

runat="server"

SelectMethod="GetAllOffersAsDataSet"

FilterExpression="controlid='' OR controlid='{0}'"

UpdateMethod="UpdateOffer"

TypeName="Swiss.WebParts.OffersCollection"

EnableCaching="False"

>

</asp:ObjectDataSource>

More Offers:

<br />

<asp:TextBox ID="txtMoreOffersText" runat="server" Text="More offers" Width="196px"></asp:TextBox>

<br />

URL:

<br />

<asp:TextBox ID="txtMoreOffersUrl" runat="server" Width="196px"></asp:TextBox>

<br />

Information:

<br />

<asp:TextBox ID="txtInformation" runat="server" Text="" TextMode="MultiLine" Height="40px" Width="196px"></asp:TextBox>

</Template>

</SharePoint:RenderingTemplate>

  1. The .xml file with the description of the field types. This file has to be copied into the directory: "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML"

<FieldTypes>

<FieldType>

<Field Name="TypeName">Offers</Field>

<Field Name="ParentType">MultiColumn</Field>

<Field Name="TypeDisplayName">Offers</Field>

<Field Name="TypeShortDescription">Offers (editing a list of offers)</Field>

<Field Name="UserCreatable">TRUE</Field>

<Field Name="ShowOnListAuthoringPages">TRUE</Field>

<Field Name="ShowOnDocumentLibraryAuthoringPages">TRUE</Field>

<Field Name="ShowOnSurveyAuthoringPages">TRUE</Field>

<Field Name="ShowOnColumnTemplateAuthoringPages">TRUE</Field>

<Field Name="FieldTypeClass">Swiss.WebParts.FieldOffers, Swiss.WebParts, Version=1.0.0.1, Culture=neutral, PublicKeyToken=585fff093fa85341</Field>

<PropertySchema>

<Fields>

<Field Name="DefaultTitle" DisplayName="Default Title:" MaxLength="250" DisplaySize="30" Type="Text">

<Default></Default>

</Field>

</Fields>

</PropertySchema>

<RenderPattern Name="DisplayPattern">

</RenderPattern>

</FieldType>

</FieldTypes>

  1. The .cs file with the actual implementation of the custom field control:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.Data;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.IO;

using System.Text.RegularExpressions;

namespace Sample.WebParts

{

#region Offers FieldControl

#region FieldOffersValue (Definition of the necessary values for storing the 'Offer' information.)

public class FieldOffersValue : SPFieldMultiColumnValue

{

private const int numberOfFields = 5;

public FieldOffersValue()

: base(numberOfFields)

{ }

public FieldOffersValue(string value)

: base(value)

{ }

/// <summary>

/// Holds the title of an offer.

/// </summary>

public string Title

{

get { return this[0]; }

set { this[0] = value; }

}

/// <summary>

/// Stores all 'Offers' records separated by \t.

/// </summary>

public string OffersData

{

get { return this[1]; }

set { this[1] = value; }

}

/// <summary>

/// Stores the text for the 'More Offers' link

/// </summary>

public string MoreOffersText

{

get { return this[2]; }

set { this[2] = value; }

}

/// <summary>

/// Stores the URL for the 'More Offers' link

/// </summary>

public string MoreOffersUrl

{

get { return this[3]; }

set { this[3] = value; }

}

/// <summary>

/// Stores the text for the additional information which can be defined for an offer.

/// </summary>

public string Information

{

get { return this[4]; }

set { this[4] = value; }

}

}

#endregion

#region FieldOffers (Communication with the values)

public class FieldOffers : SPFieldMultiColumn

{

public FieldOffers(SPFieldCollection fields, string fieldName)

: base(fields, fieldName)

{ }

public FieldOffers(SPFieldCollection fields,

string typeName,

string displayName)

: base(fields, typeName, displayName)

{ }

public override BaseFieldControl FieldRenderingControl

{

get

{

BaseFieldControl fldControl = new OffersFieldControl();

fldControl.FieldName = InternalName;

return fldControl;

}

}

public override object GetFieldValue(string value)

{

if (string.IsNullOrEmpty(value))

return null;

return new FieldOffersValue(value);

}

}

#endregion

#region OffersFieldControl (The user control)

public class OffersFieldControl : BaseFieldControl

{

protected TextBox txtTitle;

protected ObjectDataSource objOffersCollection;

protected GridView grdOffers;

protected TextBox txtMoreOffersText;

protected TextBox txtMoreOffersUrl;

protected TextBox txtInformation;

protected static OffersCollection _dsOffersCollection = new OffersCollection();

protected override string DefaultTemplateName

{

get { return "OffersFieldRendering"; }

}

public override object Value

{

get

{

// called when submitting the content for approval

EnsureChildControls();

FieldOffersValue fieldValue = new FieldOffersValue();

fieldValue.Title = txtTitle.Text.Trim();

fieldValue.OffersData = _dsOffersCollection.GetOffersData(this.ID);

fieldValue.MoreOffersText = txtMoreOffersText.Text.Trim();

fieldValue.MoreOffersUrl = txtMoreOffersUrl.Text.Trim();

fieldValue.Information = txtInformation.Text.Trim();

return fieldValue;

}

set

{

// called when editing the page

EnsureChildControls();

FieldOffersValue fieldValue = (FieldOffersValue)value;

txtTitle.Text = fieldValue.Title;

_dsOffersCollection.SetOffersData(this.ID, fieldValue.OffersData);

txtMoreOffersText.Text = fieldValue.MoreOffersText;

txtMoreOffersUrl.Text = fieldValue.MoreOffersUrl;

txtInformation.Text = fieldValue.Information;

}

}

public override void Focus()

{

EnsureChildControls();

grdOffers.DataBind();

txtTitle.Focus();

}

protected override void CreateChildControls()

{

if (Field == null)

return;

base.CreateChildControls();

if (ControlMode == SPControlMode.Display)

return;

txtTitle = (TextBox)TemplateContainer.FindControl("txtTitle");

if (txtTitle == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtTitle.");

txtTitle.TabIndex = TabIndex;

txtTitle.CssClass = CssClass;

txtTitle.ToolTip = "please enter a Title";

objOffersCollection = (ObjectDataSource)TemplateContainer.FindControl("objOffersCollection");

if (objOffersCollection == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing objOffersCollection.");

objOffersCollection.ObjectCreating += new ObjectDataSourceObjectEventHandler(objOffersCollection_ObjectCreating);

objOffersCollection.Filtering += new ObjectDataSourceFilteringEventHandler(objOffersCollection_Filtering);

grdOffers = (GridView)TemplateContainer.FindControl("grdOffers");

if (grdOffers == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing grdOffers.");

grdOffers.TabIndex = TabIndex;

grdOffers.CssClass = CssClass;

grdOffers.ToolTip = "please use these edit fields to add a new Offer";

grdOffers.RowCancelingEdit += new GridViewCancelEditEventHandler(grdOffers_RowCancelingEdit);

grdOffers.RowEditing += new GridViewEditEventHandler(grdOffers_RowEditing);

grdOffers.RowUpdating += new GridViewUpdateEventHandler(grdOffers_RowUpdating);

grdOffers.RowDeleting += new GridViewDeleteEventHandler(grdOffers_RowDeleting);

grdOffers.RowCommand += new GridViewCommandEventHandler(grdOffers_RowCommand);

grdOffers.RowDataBound += new GridViewRowEventHandler(grdOffers_RowDataBound);

// set the url's for the command button images

// unfortunately this cannot be done in the .ascx file using CodeExpressions

for (int i = 0; i < grdOffers.Columns.Count; i++)

{

DataControlField field = grdOffers.Columns[i];

if (field is CommandField)

{

string editImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_edit.gif", SPContext.Current.Site.Url);

string cancelImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_cancel.gif", SPContext.Current.Site.Url);

string deleteImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_delete.gif", SPContext.Current.Site.Url);

string updateImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_update.gif", SPContext.Current.Site.Url);

string editText = string.Format("<img src='{0}' alt='Edit' border='0' />", editImageUrl);

string cancelText = string.Format("<img src='{0}' alt='Cancel' border='0' />", cancelImageUrl);

string deleteText = string.Format("<img src='{0}' alt='Delete' border='0' />", deleteImageUrl);

string updateText = string.Format("<img src='{0}' alt='Update' border='0' />", updateImageUrl);

CommandField cmdField = (CommandField)field;

if (cmdField != null)

{

//cmdField.EditImageUrl = editImageUrl;

//cmdField.CancelImageUrl = cancelImageUrl;

//cmdField.DeleteImageUrl = deleteImageUrl;

//cmdField.DeleteText = deleteImageUrl;

cmdField.EditText = editText;

cmdField.CancelText = cancelText;

cmdField.DeleteText = deleteText;

cmdField.UpdateText = updateText;

}

}

}

txtMoreOffersText = (TextBox)TemplateContainer.FindControl("txtMoreOffersText");

if (txtMoreOffersText == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtMoreOffersText.");

txtMoreOffersText.TabIndex = TabIndex;

txtMoreOffersText.CssClass = CssClass;

txtMoreOffersText.ToolTip = "please enter the text for the 'More Offers' link";

txtMoreOffersUrl = (TextBox)TemplateContainer.FindControl("txtMoreOffersUrl");

if (txtMoreOffersUrl == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtMoreOffersUrl.");

txtMoreOffersUrl.TabIndex = TabIndex;

txtMoreOffersUrl.CssClass = CssClass;

txtMoreOffersUrl.ToolTip = "please enter the URL for the 'More Offers' link";

txtInformation = (TextBox)TemplateContainer.FindControl("txtInformation");

if (txtInformation == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtInformation.");

txtInformation.TabIndex = TabIndex;

txtInformation.CssClass = CssClass;

txtInformation.ToolTip = "please enter the additional Information for these Offers";

if (ControlMode == SPControlMode.New)

{

txtTitle.Text = Field.GetCustomProperty("DefaultTitle").ToString();

}

}

protected void grdOffers_RowEditing(object sender, GridViewEditEventArgs e)

{

grdOffers.EditIndex = e.NewEditIndex;

}

protected void grdOffers_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)

{

grdOffers.EditIndex = -1;

}

protected void grdOffers_RowUpdating(object sender, GridViewUpdateEventArgs e)

{

string destination = ((TextBox)grdOffers.Rows[e.RowIndex].FindControl("txtDestination")).Text;

string price = ((TextBox)grdOffers.Rows[e.RowIndex].FindControl("txtPrice")).Text;

string url = ((TextBox)grdOffers.Rows[e.RowIndex].FindControl("txtUrl")).Text;

int row = e.RowIndex;

objOffersCollection.UpdateParameters.Add("Row", TypeCode.Int32, row.ToString());

objOffersCollection.UpdateParameters.Add("ControlId", TypeCode.String, this.ID);

objOffersCollection.UpdateParameters.Add("Destination", destination);

objOffersCollection.UpdateParameters.Add("Price", price);

objOffersCollection.UpdateParameters.Add("Url", url);

objOffersCollection.Update();

grdOffers.EditIndex = -1;

}

protected void grdOffers_RowDeleting(object sender, GridViewDeleteEventArgs e)

{

int row = e.RowIndex;

objOffersCollection.DeleteParameters.Add("Row", TypeCode.Int32, row.ToString());

objOffersCollection.DeleteParameters.Add("ControlId", TypeCode.String, this.ID);

objOffersCollection.Delete();

e.Cancel = false;

}

protected void grdOffers_RowCommand(object sender, GridViewCommandEventArgs e)

{

if (e.CommandName == "AddOffer")

{

if (objOffersCollection != null)

{

string destination = ((TextBox)grdOffers.FooterRow.FindControl("txtAddDestination")).Text;

string price = ((TextBox)grdOffers.FooterRow.FindControl("txtAddPrice")).Text;

string url = ((TextBox)grdOffers.FooterRow.FindControl("txtAddUrl")).Text;

objOffersCollection.InsertParameters.Add("Destination", destination);

objOffersCollection.InsertParameters.Add("Price", price);

objOffersCollection.InsertParameters.Add("Url", url);

objOffersCollection.InsertParameters.Add("ControlId", this.ID);

objOffersCollection.Insert();

}

}

}

protected void grdOffers_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

if (e.Row.RowIndex == 0)

{

// the first row of the object data source contains an empty record. Without that empty record the GridView

// would not be visible (I think this is a bug in the GridView). For displaying the GridView also when

// no data is available we've created this empty record in the object data source. However this empty

// record will be hidden.

if (e.Row.Cells[0].Text == string.Empty && e.Row.Cells[1].Text == string.Empty && e.Row.Cells[2].Text == string.Empty)

{

e.Row.Visible = false;

}

}

}

}

protected void objOffersCollection_ObjectCreating(object sender, ObjectDataSourceEventArgs e)

{

e.ObjectInstance = _dsOffersCollection;

}

protected void objOffersCollection_Filtering(object sender, ObjectDataSourceFilteringEventArgs e)

{

e.ParameterValues.Clear();

e.ParameterValues.Add("controlid", this.ID);

}

protected override void RenderFieldForDisplay(HtmlTextWriter output)

{

if (Field == null)

return;

string offersRowFormat = "<li><a title='' href='{2}'><span class=right>{1}</span>{0}</a></li>";

string offersRows = "";

StringBuilder sb = new StringBuilder();

if (ItemFieldValue is FieldOffersValue)

{

FieldOffersValue fov = (FieldOffersValue)ItemFieldValue;

if (fov != null)

{

string title = fov.Title;

string offersData = fov.OffersData;

string moreOffersText = fov.MoreOffersText;

string moreOffersUrl = fov.MoreOffersUrl;

string information = fov.Information;

sb.Append(string.Format("<h3 class=top>{0}</h3>", title));

sb.Append("<ul class='listitem_redright_dotted'>");

// insert offers rows

string[] offersItemsArray = offersData.Split('\n');

foreach (string offerItem in offersItemsArray)

{

string[] offerItemDetails = offerItem.Split('\t');

if (offerItemDetails.Length >= 2)

{

if (offerItemDetails[0] != null && offerItemDetails[1] != null && offerItemDetails[2] != null)

{

if (offerItemDetails[0] != string.Empty && offerItemDetails[1] != string.Empty && offerItemDetails[2] != string.Empty)

offersRows += string.Format(offersRowFormat, offerItemDetails[0], offerItemDetails[1], offerItemDetails[2]);

}

}

}

sb.Append(offersRows);

sb.Append("</ul>");

sb.Append("<ul class='listitem_redright_dotted'>");

sb.Append(string.Format("<li><a title='' href='{0}'>{1}</a></li>", moreOffersUrl, moreOffersText));

sb.Append("</ul>");

sb.Append("<br>");

sb.Append(string.Format("<span class=ms-rteCustom-bold>{0}</span>", information));

}

}

else

{

sb.Append(string.Format("{0}", this.ID));

}

output.Write(sb);

}

protected override void Render(HtmlTextWriter output)

{

if (((SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.Edit)

|| (SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.New)))

{

TextWriter tempWriter = new StringWriter();

base.Render(new System.Web.UI.HtmlTextWriter(tempWriter));

string orightml = tempWriter.ToString();

// correct the "leaving the page problem".

Regex hrefRegex = new Regex("href=\"javascript(?<PostBackScript>.*?)\"", RegexOptions.Singleline);

string newhtml = hrefRegex.Replace(orightml, "href=\"#\" onclick=\"${PostBackScript};return false;\"");

output.Write(newhtml);

}

else

{

base.Render(output);

}

}

}

#endregion

#region OffersCollection (Used as an Object Datasource. Holds a collection of Offers.)

public class OffersCollection

{

private List<Offer> _offerList;

public OffersCollection()

{

if (_offerList == null)

{

_offerList = new List<Offer>();

_offerList.Add(new Offer("", "", "", ""));

}

}

public void UpdateOffer(int row, string controlid, string destination, string price, string url)

{

int rowForControlId = 0;

for (int pos = 0; pos < _offerList.Count; pos++)

{

Offer o = _offerList[pos];

if (o != null && o.IsDeleted != true)

{

if (o.ControlID == controlid)

rowForControlId++;

if (rowForControlId == row)

{

o.Destination = destination;

o.Price = price;

o.URL = url;

_offerList[pos] = o;

break;

}

}

}

}

public void AddOffer(string destination, string price, string url, string controlId)

{

if (_offerList != null)

_offerList.Add(new Offer(destination, price, url, controlId));

}

// RowDeleting executed twice

// http://forums.asp.net/tags/events/default.aspx?PageIndex=3

// http://www.issociate.de/board/post/285047/help_please_on_GridView_commands_+_AutoEventWireUp

public void DeleteOffer(int row, string controlid)

{

int rowForControlId = 0;

for (int pos = 0; pos < _offerList.Count; pos++)

{

Offer o = _offerList[pos];

if (o != null && o.IsDeleted != true)

{

if (o.ControlID == controlid)

rowForControlId++;

if (rowForControlId == row)

{

o.IsDeleted = true;

_offerList[pos] = o;

break;

}

}

}

}

public List<Offer> GetAllOffers()

{

if (_offerList != null && _offerList.Count > 0)

return _offerList;

else

return null;

}

public string GetOffersData(string controlId)

{

string offersData = "";

string offerItem = "";

foreach (Offer o in _offerList)

{

if (o.ControlID == controlId && o.IsDeleted != true)

{

offerItem = string.Format("{0}\t{1}\t{2}\n", o.Destination, o.Price, o.URL);

offersData += offerItem;

}

}

return (offersData);

}

/// <summary>

/// The offer items are stored in one big string where

/// </summary>

/// <param name="offersData"></param>

public void SetOffersData(string controlId, string offersData)

{

// clean-up

_offerList.RemoveAll(delegate(Offer o)

{

return o.ControlID == controlId;

}

);

string[] offersItemsArray = offersData.Split('\n');

foreach (string offerItem in offersItemsArray)

{

string[] offerItemDetails = offerItem.Split('\t');

if (offerItemDetails.Length >= 3)

{

if (offerItemDetails[0] != null && offerItemDetails[1] != null && offerItemDetails[2] != null)

{

Offer newOffer = new Offer(offerItemDetails[0], offerItemDetails[1], offerItemDetails[2], controlId);

_offerList.Add(newOffer);

}

}

}

}

public DataSet GetAllOffersAsDataSet()

{

DataSet ds = new DataSet("Table");

// Create the schema of the DataTable.

DataTable dt = new DataTable();

DataColumn dc;

dc = new DataColumn("Destination", typeof(string)); dt.Columns.Add(dc);

dc = new DataColumn("Price", typeof(string)); dt.Columns.Add(dc);

dc = new DataColumn("Url", typeof(string)); dt.Columns.Add(dc);

dc = new DataColumn("ControlId", typeof(string)); dt.Columns.Add(dc);

//dc = new DataColumn("OfferId", typeof(string)); dt.Columns.Add(dc);

// Add rows to the DataTable.

DataRow row;

for(int offerid = 0; offerid < _offerList.Count; offerid++)

{

Offer o = _offerList[offerid];

if (o != null && o.IsDeleted != true)

{

row = dt.NewRow();

row["Destination"] = o.Destination;

row["Price"] = o.Price;

row["Url"] = o.URL;

row["ControlId"] = o.ControlID;

//row["OfferId"] = offerid.ToString();

dt.Rows.Add(row);

}

}

// Add the complete DataTable to the DataSet.

ds.Tables.Add(dt);

return ds;

}

}

public class Offer

{

private string _destination = string.Empty;

private string _price = string.Empty;

private string _url = string.Empty;

private string _controlId = string.Empty;

private bool _isDeleted = false;

public string Destination

{

get

{

if (!String.IsNullOrEmpty(_destination))

return _destination;

else

return "";

}

set { _destination = value; }

}

public string Price

{

get

{

if (!String.IsNullOrEmpty(_price))

return _price;

else

return "";

}

set { _price = value; }

}

public string URL

{

get

{

if (!String.IsNullOrEmpty(_url))

return _url;

else

return "";

}

set { _url = value; }

}

public string ControlID

{

get

{

if (!String.IsNullOrEmpty(_controlId))

return _controlId;

else

return "";

}

set { _controlId = value; }

}

public bool IsDeleted

{

get { return _isDeleted; }

set { _isDeleted = value; }

}

public Offer(string destination, string price, string url, string controlId)

{

_destination = destination;

_price = price;

_url = url;

_controlId = controlId;

_isDeleted = false;

}

public Offer()

{

}

}

#endregion

#endregion

}

Until next time,
thomas

Posted by thompal | 0 Comments
Filed under:

Implementing www.swiss.com using MOSS2007

In December of 2006 swiss international airlines invited us to present the new Web Content Management functionality of MOSS2007. The idea was to use MOSS2007 for the re-launch of their public internet site www.swiss.com .

Here's a high-level overview about the requirements:

  • Multi-lingual: supporting more than 10 countries and their local languages
  • Integrating a standard ASP.NET 2.0 based application for the flight-booking application
  • Very slim pages with short download times
  • Multi-lingual forms with special validation and submit logic
  • Easy to use content management functionality
  • Configurable presenter for announcing news in a slide-show style
  • Easy to use Translation functionality
  • Re-use of master-pages and page-layouts for upcoming intra- and extranet projects

In January 2007 we decided to invest several days for a proof-of-concept (PoC). During this PoC we've installed the necessary environment, designed the architecture for the multi-lingual concept and implemented several parts of the site.

In the February timeframe we started to implement the site hierarchy and the implementation of the variations. The real development of the necessary web parts started in March. On june, 14th we went life and here's a sample showing you how this site looks like:

In the next few posts I'll describe some of the challenges we had during the implementation.

-thomas

Posted by thompal | 0 Comments
Filed under:

Introduction

Hi there
My name is Thomas Palmié and I'm a Principal Consultant working for Microsoft Consulting Services in Switzerland. I'm focusing on Information Worker solutions around Microsoft Office SharePoint Server 2007 (MOSS) and the Office clients.

In this blog I'll try to blog about experiences, tips & tricks I learned in various projects. I hope you'll find something interesting there.

-thomas

Posted by thompal | 0 Comments
 
Page view tracker