Navigation : Sub-levels

In this tutorial we will see that it is possible to increase the number of sub-levels displayed in navigation, whether at the global navigation, or at the quick navigation.

To illustrate this example, create a site like Team Site. In this site, create a new one and so on for a tree of 5 levels. The first site we will activate the publishing feature that will allow us to have a global view of the tree.


Once the sites created, we have a tree that looks like this:

At each site, go to the navigation menu and check show subsites.

Let us return to our site “Tuto” and look what it’s like in the global navigation.

As we see, only the first sublevel appears statically, if we move the mouse over the “Sub-Site 1” of the global navigation, we see the second sublevel and if we move the mouse on “sub-sub site-“the title is just highlighted.

In fact the number of subsites displayed just one parameter of navigation. We’ll go see the definition of navigation components that are in the masterpage.

For this, we will open the site “Tuto” through SharePoint Designer.

We will start by making a copy of the default.master we’ll call “Tuto.master”

Once the new master page added, we must approve it in the masterpage catalog of our application.

Once it is approved, it becomes available in the choice of the masterpage of the site, thus apply our new masterpage and back to SharePoint Designer

We must now extract the file before editing.

Do a search on “ID =” TopNavigationMenu “” to fall on the definition of the global navigation. We arrive on a block like this:


<SharePoint: AspMenu ID = "TopNavigationMenu" runat = "server" DataSourceID = "topSiteMap" EnableViewState = "false" AccessKey = "<% $ Resources: wss, navigation_accesskey%>" Orientation = "Horizontal" StaticDisplayLevels = "2" = MaximumDynamicDisplayLevels "1" DynamicHorizontalOffset = "0" StaticPopoutImageUrl = "/ _layouts / images / menudark.gif" StaticPopoutImageTextFormatString = "" DynamicHoverStyle-BackColor = "# CBE3F0" SkipLinkText = "" StaticSubMenuIndent = "0" CssClass = "ms-topNavContainer">

In this definition, we are interested in two parameters, namely StaticDisplayLevels and MaximumDynamicDisplayLevels

StaticDisplayLevels: Expects an integer greater than 0 as a parameter and indicates how many sublevels should be displayed statically. Modify the field value by replacing the “2” initial by “4”.

As we can see, we now have the first 4 levels that are displayed in the navigation. Let’s put the value “2” before proceeding to the second parameter.

MaximumDynamicDisplayLevels expects an integer greater than or equal to 0 as a parameter and indicates how many sublevels should be displayed dynamically. Modify the field value by replacing the “1” initial by “2”.

As we see, a sublevel was added to the signage. If now we change the field value by replacing the “2” to “3”. We will still see a sublevel extra.

So much for global navigation, now see the quick navigation that is almost identical.

Do a search on “id =” QuickLaunchMenu “” to fall on the definition of quick navigation.We arrive on a block like this.


<SharePoint:AspMenu Id="QuickLaunchMenu" DataSourceId="QuickLaunchSiteMap" runat="server" Orientation="Vertical" StaticDisplayLevels="2" ItemWrap="true" MaximumDynamicDisplayLevels="0" StaticSubMenuIndent="0" SkipLinkText="">

As we see, our two parameters are always present, the only difference being that the default value of MaximumDynamicDisplayLevels is 0.

At the moment our view is similar to that.

For example, we will put the value of MaximumDynamicDisplayLevels to 4. See the result:

Voila, we now increase the number of levels displayed no development.

Christopher.

Article originally posted on 05/05/2010 on Areaprog

Navigation : Pages


When dealing mainly with pages, sometimes we fall on concern caused by a limitation of SharePoint , ie the number of pages displayed in navigation natively.

Indeed, SharePoint limits the number of pages displayed at 50 (here the example in the current browser).

While we have, for example, 51 pages:

To overcome this limitation, we must add a parameter in the configuration file of the web application: the web.config

Start by identifying the part sitemap and especially the 4 nodes “CMS”

 
<siteMap ... >       
<providers> 
..         
<add name="GlobalNavSiteMapProvider" description="CMS provider for Global navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Global" EncodeOutput="true" />         
<add name="CombinedNavSiteMapProvider" description="CMS provider for Combined navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" EncodeOutput="true"/>         
<add name="CurrentNavSiteMapProvider" description="CMS provider for Current navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="true"/>         
<add name="CurrentNavSiteMapProviderNoEncode" description="CMS provider for Current navigation, no encoding of output" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="false" /> 
...       
</providers>     
</siteMap> 

At these nodes we will add a new parameter: DynamicChildLimit

This parameter takes an integer that determines the number of items to display. The default is 50. Set this to 0 removes the limit.

Example:

DynamicChildLimit = “10” to display 10 items
DynamicChildLimit = “60” to display 60 items
DynamicChildLimit = “0” to display all items as we wish now

So we have in our web.config file

 
<add name="GlobalNavSiteMapProvider" description="CMS provider for Global navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Global" EncodeOutput="true" DynamicChildLimit="0" /> 
<add name="CombinedNavSiteMapProvider" description="CMS provider for Combined navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" EncodeOutput="true" DynamicChildLimit="0" /> 
<add name="CurrentNavSiteMapProvider" description="CMS provider for Current navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="true" DynamicChildLimit="0" /> 
<add name="CurrentNavSiteMapProviderNoEncode" description="CMS provider for Current navigation, no encoding of output" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="false" DynamicChildLimit="0" /> 

We save this file (the backup triggers a iisreset automatically) and return to our site

We can see that our 51st page appeared in the navigation.

In the Quick Launch

In the navigation menu at the global navigation

And in the navigation menu at the current browser

Christopher.

Article originally posted on 03/05/2010 on Areaprog

The webpage you are viewing is trying to close the window


For my first tutorial, here is a tip I found while working with pop-ups.

I was developing a custom page with a closing button when I was confronted to this error when I tried to use this closing button.

The context was to have a WebPart with a link opening my custom page as a pop-up, link defined as a basic a href.

 
protected override void CreateChildControls() 
{ 
string urlPage = string.Format("{0}/_layouts/TutoPopUp.aspx",SPContext.Current.Web.Url); 
Controls.Add(new LiteralControl(string.Format(@"<a href='{0}' target='_blank' >Mon lien </a>", urlPage))); 
} 

This link opened my pop-up perfectly. This pop-up was use to confirm an item deletion.

Clicking on « oui », the item was deleted and the « non » button event was called, event used to close the current page.

 
public void btnCancel_Click(object sender, EventArgs e) 
{ 
Page.Response.Write(@"<script language='javascript'> window.close();</script>"); 
} 

And then the error appeared.

This error come from a JavaScript rule : You can close a windows with JavaScript only if this windows was opened with JavaScript

To fix this error, we need to open the pop-up like this.

 
protected override void CreateChildControls() 
{ 
string urlPage = string.Format("{0}/_layouts/TutoPopUp.aspx",SPContext.Current.Web.Url); 
Controls.Add(new LiteralControl(string.Format(@"<a onclick='javascript:window.open (""{0}"");'>Mon lien </a>", urlPage))); 
} 

Christopher.

Article published for the first time the 22nd of April 2010 on Areaprog

Edit Mode on a WebPart


It is sometimes interesting to know if the view of the current WebPart is the display view or the edit view.

I’ve done this little tutorial to show one difference which occur when you work with a Publishing site or not.

Working most of the time with publishing , I’m use to check the status with this test


if (SPContext.Current.FormContext.FormMode == SPControlMode.Edit)

For example, the code below displays 2 lines. The first is always displayed and the second only on edit mode.


protected override void CreateChildControls()
{
string contenuPasSecret = "Je m'appelle Christopher.";
string contenuSecret = "J'ai 26 ans.";
Controls.Add(new LiteralControl(contenuPasSecret));
if (SPContext.Current.FormContext.FormMode == SPControlMode.Edit)
Controls.Add(new LiteralControl(contenuSecret));
}

As you can see on the print screen below, only the first line is displayed.

When we edit the page, we see the second too.

Until there, all was ok!
Then I tried to use this WebPart on a “Team site” and I was surprised to see only one line on the edit mode!

So I’ve done some debug and I found that the error comes from the SPContext.Current.FormContext.FormMode , instead of sending me « Edit » , it send me « Invalid »!


So the tip to perform this check on a Team site is to use the current page’s WebPartManager . Let’s modify our code to have something like this.


protected override void CreateChildControls()
{
WebPartManager wpm = WebPartManager.GetCurrentWebPartManager(Page);
string contenuPasSecret = "Je m'appelle Christopher.";
string contenuSecret = "J'ai 26 ans.";
Controls.Add(new LiteralControl(contenuPasSecret));
if ((SPContext.Current.FormContext.FormMode == SPControlMode.Edit)|| (wpm.DisplayMode.Name.Equals("Design")))
Controls.Add(new LiteralControl(contenuSecret));
}

Now, all is ok! We have our 2 lines displaying in edit mode on both a team site and a publishing site

Christopher.

Article published for the first time the 2nd of June 2010 on Areaprog

Force module update


In one project, it is not uncommon to use a provisioning feature (a feature allowing mass deployment page layouts, images, javascript, css, etc. ..).

Once these files are deployed in your environment, they are accessible via your code so you can make your own design. Moreover, once the files are uploaded, they are still “alive”, nothing prevents you, for example, to go get your css file in the proper library to make changes on the fly and change the rendering of your site. Until then, no worries.

But what does it happend when you deploy an upgrade of your wsp after changing your css file via the interface?

Here, we realize that the css, pages and other documents that we have changed are not updated by the feature! The reason is simple, when you deploy your wsp first, the elements are present in your site (where you put the provisioning feature) and in the 12 hive. Moreover, these elements are ghosted.
When an element is ghosted, SharePoint make the link directly with those in the 12 and not those included in the lists on the site. But once you have changed your css via the interface, the link is changed and now points to the SharePoint file in the site. File is not updated during the update of your wsp since it is the one on the 12 hive which is updated.

This has its good sides as its bad, but if you want to force the update of documents, just add the following snippet in feature receiver.

Upon activation of the feature, the code will retrieve all the data file associated with Elements which are module type (the type used to upload files. See on this subject Sebastien’s tutorial) for upload “manually “.

First, check the presence of 2 using the following



using System.Xml;
using System.Xml.Linq;

Feature receiver content :



public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    try
    {
         
        if (properties != null)
        {
            using (SPSite currentSite = (SPSite) properties.Feature.Parent)
            {
                using (var web = currentSite.OpenWeb())
                {
                    var ElementDefinitions =
                        properties.Definition.GetElementDefinitions(CultureInfo.CurrentCulture);

                    foreach (SPElementDefinition ElementDefinition in ElementDefinitions)
                    {
                        if (ElementDefinition.ElementType == "Module")
                        {
                            Helper.UpdateFilesInModule(ElementDefinition, web);
                        }
                    }
                }

            }
        }

                
    }
    catch
    { 
        //catcher et logger l'erreur
    }
}


Add a class



internal static class Helper
{
    internal static void UpdateFilesInModule(SPElementDefinition elementDefinition, SPWeb web)
    {
        XElement xml = elementDefinition.XmlDefinition.ToXElement();
        XNamespace xmlns = "http://schemas.microsoft.com/sharepoint/";
        string featureDir = elementDefinition.FeatureDefinition.RootDirectory;
        Module module = (from m in xml.DescendantsAndSelf()
                            select new Module
                            {
                                //récupère les données du module courant a partir des données récupérée dans la définition
                                ProvisioningUrl = m.Attribute("Url").Value,
                                PhysicalPath = Path.Combine(featureDir, m.Attribute("Path").Value),
                                Files = (from f in m.Elements(xmlns.GetName("File"))
                                        select new Module.File
                                        {
                                            //recupère les données de chaque fichier présent dans le module
                                            Name = f.Attribute("Url").Value,
                                            Properties = (from p in f.Elements(xmlns.GetName("Property"))
                                                        select p).ToDictionary(
                                                            n => n.Attribute("Name").Value,
                                                            v => v.Attribute("Value").Value)
                                        }).ToArray()
                            }).FirstOrDefault();

        if (module == null)
        {
            return;
        }

        foreach (Module.File file in module.Files)
        {
            string physicalPath = Path.Combine(module.PhysicalPath, file.Name);
            string virtualPath = string.Concat(web.Url, "/", module.ProvisioningUrl, "/", file.Name);

            //verifie si le lien fourni pointe bien vers un fichier présent dans la deature
            if (File.Exists(physicalPath))
            {
                using (StreamReader sreader = new StreamReader(physicalPath))
                {
                    //met le fichier présent sur le site en checkout si ce n'est pas déja le cas
                    if (web.GetFile(virtualPath).CheckOutStatus == SPFile.SPCheckOutStatus.None) web.GetFile(virtualPath).CheckOut();
                    //upload du fichier
                    SPFile spFile = web.Files.Add(virtualPath, sreader.BaseStream, new Hashtable(file.Properties), true);
                    //checkin du fichier (avec "Updated" comme commentaire)
                    spFile.CheckIn("Updated", SPCheckinType.MajorCheckIn);

                    //approuve le fichier (avec "Updated" comme commentaire) si requis
                    if (spFile.Item.ParentList.EnableModeration) spFile.Approve("Updated");

                    spFile.Update();
                }
            }
        }
    }

    public static XElement ToXElement(this XmlNode node)
    {
        XDocument xDoc = new XDocument();

        using (XmlWriter xmlWriter = xDoc.CreateWriter())

            node.WriteTo(xmlWriter);

        return xDoc.Root;

    }
}

public class Module
{
    public string ProvisioningUrl { get; set; }
    public string PhysicalPath { get; set; }
    public File[] Files { get; set; }

    public class File
    {
        public string Name { get; set; }
        public Dictionary Properties { get; set; }
    }
}


Now your files will be updated

Christopher.

Membership Provider


In this tutorial we will see how to use a SQL database to allow visitors to authenticate on our site. For this we will use a Membership Provider .

First create the database using aspnet_regsql.exe which by default is located in the folder “C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727”. At launch, this utility opens a configuration window.

Configure it step by step to create the database “aspnetdb”.

Once the database is created you will see it in SQL Server .

We will create a user who will be master of this new database and serve as a login to our application.

Create the user and give him the database “aspnetdb” by default.

Let’s give him also the role of dbowner .

Once the admin account of the database created, we will add users. For this, we will create a web application management.

Once the application is created, we will add to web.config 3 elements:

1) The connexion string :

 
<connectionStrings>     
<add name="TutoConnectionString" connectionString="Votre connectionstring" providerName="System.Data.SqlClient" />   
</connectionStrings> 

FYI, Visual Studio contains an utility that can build our string .In the Tools tab , choose Connect to database .

Select Microsoft SQL Server.

In the Add Connection window , select our server, inform our login and password and select the aspnetdb database .

If we want to test the connection, the button “Test Connection” is designed for this purpose.

To recover our connection, click on Advanced and retrieve the data source .

2) The membership provider :

In the tag system.web , add the following:s :

 
<membership defaultProvider="TutoMembershipProvider">      
 <providers>         
<add name="TutoMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web,Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
connectionStringName="TutoConnectionString" 
enablePasswordRetrieval="false" 
enablePasswordReset="true" 
requiresQuestionAndAnswer="false" 
applicationName="/" 
requiresUniqueEmail="false" 
passwordFormat="Hashed" 
maxInvalidPasswordAttempts="5" 
minRequiredPasswordLength="1" 
minRequiredNonalphanumericCharacters="0" 
passwordAttemptWindow="10" 
passwordStrengthRegularExpression="" />      
 </providers>     
</membership> 

Those settings are used to establish a level of security passwords. For this tutorial we will do the minimum (length of 1, no regular expression or non-alphanumeric needed).

 minRequiredPasswordLength="1" minRequiredNonalphanumericCharacters="0" passwordStrengthRegularExpression="" 

3) The role provider :

 
<roleManager enabled="true" defaultProvider="TutoRoleProvider">      
<providers>         
<add name="TutoRoleProvider" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="TutoConnectionString"
applicationName="/" />       
</providers>     
</roleManager>
 

These items added to the web.config we can now add users to our database. To do this we will go through the ASP. NET Configuration Visual Studio which is located in the Project tab .


The first operation we must realize is happening in the “Security” tab.

Here we will change the authentication type that is basically in Windows to move it in form .

By selecting “From Internet” we can now add users in the database. After clicking “finish” let’s get back in the Security tab.

By clicking “Create User”, we can add a user to our database.

Once the data is stored, check that the box “Active User” is checked and click on “Create User”.

Our database configuration is now over. Now let’s go for the SharePoint part!

For this tutorial, we will be careful and start from scratch. We will first create the web application that will serve as a side “extranet” that will be accessible via the classic windows authentication .

Leave all default settings (as you see I just specified the port to 2000 and the name of the database to WSS_Content_2000).

Once the application is created, create a new site collection based on a Publishing template.

Here we are before a classic publishing site accessible via the classic windows authentication

We will now create our website which will be accessible by outsiders. Begin by extending our web application.



Be sure to select the Web application that we just created and set the area on “Internet”. When all information is filled in, click “OK”.

Now that our applications are defined, we still need to modify the web.config . Changes made to web.config of the two web applications are identical.

Do a search on:

 </SharePoint> 

We come across a block of code like:

 
</SharePoint>   
<system.web>    
 <securityPolicy>      
 <trustLevel name="WSS_Medium" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_mediumtrust.config" />       
<trustLevel name="WSS_Minimal" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_minimaltrust.config" />     
</securityPolicy>     
<httpHandlers> 

Add the three elements we have already added to our web application, namely the connection string , the membership and the role provider so that our code looks like this.

 
</SharePoint>   
<connectionStrings>     
<add name="TutoConnectionString"  connectionString="Votre connectionstring" providerName="System.Data.SqlClient" />   
</connectionStrings>  
<system.web>     
<membership defaultProvider="TutoMembershipProvider">       
<providers>         
<add name="TutoMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web,Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"              
connectionStringName="TutoConnectionString"              
enablePasswordRetrieval="false"              
enablePasswordReset="true"              
requiresQuestionAndAnswer="false"              
applicationName="/"              
requiresUniqueEmail="false"              
passwordFormat="Hashed"              
maxInvalidPasswordAttempts="5"              
minRequiredPasswordLength="1"              
minRequiredNonalphanumericCharacters="0"              
passwordAttemptWindow="10"              
passwordStrengthRegularExpression="" />       
</providers>     
</membership>     
<roleManager enabled="true" defaultProvider="TutoRoleProvider">       
<providers>         
<add connectionStringName="TutoConnectionString" applicationName="/" name="TutoRoleProvider" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />       
</providers>     
</roleManager>     
<securityPolicy>       
<trustLevel name="WSS_Medium" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_mediumtrust.config" />       
<trustLevel name="WSS_Minimal" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_minimaltrust.config" />     
</securityPolicy> 

Now we must change the authentication provider in the central administration.

Select the “Internet” zone.

On this screen, we will inform the type of authentication on “forms” and the name of our membership provider and role manager .

Once this is filled, we still have a thing to do: give our users the right to log on our website. To do this, go through the extranet site and add “josephine” to our members:

Add the name « josephine » :

After clicking the people picker , waiting a few seconds for “Josephine” to be underlined!

We see that “Josephine” is one of our members. It is now time to test the account by going to the web portal.

We can notice that access to our site is restricted. Introduce the login and the password of “josephine”.

After we clicked Sign In we find ourselves identified as “Josephine” on our website.

Here, you can now give access to external users to your site.

Christopher.

Article originally posted on 05/05/2010 on Areaprog