Here was my problem; I had a SharePoint site which served as a companies intranet and thus had quite a few sub sites and therefore the top navigation bar had quite a few tabs.  Obviously this isn’t an the wall scenario and I’m sure that most people can relate if they’ve seen a company use SharePoint as their main intranet.  The issue was the number of tabs that they had on their top navigation caused the browser, even on LARGE monitors, to have horizontal scroll bar.  This wasn’t cutting it for the client and they needed a solution to wrap the tabs so that their would be multiple lines of intranet site tabs.

I wasn’t originally the developer assigned to this task.  The original developer took the typical approach and looked around to see if we this problem had been previously solved.  The telerik asp.net controls were identified as a solution since it had a navigation control that looked promising.  I was told that this control would automatically wrap the tabs.  Of course…when I was put on the project and tried out the telerik control…i didn’t wrap and I started to shit myself since I was only given a short timeframe to complete the task since the control was SUPPOSED to work.  After I played around a bit as well, I noticed that the telerik navigation control really didn’t style itself well with SharePoint, it looked out of place, even with its “office” theme.

My initial custom approach I thought would be pretty simple, so I added a content editor webpart to the page and began dusting off my javascript skills and trying to edit the DOM.  After about a day, I was ready to pull my hair out for 2 reasons, first being that I couldn’t solve it, and the second was that javascript just flat out blows if you aren’t doing it 5 days a week.  The basic javascript approach was something that I though would work best since all it needed was a content editor webpart and pasting in some code, I could then add that code to the master page.

Being that I had recently learned to love jQuery, I gave that a shot but frankly I am just not that well versed with jQuery to accomplish what I needed, in addition I would have to include jQuery into the master page and thus slow down the render of each page (which was already a complaint of the client).

After roaming the net I stumbled across a little known ASP.NET “browser” file which basically allows you to tell the asp.net web application that for any control that is using X class, pass it through to your adapter class, through the use of control adapters.  All you need is the class you want to inject, and the browser file which tells asp.net to do the replacement.

Here is what the browser file looks like:

<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter controlType="Microsoft.SharePoint.WebControls.AspMenu, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
      adapterType="DTCubed.Navigation.MenuAdapter" />
    </controlAdapters>
  </browser>
</browsers>

 

As you can see I am telling my web application that for the AspMenu control, from the SharePoint.WebControls namespace, replace it with my custom MenuAdapter.

The code for my MenuAdapter class is pretty simple:

namespace DTCubed.Navigation
{
    public class MenuAdapter : ControlAdapter
    {
        private string MAX_TAB_COUNT = ConfigurationManager.AppSettings["MaxTabCountPerLine"].ToString();
 
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            StringWriter stringWriter = new StringWriter(sb);
 
            HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
            base.Render(htmlWriter);
 
            string txt = sb.ToString();
            txt = txt.Replace("<td style=\"width:0px;\"></td>","");
            string txtToFind = "<td onmouseover=\"Menu_HoverStatic(this)\" onmouseout=\"Menu_Unhover(this)\" onkeyup=\"Menu_Key(this)\" id=\"zz1_TopNavigationMenun";
            bool stringFound = true;
 
            int txtFoundIdx = 0;            
            int currentTabCount = 0;
            int currentTabIdx = 0;
            int tabCount = 0;
 
            //check the tab count config setting, if it exists, set our variable, if not, default it.
            if(string.IsNullOrEmpty(MAX_TAB_COUNT))
                tabCount = 12;
            else
                tabCount = Int32.Parse(MAX_TAB_COUNT);
 
            while (stringFound)
            {
                stringFound = false;
 
                txtFoundIdx = txt.IndexOf(txtToFind, currentTabIdx);
 
                if (txtFoundIdx > 0)
                {
                    stringFound = true;
                    currentTabCount++;
                    currentTabIdx = txtFoundIdx + 10;
 
                    if (currentTabCount == tabCount)
                    {
                        currentTabCount = 0;
                        txt = txt.Insert(txtFoundIdx, "</tr><tr>");
                    }
                }
            }
            writer.Write(txt);
        }
 
    }
}

What is going on here is that I am inheriting from the ControlAdapter class and making my own class where I can modify the Render method, basically modifying the output of the AspMenu class.  As you can see in the Render method, I am taking the HTML output of the AspMenu class, searching it, and breaking it after X number of tabs.  I also have it reading from the Web.Config to tell how many tabs to break at so that you can easily change it to meet your needs.

All that is needed to deploy this is to deploy the DLL to the GAC (or the BIN), put the .browser file into the “App_Browser” directory, add the web.config app key for tab count, and do an IIS reset.  The beauty of this is that it will work on any site collection underneath the web application and there are no modifications needed to the sites themselves or master pages.

Below is the zipped up code…technically its packaged as a feature, but the feature doesn’t actually deploy cleanly (I am working on fixing that at the moment). 

DTCubed.Navigation.zip (429.90 kb)


The other week I created a pretty cool web part that displays the top X blog postings that have been viewed on a particular SharePoint blog site.  It turned out that a lot more was involved to get this functionality to work, but in the end I ended up with a decent reusable web part.

Here are the basic steps to achieve the functionality:

  • Create a feature
  • Created a feature receiver which on activate, would look for the Posts list in the blog site and add a “PageViews” column to it which will store the number of times the blog posting has been viewed
  • A custom web part which will read the Posts list and display the top X blog postings based on “PageViews”
  • A custom control which record posting “views” and has to be added to the master page…this piece is really the core of the solution.  Basically what it does is since its on the master page, every page within the site will run this control.  The control has logic to basically only jump into action if the page your viewing is a blog posting.  If the page your viewing is a blog posting, it will then increment the “PageViews” column for the specific posting in the Posts list.

The real struggle was the logic to only record clicks if the page your viewing is a blog posting page and not some other page on the site.  In addition, I need to include some logic to exclude the search crawler, since these views really aren’t legit.

I will post the code up shortly once I clean it up a bit, it will take me a few days to clean up some of the kinks. 


Posted in:   Tags:

A recent MSDN forums question prompted this posting.  Since SharePoint has all these built in web services it is extremely easy to integrate SharePoint with other systems, LOTS of other systems.  Typically people use the NTLM SharePoint site and thus typically don’t worry all too much about authentication of their code when it comes to the SharePoint web services.  Sometimes though, for whatever reason, perhaps you don’t own the SharePoint environment, etc. but your SharePoint site ONLY uses FBA, and thus your stuck with trying to call the SharePoint web services using FBA.  At first this might seem like a daunting task but in reality its really no different/harder than the typical way you would call a SharePoint web service.  The difference is mainly that you need to first call an authentication web service, and then use the cookie result you get back in all your subsequent web service calls.  Enough Rambling…BRING ON THE CODE!!!

Pre Reqs:

Code:

I created a simple console program project and added 2 web references to it:

//authenticate to the web services of a SharePoint FBA site
using (AuthenticationSvc.Authentication authSvc = new AuthenticationSvc.Authentication())
{
    authSvc.Url = @"http://winsvr2003base:14059/_vti_bin/authentication.asmx";
    authSvc.CookieContainer = new System.Net.CookieContainer();     //create a new cookie container
    authSvc.AllowAutoRedirect = true;
 
    //set the FBA login information
    AuthenticationSvc.LoginResult result = authSvc.Login("triplet", "P@ssw0rd");
 
    //check our loginresult to make sure that we don't have any errors
    //if we don't have any errors, then consider us authenticated and we can then call our
    //other SharePoint web services by passing in our authentication cookie
    if (result.ErrorCode == AuthenticationSvc.LoginErrorCode.NoError)
    {
        try
        {
            //now that we're authenticated through FBA try and call the lists web service
            using (ListsSvc.Lists listSvc = new SharePointFBAWebSvcTester.ListsSvc.Lists())
            {
                listSvc.Url = @"http://winsvr2003base:14059/_vti_bin/lists.asmx";
                
                //set our authentication cookie that we got above
                listSvc.CookieContainer = authSvc.CookieContainer;  
                
                //get the lists from the site
                XmlNode listCol = listSvc.GetListCollection();
 
                //dump all the lists in our site
                Console.WriteLine(listCol.InnerXml);
            }
        }
        catch (Exception ex)
        {
           Console.WriteLine("Exception occured while calling lists.asmx" + ex.Message);
        }
    }
    else
        Console.WriteLine("Error authentication to web service.");
}

*** AuthenticationSvc is the name of the web service reference I added to Authentication.asmx

*** ListsSvc is the name of the web service reference I added to Lists.asmx


Pretty simple, huh?  With the basic code above, you should be able to authenticate to the FBA site, and then call whatever SharePoint web service you want.


Posted in:   Tags:

I was doing my daily SharePoint blog reading and ran across a blog posting by Rob Bogue where gets into how he was brought into a client site to look at some custom SharePoint code that they were having a problem with.  Naturally the code wasn’t deployed in the recommended way, nor did it apparently have a visual studio project so he was basically going from ground zero to determine the problem.

 

Here is my take/personal experience with a similar situation(s) and hopefully that will explain the title of this blog posting and drill it into your head.

At a previous client, I was part of a development team working on a high-profile SharePoint internet facing site.  A lot of custom coding was done to achieve various features that were required.  I brought in late to the project and was therefore not there at the beginning to stress the importance of WSP’s.  What we had was a large visual studio project with X number of class libraries that contained our custom code.  Each “feature” was its own class library that would then be packaged into its own DLL.  As you can guess, when it came time to deploy, the “build master” would then take all those DLL’s, add then to the GAC of EVERY SINGLE WEF, manually do the web.config modifications for safe controls, etc. etc.  What we had then was a few hour “deployment” process where mistakes were quite common and multiple hours of work lost. 

How would you solve this above case?  Use features/solutions obviously!  Each custom piece of code could have been written and included in the master feature, along with the web.config modifications, allowing for a single package to be deployed to 1 SharePoint server and let SharePoint use its built in features to distribute that to each server in the farm.  All in all it would have taken a few hours of work at the beginning to build the WSP project into the visual studio project work out the deployment kinks, but after that the deployment should have taken minutes, not hours every time like it did.

Onto the next example, yet again, another client project where we had multiple developers doing their own thing.  Basically it was a set of webparts with some css/xsl and images that needed to be deployed.  Luckily we weren’t at the production deployment stage, but in development we were manually doing things.  Now, I’ll admit, I didn’t follow my own rules exactly here and certainly contributed to the early struggles.  Basically we would move the update files to the _layouts folders, deploy manually to the GAC, etc.  The plan all along was to just get things going ASAP and then when it came close to our first deployment, we would create the proper WSP project/solution and do deployment the right way.  The result was that prior to our first deployment, after all this code had been written, we ended up spending about 2+ days working on just getting the stupid WSP built correctly and deploying correctly.  This EASILY could have been solved if we had just started with a WSP in the first place.

 

Now I won’t sit here and tell you WSP’s are the greatest thing since sliced bread and that they are SOOO easy to build/use…cause I won’t and frankly I feel they are a pain, BUT when it comes to deployment on multiple environments, I feel that the pain is worth it and they work pretty well.  Here is my current list of recommendations:

Start out from day 1 with a WSP project and then continue to use that.  You have a few choices here to help you with that.

I personally use/like STSDev because it basically creates a visual studio class library for me, all the xml files, as well as having build targets built for me that handle the manifest.xml files, DDF, etc. and when I do a rebuild, I get a .wsp file everytime.  I like this because all I need is basic visual studio, and not some extra project templates that not every developer has.  I can package up my solution, send it to someone, and they can open it and work from it right away.  BUT, I do have a few things that I do that I feel help me make my development life easier.

Create 3 bat files in my project that I will use to ease deployment

  • deploy_solution.bat (deploys the solution using stsadm commands)
  • retract_solution.bat (retracts the solution from the farm using stsadm commands)
  • reinstall_dll_gac.bat (reinstalls my code DLL(s) to the GAC and does an IISRESET)

deploy_solution.bat

CLS
 
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN"\stsadm -o addsolution -filename MyCustomCode.wsp
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN"\stsadm -o deploysolution -name MyCustomCode.wsp -immediate -allowGacDeployment -url http://{SITE TO DEPLOY TO} -force
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN"\stsadm -o execadmsvcjobs
 
IISRESET 
 
PAUSE

 

retract_solution.bat

CLS
 
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN"\stsadm -o retractsolution -name MyCustomCode.wsp -immediate -allcontenturls
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN"\stsadm -o execadmsvcjobs
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN"\stsadm -o deletesolution -name MyCustomCode.wsp
 
 
IISRESET
 
PAUSE

 

reinstall_dll_gac.bat

CLS
 
 
GACUTIL.exe /u MyCustomCode
 
GACUTIL.exe /i C:\PROJECTS\MyCustomCode\bin\Debug\MyCustomCode.dll
 
GACUTIL.exe /l MyCustomCode
 
 
iisreset
 
PAUSE

 

Now, once I have my visual studio project (created using STSDEV), I can then use my bat files to easily deploy/retract my WSP from my site (you could also if you want, wrap both these together so it retracts first, then deploys).  This works great when I want to test soup to nuts to make sure my WSP, features, xml files, _layouts, etc. are all going to the right place and there are no errors in my XML files.  BUT, what really comes in handy for my is the reinstlal_dll_gac.bat file.  What this does is reinstalls my DLL into the GAC and does an IISRESET (yes, simple enough).  The benefit here though is once I have my basic of my WSP build, feature.xml, manifest.xml. etc. and I know all those files are kosher, there is NO need to retract/deploy that EVERYTIME i want to test something, therefore only redeploying the code in the GAC is necessary.  I can then change my code, reinstall the dll, load my site, which already has my code on it, and see how the new version functions.

 

What should you as the reader take away from all of this? 

  • Start with a WSP project from day 1 and work out the kinks at the beginning and save yourself the trouble later
  • Use some simple batch scripting to make deployment, as well as functional testing, easier on yourself

Posted in:   Tags:
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017 Tony Testa's World