Filtered Lookups Across Content Types

July 23rd, 2009 by mikes

Creative Use of Calculated Site Columns

This might not have direct application to your situation, but hopefully if will give you some ideas to try…


The Requirement

I recently had to provide a filtered drop down for a content type in a list containing two content types.

The content types were – 'Actions' (parent: Task) and 'Deliverables' (parent: Item).

The filtered drop down needed to be on 'Action', to link it to a ‘Deliverable’ by a lookup column (named 'Deliverable').

This all worked, apart from the fact that you can not only associate a 'Deliverable' to an 'Action', but also another 'Action' to an 'Action', or even the same 'Action'… you can see the rather nasty potential circular reference issues there!

The above screen shot shows two ‘Actions’ in the first page of the ‘Deliverable’ drop down – Business Process Tool Integration and Calendar view for leave, that we don’t want there.

Now, of course, we could add two separate lists for each content type, and a content query web part to combine them.

But in this case the user wanted the full list abilities, particularly views, across both content types, and no more items on the default menus, or messing about with web parts, audiences, security etc.

The Solution

I had to scratch my head for a few minutes, but this is the quick and dirty fix I came up with.

Add a new 'calculated' site column called 'DeliverableTitle', with the formula defined as: =IF([Content Type]="Deliverable",[Title],"")

As this references the same inherited 'Title' field, we'll only see it in a list when the content type is 'Deliverable'. You can make it hidden on any content types if you want, or just never show it in any views.

Now, as I mentioned, these two content types are already in a list called 'Deliverables / Actions'.

If you don't have that already:

   1. Add your two content types, 'Action' and 'Deliverable', without any lookups or calculated fields:

         

   2. Put the 'DeliverableTitle' calculated site column on the 'Deliverable' content type.

     

   3. Put them both on a list that allows management of content types.
 
          

      

We can now create (modify in my case) the lookup site column called 'Deliverable', to get information from the 'Deliverables / Actions' column 'DeliverableTitle'.

If it wasn't already on your equivalent of 'Action', put the 'Deliverable' lookup site column on the 'Action' content type now.

And hey presto, now when adding a new ‘Action', we only ever see ‘Deliverables’ in the lookup.

As can be seen above, the errant Business Process Tool Integration and Calendar view for leave 'Actions' are no longer visible in the ‘Deliverables’ combo box.


Potential Gotchas

Of course the usual lookup column issues still exist, such as no control over ordering.

      Aside: Wouldn't it be nice to be able to point lookups at views, or some other list 'filter' – are you listening Microsoft?!

But there is one issue you may encounter – because of when calculated columns get evaluated, you could see this ‘RENDER FAILED’ issue with collapsed view groupings:
 

To fix this, you can either set the Views ‘Group By – By default – show groupings:’ to be ‘Expanded’…

…or apply this Microsoft Hotfix: http://support.microsoft.com/kb/952698/en-us

      You try to render the view of a custom list. When you do this, you may receive the following error message:
      Render failed
      This issue occurs when the following conditions are true:
      The list is grouped by one of the columns.
      You try to display the sum of all the values of a single column.
      The list contains more than the limit for the number of columns that have a certain data type.
      For example, in Windows SharePoint Services 3.0, the limit for the number of columns that have the "float" data type is 12.
      Therefore, this problem occurs when the following conditions are true:
      The list is grouped by one of the columns.
      You try to display the sum of all the values of a single column.
      The custom list contains 13 columns that have the "float" data type.

Thanks for reading, and I hope this helps those with other curly lookup field issues come up with creative solutions.

The end of another MOSSuMS post

Lies, damn lies and software estimates

July 14th, 2009 by mikes

BEstimation – The Best Practices of Software Estimation

We've all been bitten by poor estimation, whether by our own hand or someone else’s.

I estimate 87.3% of estimations are made up on the spot.

I'm not going to cover yet another estimation method or propose top down or bottom up approaches. Suffice to say there are few ways to estimate a complex or large software project that are little better than 'guestimation' or 'gut feel', however much process and technology is thrown at it. As withnessed by the attempt to apply engineering rigour to a field more unlike engineering than just about any other, the result has always been the same 'Software Crisis'.

Please read this great post (How I Learned to Stop Worrying and Love the Gantt Chart Bomb) for a specific take on the misuse of the evil Gantt Chart.

But what I do want to do is highlight some of the pitfalls that businesses, and IT fall into with regard to estimation. Caveat Emptor – as Baz Luhrmann says, my advice has no basis more reliable than my own meandering experience… I will dispense this advice now. Oh yes, and wear sunscreen.

So here we go, these are the things to adopt as best practice (maybe even estimation 'Governance'), and the reasons why:

1. Treat software estimation as a discrete skill.

It doesn't matter so much if it sits in IT, the Project Office or some other department, but it should be a discreet, recognised and rewarded skill that needs training, mentoring and practice.

Programmers get a degree and years of practice, only to end up as Project Managers, Architects, Analysts and Technical leads. This happens without any formal process. But at least these are seen as specialisations – estimation itself is just lumped into a skill set they are presumed to have as an innate ability without any education or practice. IT managers (you know who you are) prefer to ignore the fact their own very best software estimate was a leap of faith with more in common with art and social science than programming.

In fact, programmers have a very limited view of what goes into delivering software, beyond the code they write. It is also the case that programmers are often ill suited to these roles, including estimation, but that's another post.

2. Close the loop.

Feedback. The results of your chosen estimation method must be compared with the project outcome. Do not presume that the people doing the work were too slow, maybe the estimate was wrong? But in reality it is usually other factors that doom projects to being late or an utter failure, well before a single hour has been estimated or a line of code written. A blame free project wash-up is better than hearing it in a series of exit interviews!

3. Do NOT get those who will do the work to estimate it.

This one is a doozey, but most organisations fall into the trap. It seems to make sense to work to your own estimates, but in reality it has significant problems:

a. Junior, inexperienced or just plain shy staff will match an estimate to their perception of what is expected, to peer or to management pressure. They want to make a good impression, so bust a gut (all-nighters anyone?) to meet those estimates. If they do (without major quality issues) they are rewarded for a bad estimate with a future expectation of very low estimates and too much work. If they don't there is rarely any consideration that their work rate was actually fine, but the estimate was bad. Even if there is, they are still perceived to have failed in part of the 'software delivery' process.

b. More wily workhorses will have a better idea of how long it will take, but will always add a fudge factor, but may still not want to rock the boat or loose face. Again they have extra reason to work to that (their) estimate, even if it can, or can't, be done in half the time.

So what should happen?

Get a skilled software project estimator. Even if they can do the actual work (they probably are, or were, a programmer) they shouldn't. These mythical beasts will factor in all the things us mortals forget, have equations for normal distributions, research on resourcing levels and matrices on coding effort and complexity we just won't understand. Developers will have to provide input for them, and that will be an ongoing task during the project, but the programmers won't come up with the actual impartial numbers.

You will then have a better chance of discovering if the estimate was wrong early on (the dev won't be backward about saying 'you are joking!'), and you will have better feedback afterwards to identify where the process fell down, be it in the project frame, management, estimation, technology, human factors, or software dev. Combining the work and estimate is like changing two variables in an experiment at the same time – you can't hope to separate the influences, so you'll never be able to get good feedback to improve the process.

4. An estimate is costly, so spend it, or don't estimate at all!

If you aren't prepared to spend what could turn out to be a significant amount of time and effort investigating a project, you won't get an estimate worth having. This is a chicken and egg thing – for anything novel, as software projects tend to be, you have to do a reasonable percentage of the work to have a handle on how long it will take to do it all.

My advice here, if you aren't going to spend the time and money on a good estimate, is just get started without one. WHAT I hear you cry! Well, why not spend the time on real planning, architecture and design tasks that will inform your timescale anyway? I've lost track of the times I've given the required estimate, only to be told the delivery date is fixed – why did we waste each other's time in that case?

5. Don't estimate a moving target ,or a mirage.

Sorry about the mixed metaphor there, but there are classes of projects that you just can't estimate. They are complex, high risk, multi-disciplinary, bleeding edge etc. If it fits the definition of a wicked problem, step back. You’re first going to have to get the vision nailed down, get the stakeholders to agree and go through a seemingly never-ending process of meetings, and all project long communication.

I don't mean projects can't change and evolve, but you have to have agreement on what the project is first. Shared understanding, leading to shared commitment. Cognexus, Issue Based Information Systems (aka Dialogue Mapping) and Compendium can help you here.

And if you have a risk section in your process that's in the 'High' segment overall, you may have to sort those out before you can even consider an estimate. Remember, those risks need to impact the bounds, accuracy or confidence level you give to your estimate

But even after all that, you may have to accept that a project just isn't "estimatable" to any worthwhile degree of certainty.

6. Don't deliver an estimate in double figures.

This statement might sound weird at first, but match the scale of the estimate to the figures given. I would expect estimates like 3 days, 1 week, 7 months, 1 year. If you are doing anything that isn't production line, I would argue you can't estimate to any greater level of accuracy. If someone presents you with an estimate of '34 man-days', throw it back. If they hit that it will be a fluke, or worst a carefully orchestrated conspiracy.

Likewise for your confidence level – not that I've ever seen a process where this is much more than a guess, so I generally don't like them, but try to ensure any figures match your capability to calculate those bounds.

My preference is to couch the estimate in the appropriate units to indicate your level of confidence, and give a relative scale if more detail is needed, rather than a figure.

 

Conclusion

What's all this got to do with SharePoint? Well, SharePoint provides a powerful but complex platform – it can be used to leverage your solution, or it can sink you in a world of XML and customisations. It requires rare skills and developer discipline across a wide range of technical areas. So it's a risk factor all in itself and can tip a problem into the 'wicked' space, making estimation at best wildly inaccurate.

I hope I've triggered some estimation déjà-vu with these 'best practices'. Everything I've read on estimation tends to focus on a method or process, and ignores the human element. In software as in any endeavour, you ignore that at your peril. Work with human behaviour, don't ignore or fight against it. So to summarise:

Educate, train, mentor and reward people for improving their estimation skills.

Perform 'no blame' wash-up that aim to improve ALL aspects of the project that will result in improved estimates.

Promote estimation as a discreet job or skill set that is not part of, and does not lead on to, performing the task.

Don't ask for quick estimates if the task isn't quick  – if you can't estimate it, don't force some poor snap to gamble that they can.

If the project is a fluid mass of politics or technical complexity, solve those issues first.

And even then, remember sometimes you have to start on the journey to know if and when you can get to the destination. Software is not yet, and probably never will be, like engineering with it's anchor in the physical world.

Use units people can relate to.

And dance.

The end of another MOSSuMS post

Code Monkey Hates SharePoint

February 23rd, 2009 by mikes

The SharePoint Developer Maturity Model.

The other ‘MS’ has a problem. Those gnarly developers that write your software on the smell off an oily Pizza Box emerge from a four day MOSS training course and sware (and I quote) “I don’t care what they told you, I’m never going to do SharePoint development.”

As Jeremy Thake’s excellent blog post has already covered, Microsoft just threw in the towel when it came to documenting and supporting the product for us SharePoint developers. And the community had to take up the slack (and a heartfelt thankyou to everyone who contributes).

But that’s not the core issue here. It’s one of positioning. In a previous post I asked the open question of where SharePoint is heading. And that’s the rub. Nobody knows (and my guess is even MS has a light hand on the tiller, waiting to steer it down the next big wave). And who will they support best for SharePoint 14 and beyond?

Back to Code Monkey. He saw the MS features and framework not as something to leverage, but as a straight jacket – to him the fact he didn’t have to code something that came OOTB meant he had to re-write it to his liking first (and I won’t go into the mess that results).

In another example, an experienced (and very good) systems support guy went on the SP admin course. But afterwards he still often refused to do things through the SharePoint admin UI – he changed IIS directly. And therefore sometimes things broke when SP didn’t find what it was expecting. He knew how to do it as he used to, and now in SharePoint, but didn’t trust or feel comfortable enough with the product.

So beware who you send on that training course – they may not want to be a SharePointer, them might just want more Fritos. And my experience has been some of the most technically minded and able find it hardest to adjust.

The ‘tongue in cheek’ solution? Buy them some Mountain Dew and get those you want to ‘up-skill’ to answer this short questionnaire before handing over your money and risking unintentional damage (or outright sabotage ) in your SharePoint deployments:

  1. If your Car breaks down, do you first:

a. Call the RAC/AAA
b. Phone a friend
c. Get you wife to walk to the nearest garage
d. Make death threats to the dealer
e. Try to fix it yourself
f.  Cannibalise the burnt out truck you just passed for parts

  2. If someone asks you for directions, is you first answer most likely:

a. Don’t shoot me – here, take my wallet/wife/kids
b. I’ll draw you a map
c. First left, 3rd on the right, straight through the lights, hang a U, then…
d. Why do you want to go there – Luigis is cheaper & does better seafood.
e. You really shouldn’t have come this way, you should have started north of the bridge and stuck to the expressway…
f. 
I’m going to the Overclockers convention as well – just follow me.

  3. When programming your VCR, do you most often find:

a. You get it wrong
b. You get it right, but sometimes turn the power off at the wall afterwards
c. You don’t have a VCR, you use a PVR, iPOD or Portable HD instead.
d. Your Media Centre PC didn’t come out of standby
e. You sent the VCRs RS232 codes without a parity bit
f. 
Even the Ubuntu distro is more l33t than Vista. What’s a VCR?

  4. You find classes are:

a. No longer a relevant social characterisation, or are just an excuse to charge more for an inch of legroom
b. That’s something to do with code isn’t it?
c. A core OO concept, but often too many levels of inheritance and complexity are used.
d. Often sealed in SharePoint which makes some tasks difficult or inelegant
e. Better with multiple inheritance.
f.  We should be using Ruby on Rails.

The Results:

Mostly a and b – should probably stick to the SP UI. Are you sure they are developers?
Mostly c and d – about in the sweet spot of technical eagerness versus pragmatism. Ideal SharePoint Developer or Serial Killer.
Mostly e and f  – do not under any circumstances either talk to this person at a party, or send them on a SharePoint course.

And for the record, here are my answers (draw your own conclusions!): 1.e (but fail so go with a), 2.c, 3.b, 4.d.

NB: Alternatively a 'key learning styles' and 'dominant personality trait' survey will also suffice, plus help you build a well balanced team! Maybe these should be a core OOTB site definition survey in the next MOSS?

The end of another MOSSuMS post

The Server Revolution

February 9th, 2009 by mikes

Moving the server from the data-centre to the desktop.

Lifting my gaze from the code for a few seconds, one of the things that has always stuck me with SharePoint is the similarity its impact has on businesses to the PC Revolution. The same arguments for and against central control, the same immaturity of tools, the same gist of ‘user empowerment’, and the same fears and demarcation lines are surfacing.

I like analogies – they provide a framework and a perspective from which to form a critique. I remember way-back-when we had an illuminating student task to analyse and categorise businesses using metaphors, not just their numbers. So let look at this through the lens of that long past but ongoing Desktop Revolution…

In the SharePoint case the thing that stands out to me as very different to the PC revolution is its breadth– there is no one ‘killer app’ or enabling technology driving this. It’s more a confluence of established concepts, some from MS, but many more from the wider web ecosystem.

Hopefully this starts a discussion – where are we headed?

  • Will SharePoint and its successors become a de faco standard on the Server, as the IBM PC and DOS did on the desktop?
  • Will Microsoft mature its tools to better manage SharePoint customisation, deployment and management?
  • Will that help the data centre wrestle control of important definitions, data and processes back from the end users?
  • Will developers get left out in the cold, as users do more and more, or will there be a backlash due to a lack of rigour?
  • Will a ‘killer app’ appear on the SharePoint server, as the spreadsheet did on the desktop?
  • If so, will there be a VisiCalc/Lout123/Excell style progression?
  • Because of its broad scope, will it only really succeed in a few niche areas?
  • Is MOSS simply a glimpse of the possibilities and challenges in the coming ‘Server Revolution’?

Let me know your thoughts, especially any futurists out there!

NB: As a good example of the discussions the future of SharePoint kicks off, here’s Paul’s take on the direction of the nascent external data access API: http://www.cleverworkarounds.com/2008/03/25/sharepoint-external-storage-api-crushing-my-dream

 

The end of another MOSSuMS post

Multiple Projects, one WSPBuilder Solution

February 9th, 2009 by mikes

When two (projects) become one (sharepoint solution).

When I originally used WSPBuilder, after a great deal of messing about, I discovered there was a bug that prevented the 'BuildSolution' switch from working. That was a pity, as for everthing else "it did exactly what it said on the can".

I recently revisited it, and I'm pleased to report it now works a treat for multiple projects! You have to place then under the same 'WSPBuilder solution' folder in your file system, but using source control bindings this isn't a big issue.

You can find the WSPBuilder site here – just download and run the latest Extensions for Visual Studio installer from here, which includes the command line version as well.

Below is the config file I use:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!– EXISTING SETTINGS –>
    <!–    As in C:Program FilesWSPToolsWSPBuilderExtensionsWSPBuilder.exe.config  –>
    <add key="BuildDDF" value="true" />
    <!–    As in C:Program FilesWSPToolsWSPBuilderExtensionsWSPBuilder.exe.config, but value changed from 3 to 4 for more feedback –>
    <add key="TraceLevel" value="4" />
    <!– MIKE'S EXTRA SETTINGS –>
    <!–    The following key will ensure we build in debug mode. –>
    <add key="BuildMode" value="Debug" />
    <!–    The following key will override the wsp default of files in dev GAC dir going to the gac, and those in the bin dir going to the iis site bin directory. –>
    <add key="DeploymentTarget" value="GAC" />
    <!–    The following key will create a full 12 hive in your projects, so use only if you need all deployable folders –>
    <!–<add key="Createfolders" value="True" /> –>
    <!–    Don't delete intermediate files such as the manifest.xml (don't add these files to source control) –>
    <add key="Cleanup" value="False" />
    <!–    New name proposed for current BuildSolution key –>
    <!–<add key="BuildMultipleProjects" value="True" /> –>
    <!–    Build all projects into one solution –>
    <add key="BuildSolution" value="True" />
    <!–    Solution path – this tells WSP builder to step back one level for the master (startup/build) project to the main 'wsp solution'
            folder, and build all projects within that folder to one wsp (cab). By default this wsp will take the name of the folder it sits
            in, or use the WSPName key (see below) to give your own name. Unfortunately this dictates a rigid project folder structure, so it
            is advised that projects used in more than one wsp solution solutions are subsequently 'referenced/linked' in source control to
            match this client build structure rather than physically copied. They can then be 'project referenced' in Visual Studio, which
            leaves the option of branching them completely if required. Although this promotes re-use, care should be taken as it often
            indicates a new wsp is required to prevent inadvertent upgrades or clashes with already installed features in other solutions.
            If it is a variation, consider branching or copying files (especially feature.xml to ensure guid is used), dll referencing of base
            feature and inheritance.  –>
    <add key="SolutionPath" value=".." />
    <!–    Override the default of using the parent folder's name as the wsp solution name –>
    <!–<add key="WSPName" value="SomeSolutionName" /> –>
  </appSettings>
</configuration> 

This screen shot shows the place of the config for WSPBuilder in the Visual Studio world (your default project's directory, where you choose to 'build wsp'):

Now you can right click on your main project (where the wsp config file lives) to see the following:

So, hey presto, no more excuses for not putting all those pesky little features into your WSP where they belong! You can also name your solution differently to the containing folder using the WSPName setting. To see all the command line arguments type

C:Program FilesWSPToolsWSPBuilderExtensions>wspbuilder -help:full

This isn't fully aligned with the list of optional settings they give below, so take care to test if it is supported in the config, as well as the command line, before you use it.

Optional settings
    You can set the arguments in this file or use them directly in the console.
    All arguments has a defualt value. See wspbuilder -help
    
    <add key="12Path" value="" />
    <add key="80Path" value="" />
    <add key="BinPath" value="" />
    <add key="BuildCAS" value="" />
    <add key="PermissionSetLevel" value="" />
    <add key="BuildSafeControls" value="" />
    <add key="BuildWSP" value="" />
    <add key="Createfolders" value="" />
    <add key="DeploymentTarget" value="" />
    <add key="Destination" value="" />
    <add key="DLLReferencePath" value="" />
    <add key="Excludefiletypes" value="" />
    <add key="ExpandTypes" value="" />
    <add key="Help" value="" />
    <add key="IncludeAssemblies" value="" />
    <add key="IncludeFeatures" value="" />
    <add key="Includefiletypes" value="" />
    <add key="ManifestEncoding" value="" />
    <add key="Outputpath" value="" />
    <add key="ResetWebServer" value="" />
    <add key="Silence" value="" />
    <add key="SolutionId" value="" />
    <add key="SolutionPath" value="" />
    <add key="WSPName" value="" />
       
    TraveLevel switch controls the general messages. In order to 
         receive general trace messages change the value to the 
         appropriate level. 
         Possible values :
         "0" gives no messages
         "1" gives error messages
         "2" gives errors and warnings
         "3" gives information
         "4" gives verbose information 
       
     The default value in WSPBuilder is Information (3).

All I've got to do now is work out how to get wsp builder to deploy to a specific URL, and my dev time STSADM worries have gone!

The end of another MOSSuMS post

Template that

February 9th, 2009 by mikes

Custom Site Definition '3rd way'. 

Do you want a custom site definition, but can't face copying and modifying an ONet? Need to use an OOTB Site Definiton, but also change it? Like features, but hate the issues stapling gives you? Then read on…

The debate about this rages on-and-on (see Microsofts guidelines here, and other developers perspective on upgrade issues here, here and here). Some like features, some like stapling, some like to use the .onet file. Each has its pros and cons. But all agree it would be nice to end up with a deployable sharepoint solution, use features within it, build on the OOTB site definitions (templates), provide a custom site definition the user can easily choose for their new collection, all without hacking a big Onet file. So how to square the circle for us devs?

Raymond Mitchell deserves the credit for this one using web provisioning, as I based my approach on his suggestions and added my preference for Features and WSPBuilder on top of it.

Note: I abandoned my original preference of stapling a master feature within this approach, as I couldn't get the stapled features to activate automatically (stapling happens at farm level, but provisioning is at web level), in the right order or to wait for provisioning to complete. I do think this aspect warrants revisiting as I may have made some mistakes and it would be nice to use more of SharePoint's built-in abilities.

In my implementation I have the following web temp to give us our new site definition:

<?xml version="1.0" encoding="utf-8"?>
<Templates xmlns:ows="Microsoft SharePoint">
 <!– OOTB IDs are listed here: /mossms/archive/2009/02/09/configure-this.aspx –>
 <Template Name="Your.Project.Template" ID="9999999">
   <!– Choose a unique number for this Customisation's ID that is above 10000. –>
        <Configuration
          ID="0"
          Title="Your.Project.Template#0"
          Hidden="FALSE"
          ImageUrl="/_layouts/images/Your.Project/templatePrev.png"
          Description="Custom Your.Project.Template#0 used to provision the OOTB Document Center (BRD#0) before automatic customisation with features."
          DisplayCategory="Custom"
          AllowGlobalFeatureAssociations="TRUE"
          Version="12.0.0.0"
          ProvisionAssembly="Your.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Your.Project.snk"
          ProvisionClass="Your.Project.l033.XML.WebProvisioningProvider">
        </Configuration>
 </Template>
</Templates>

It should be placed in the 12 hive location here:

Have a look at 'Configure this' for a list of existing template IDs, but ensure you use a new unique ID above 10000. This file will have named the custom site definition (template) and points to my provisioning provider shown below:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
 
namespace Your.Project.l033.XML
{
    public class WebProvisioningProvider : SPWebProvisioningProvider
    {
        // Set your existing template and #configuration number here
        public static string WebTemplateConfiguration = "BDR#0";
        // OOTB Configurations are listed here: /mossms/archive/2009/02/09/configure-this.aspx –>
 
        // Set your 'master' site (collection) scope feature here (master feature will activate sub features – can't use feature associations as these don't wait until site is provisioned).
        public static Guid SiteCollectionMasterFeatureId = new Guid("Your-Projects-Site-Master-Feature-Guid");
 
        // Set your 'master' web scope feature here
        public static Guid WebMasterFeatureId = new Guid("Your-Projects-Web-Master-Feature-Guid");
 
        public override void Provision(SPWebProvisioningProperties properties)
        {
            // Provision a site based on the OOTB Document Center site definition – do not dispose the spweb as it it part of the context.
            SPWeb web = properties.Web;
 
            if (WebTemplateConfiguration != null && WebTemplateConfiguration.Trim().Length > 0)
            {
                web.ApplyWebTemplate(WebTemplateConfiguration);
                web.Update();
            }
 
            //Activate the master Site collection scope feature
            if (SiteCollectionMasterFeatureId != null && SiteCollectionMasterFeatureId != Guid.Empty)
            {
                SPSite site = null;  // Must be disposed
                try
                {
                    site = web.Site;
                    site.Features.Add(SiteCollectionMasterFeatureId);
                }
                finally
                {
                    if (site != null)
                    {
                        site.Dispose();
                    }
                }
            }
            //Activate the master Web scope feature
            if (WebMasterFeatureId != null && WebMasterFeatureId != Guid.Empty)
            {
                web.Features.Add(WebMasterFeatureId);
            }
        }
    }
}

I resisted the desire to reference the Stair Master feature, and placed my provisioning provider code here:

 

The provisioning provider will trigger when a new web (when a site collection is created you get the collections root web by default, so it will be triggered then) is created. My example above shows that it optionally provisions an OOTB template (see appendix my previous post for a list of OOTB definitions), activates a master site collection scope feature and a master web scope feature by referencing their Guids. Other scoped features could be added, but there may be some governance/assurance implications in applying features scoped beyond your specific ‘scope’ requirement.

My elements file is empty for now, but the site master feature xml is shown below:

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="Your-Projects-Site-Master-Feature-Guid"
          Title="Your.Project.Features.Site.Master"
          Description="Description for Your.Project.Features.Site.Master MASTER feature"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Site"
          ImageUrl="Your.Project/your.Project.png"
          DefaultResourceFile="core"
          ReceiverAssembly="Your.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Your.Project.snk"
          ReceiverClass="Your.Project.Features.Site.Master.Receiver"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

Remember to replace the Guid and Assembly public key with your own values – this feature lives here in my world:

This is the master site (collection) scope feature receiver code – don' worry about my utils 'using' for a dll reference, it's only used for the logging so comment out that code if you copy from here:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Threading;
//using Your.Project.Features.Site.Utils.Logging; // Comment line out if you copy this code
 
namespace Your.Project.Features.Site.Master
{
    // Single 'master' feature for a site definition. Use this to activate other custom features as stapling does not activate features in order
    public class Receiver : SPFeatureReceiver
    {
        // *** Add your unstapled Site Collection Scope Features here (could be driven from an xml file as this approach matures) ***
        public static Guid[] FeatureGuids = new Guid[] {
            //new Guid("F6924D36-2FA8-4f0b-B16D-06B7250180FA"), // This is the OOTB publishing feature required for style library creation
            new Guid("Your-Projects-Site-Utils-Feature-Guid") // Your.Project.Features.Site.Utils – this is just an example feature
        }; // You can make these features' xml property 'Hidden="TRUE"' once testing is complete
 
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            //Activate Unstapled Site Collection Scope Features (do not wait for root web to be provisioned here, as we will wait forever…)
            SPSite site = properties.Feature.Parent as SPSite;
 
            if (FeatureGuids != null)
            {
                foreach (Guid guid in FeatureGuids)
                {
                    if (guid != null && guid != Guid.Empty)
                    {
                        try
                        {
                            site.Features.Add(guid, true);
                        }
                        catch (Exception ex)
                        {
                            //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                        }
                    }
                }
            }
        }
 
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            SPSite site = properties.Feature.Parent as SPSite;
 
            //Deactivate the master web scope feature on all subwebs in reverse subweb order
            if (Your.Project.l033.XML.WebProvisioningProvider.WebMasterFeatureId != null && Your.Project.l033.XML.WebProvisioningProvider.WebMasterFeatureId != Guid.Empty)
            {
                for (int i = site.AllWebs.Count -1; i >= 0; –i)
                {
                    SPWeb web = null;
                    try
                    {
                        web = site.AllWebsIdea;
                        web.Features.Remove(Your.Project.l033.XML.WebProvisioningProvider.WebMasterFeatureId, true);
                    }
                    catch (Exception ex)
                    {
                        //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                    }
                    finally
                    {
                        if (web != null)
                        {
                            web.Dispose();
                        }
                    }
                }
            }
 
            //Deactivate unstapled Site collection scope features in reverse order
            if (FeatureGuids != null)
            {
                for (int i = FeatureGuids.Length -1; i >= 0; –i)
                {
                    try
                    {
                        Guid guid = FeatureGuidsIdea;
                        SPFeature feature = site.Features[guid];
                        site.Features.Remove(guid, true);
                    }
                    catch (Exception ex)
                    {
                        //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                    }
                }
            }
        }
 
        public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}
    }
}

..and you can find it here – after this I think you'll have the idea of where I locate my code and the xml 12 hive locations, so I'll stop posting my project structure:

 

 

 

 

 

This activates all site collection features in order (otherwise dependencies may cause activation issues) and deactives them in reverse. It also deactivates and web features when you pull the site master by call in the web master feature I'll cover next. That probably isn't necessary, but it seems to prevent issues for me.

So below is the master web scope feature xml and code, activating and deactivating web features in a similar way to the site master, but first waiting for web provisioning to complete. This ensures all OOTB items are present for us to find/modify with our features. NB. If timing issue occur between site collection and web scoped feature activation, it could be done in the single web master feature below, with wider scopes activated first BEFORE we wait for OOTB web provisioning to complete.

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="Your-Projects-Web-Master-Feature-Guid"
          Title="Your.Project.Features.Web.Master"
          Description="Description for Your.Project.Features.Web.Master MASTER feature"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          ImageUrl="Your.Project/your.Project.png"
          DefaultResourceFile="core"
          ReceiverAssembly="Your.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Your.Project.snk"
          ReceiverClass="Your.Project.Features.Web.Master.Receiver"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

…and the receiver code this feature points to:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Threading;
//using Your.Project.Features.Site.Utils.Logging; // Comment line out if you copy this code
 
namespace Your.Project.Features.Web.Master
{
    // Single 'master' feature for a site definition. Use this to activate other custom features as stapling does not activate features in order and doesn't wait for
rovisioning to complete
    public class Receiver : SPFeatureReceiver
    {
        //Add your unstapled Web Scope Features here (could be driven from an xml file as this approach matures) – these are examples of the sorts of features you can add
        public static Guid[] FeatureGuids = new Guid[] {
            new Guid("Your-Projects-Web-Customisation-Feature-Guid"), // Your.Project.Features.Web.Customisation
            new Guid("Your-Projects-Web-ListInstances-Feature-Guid"), // Your.Project.Features.Web.ListInstances
            new Guid("Your-Projects-Web-Branding-Feature-Guid") // Your.Project.Features.Web.Branding
            //new Guid("fc5e2840-0b48-42eb-9ad7-076f5add58ad") // OOTB HIDE the view all site content link feature
        }; // You can make these features' xml property 'Hidden="TRUE"' once testing is complete
 
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            // Ensure Web is provisioned (otherwise provisioning may not complete before feature activation starts)
            SPWeb web = properties.Feature.Parent as SPWeb;
            while (!web.Provisioned)
                Thread.Sleep(1000);     
 
            //Activate Unstapled Web Scope Features
            if( FeatureGuids != null )
            {
                foreach (Guid guid in FeatureGuids)
                {
                    if (guid != null && guid != Guid.Empty)
                    {
                        try
                        {
                            web.Features.Add(guid, true);
                        }
                        catch (Exception ex)
                        {
                            //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                        }
                    }
                }
            }
        }
 
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            //Deactivate Unstapled Web Scope Features in reverse order
            SPWeb web = properties.Feature.Parent as SPWeb;
 
            if (FeatureGuids != null)
            {
                for (int i = FeatureGuids.Length – 1; i >= 0; –i)
                {
                    try
                    {
                        Guid guid = FeatureGuidsIdea;
                        SPFeature feature = web.Features[guid];
                        web.Features.Remove(guid, true);
                    }
                    catch (Exception ex)
                    {
                        //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                    }
                }
            }
        }
 
        public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}
    }
}

So once this has been done, each new feature simply requires adding into the correct master sope features code. I would rather maintain the flexibility of reproducing this simple process for each project, but this procedure could easily be extended to provide some automation, resources or configuration file driven facilities (eg. an xml file to choose the OOTB configuration and list each feature, grouped by scope, in their desired order).

The end of another MOSSuMS post

Configure this

February 8th, 2009 by mikes

A List of WSS and MOSS Site Definitions and Configurations. 

The other 'MS' have a list on OOTB content types here: http://msdn.microsoft.com/en-us/library/ms452896.aspx ( have a look in C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESctypesctypeswss.xml), but I though I would share another piece of reference material I often use – all those "Out Of The Box" Site Definition Configurations. You know, all those 'site template' choices you get when you create a new site collection…

They are normally found in the 12 hive location: C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATESiteTemplates.

If your desired definition isn't listed, save site as a template (.stp), extract the manifest.xml file and locate the <Details><TemplateID>entry to find the number show below.

Remember that saving as a template won't replicate a full site definition (many customisations, such as content types, won't be there), so if it's a hand customised site you will have to reverse-engineer it yourself, or use one of the 3rd party or MS dev tools, to help you create a definition from it in Visual Studio. I'll cover my approach to using these in a later blog post.

WSS Templates (with configuration #)

·         0 – Global: GLOBAL#0 (SetupPath=global) – “Global template”
·         1 – Team Site: STS#0 – “Team Site”
·         1 – Blank Site: STS#1 – “Blank Site”
·         1 – Document Workspace: STS#2 – “Document Workspace”
·         2 – Basic Meeting Workspace: MPS#0 – “Basic Meeting Workspace”
·         2 – Blank Meeting Workspace: MPS#1 – “Blank Meeting Workspace”
·         2 – Decision Meeting Workspace: MPS#2 – “Decision Meeting Workspace”
·         2 – Social Meeting Workspace: MPS#3 – “Social Meeting Workspace”
·         2 – Multiple Meeting Workspace: MPS#4 – “Multipage Meeting Workspace”
·         3 – Central Administration: CENTRALADMIN – “Central Admin Site”
·         4 – Wiki Site: WIKI#0 – “Wiki Site”
·         9 – Blog Site: BLOG#0 – “Blog”

MOSS Templates (with configuration #)

·         7 – Document Centre: BDR#0 – “Document Center”
·         20 – SharePoint Portal: SPS (OBSOLETE) – “SharePoint Portal Server Site”
·         21 – Personal Space: SPSPERS – “SharePoint Portal Server Personal Space”
·         22 – Personalisation Site: SPSMSITE#0 – “Personalization Site”
·         30 – Contents Area: SPSTOC (OBSOLETE) – “Contents area Template”
·         31 – Topic Area: SPSTOPIC (OBSOLETE) – “Topic area template”
·         32 – News Area: SPSNEWS (OBSOLETE) – “News area template”
·         33 – News Site: SPSNHOME#0 (SubWebOnly) – “News Home template”
·         34 – Site Directory: SPSSITE#0 (SetupPath = SPSSITES) – “Site Directory Area”
·         36 – Community Area: SPSCOMMU (OBSOLETE) – “Community area template”
·         38 – Report Centre: SPSREPORTCENTER#0 – “Report Center Site”
·         39 – Publishing Site: CMSPUBLISHING#0 (SetupPath =PUBLISHING) – “Publishing and Team Collaboration Site”
·         40 – OSRV – “Shared Services Administration Site”
·         47 – Collaboration Portal: SPSPORTAL#0 – “Corporate Intranet Site”
·         50 – Search Centre with Tabs: SRCHCEN#0 – “Search Center”
·         51 – Profiles: PROFILES – “Profiles”
·         52 – Publishing Portal: BLANKINTERNETCONTAINER#0 – “Internet Presence Web Site”
·         53 – Publishing Site with Workflow: BLANKINTERNET#2 – “Press Releases Site”, “Publishing Site”
·         54 – My Site Host: SPSMSITEHOST#0 – “My Site Host”
·         90 – Search Centre: SRCHCENTERLITE#0 – “Search Center Lite”
·         6221 – Project Web Access Site: PWA#0  - “Project Web Access Site”
·         6215 – Project Workspace: PWS#0  - “Project Workspace”
·         14483 – Records Repository: OFFILE#0  - “Records Repository”

UI Templates

I haven't yet gone through the process of mapping them to the above, but here is a list of the site templates you see in the UI, and the 'type' icons associated with them in the hierarchical list view:

     Site
Publishing Portal
Enterprise – Personalisation Site
Enterprise – Report Centre
Enterprise – Search Centre with Tabs
Enterprise – Site Director
Document Centre (created with portal site)
News (created with portal site)
Publishing- News Site
Publishing- Publishing Site
Publishing- Publishing Site with Workflow

    Sharepoint Site
Collaboration Blank Site
Collaboration Blog
Collaboration GroupBoard Workspace
Collaboration Team Site
Collaboration Wiki Site
Enterprise – Document Centre
Enterprise – Records Centre
Enterprise – Search Centre

Document Workspace
Collaboration Document Workspace

    Single Meeting Workspace or   Recurring Meeting Workspace
Basic Meeting Workspace
Blank Meeting Workspace
Decision Meeting Workspace
Multipage Meeting Workspace
Meeting Workspace Workspace

Microsoft's UI Site Templates List is at:
http://sharepoint.microsoft.com/blogs/GetThePoint/Lists/Posts/Post.aspx?ID=65

The end of another MOSSuMS post

So you think you can change your Web Config?

March 18th, 2008 by mikes

or 'Making a Feature of SPWebConfigModifications'

Part 1 - A Winner from SharePoint

I know, we've all done it. Jumped into the web config and messed it about for our own evil ends (and we might even have taken a backup first).

Well in the brave new SharePoint world, beyond your 'playpen' dev box, that just ain't allowed!

So what do you do when there's a must have app setting you can't/won't deploy via a WSP solution (please oh please leave your solution to do all the standard things like safe control entries) and you want them to follow wherever your Feature goes?

NB: MrCleverWorkArounds, the Guru of Governance, tells me there may be some issues with what I'm doing here if the feature isn't at WebApplication or Farm scope (as you would expect messing about with web apps configs), so expect a review of this once he's run his strict ruler over it!

Well, there is a little known (it has been news to a couple of SharePoint Gods I occasionally ask silly questions of) part of the object model to help us.

On each web app (Microsoft.SharePoint.Administration.SPWebApplication) there is a property called WebConfigModifications which is a collection of these guys: Microsoft.SharePoint.Administration.SPWebConfigModification

They are just the ticket! You can add and remove modifications to the collection, they don't break if the entry is already in there or isn't there to be removed. If you want to you can even mess with things like debug settings.

spWebApp.WebConfigModifications.Add(configMod1);

spWebApp.WebConfigModifications.Remove(configMod2);

spWebApp.WebConfigModifications.Remove(configMod2);

spWebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();  // propagate across farm
spWebApp.Update();

So the logical next step is to add them in your FeatureActivated and remove them in your FeatureDeactivating SPFeatureReceiver events.

Ah, this is where we hit some small snags:

1. We don't really want to hardcode the updates
2. We only want to remove those from our feature, so probably need to remember a SPWebConfigModification.Owner property.
3. The SPWebConfigModification Class seems to rely on us setting xPath strings to do its things.
4. There are some other properties we need to set.

But fear not. I wrote some simple XML serialisable classes for the base entries, app settings, sections and connection strings. That way I can build those classes up, and serialise them to a file in code, to give you a nice guaranteed valid starting structure. Here's an example of the XML that results:

<?

xml version="1.0"?>
<
SPwebconfigMods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <
Owner>mrwaUtils.UtilsFeatureReceiver</Owner>
     <
Entries>
          <
Entry xsi:type="AppSettingsAddEntry">
               <
Name>mrwaUtils.Logging.Log+DestinationTypes.EventLog</Name>
               <
Value>MACHINE01</Value>
               <
Xpath>//configuration/appSettings</Xpath>
               <
ModificationType>EnsureChildNode</ModificationType>
               <
Sequence>10</Sequence>
               <
SetValueFromEnvVar>true</SetValueFromEnvVar>
          </
Entry>
     </Entries>
</
SPwebconfigMods>

…and this can be deployed with your Feature as a file, so your receiver can find it.

I'll go through this XML from my class, as it also nicely details the properties for the SPWebConfigModification class that does the real work:

The AppSettingsAddEntry class contains all the entries we need for an app setting (specialised from my base Entry class) with properties, such as the xpath, defaulting to the correct value so we don't have to worry about setting them or making a mistake – you can set them to something else if you want to though.

My containing class SPwebconfigMods class has the owner (this could be the feature GUID, but I've just gone with the Feature's Receiver type). This is the one small difference between my object graph and the SPWebConfigModification class - I only put the owner on the container class, SPwebconfigMods, and all mods will use that. Just remember it actually lives on the SPWebConfigModification class.

Name and Value are my base properties for the simplest NVP Entry style, and match those on the SharePoint class. Where my subclasses have more properties (such as connection strings), at serialise time they are concatenated onto the name/value properties using an override and serialised out/in.

The ModificationType can have 3 values: EnsureChildNode, EnsureAttribute and EnsureSection.

The Sequence determines the order they are applied in, so if you have a section you have to add, ensure it's number is lower than the values it contains. In the above example this class defaults to 10 as it is in the second level of the xpath – if there were sub items I'd set those classes to 20 be default, and the parent to 0 etc.

The SetValueFromEnvVar is one of my own additions, so don't worry about it too much. Suffice to say, this allows me to pick up environment variables, from a batch file of via policies, at actiuvation time. This is an optional way I make releasing the same image accross different enviroments more manageable – the downside is you don't have a record of what was set in the SPwebconfig.xml file. Therefore the results in your web config could be different next time the feature is activated. This is different to actually placing a %SomeEnvVar% ref in the web config, which could then vary every time accessed. My preferece is not to do this, but to read from env vars at file creation time, so they are recorded and fixed after the first install. You pays yer money…

NB2: I've seen a project, I think on codeplex, that allows the management of these updates through a web interface – a very nice idea if you are comfortable with the parameters (more of those later). Obviously add/removing features that change these will mean you loose those interactive changes, so if I get a change I'll see if that project has an export/view facility so we can get them back into our feature (perhaps with some XSLT).  I think Ted Pattison is the originator of that project. The Kid also has a page/solution you can download to edit these interactively from within sharepoint.

So how does this end up looking in my feature receiver? Every time I need web config changes in a feature I inherit from my base class like this:

public

class UtilsFeatureReceiver : ConfigSPFeatureReceiver
{
   
    public override SPwebconfigMods SPwebconfigModsFeatureActivatedFileEntries(Guid featureGuid)
    {
        // Optional method used to create SPwebConfigMods if xml file not found
        // Try to get the required setting from environment variables:
        string machineNameKey = "mrwaUtils.Logging.Log+DestinationTypes.EventLog";
        string machineName = Environment.GetEnvironmentVariable(machineNameKey);
        if (machineName == null || machineName.Trim().Length == 0)
        machineName =
Diagnose.GetCurrentMachineName();
        AppSettingsAddEntry cmAsae1 = new AppSettingsAddEntry(machineNameKey, machineName);
        cmAsae1.SetValueFromEnvVar =
true;

        Entry[] entries = new Entry[] { cmAsae1 };
        return new SPwebconfigMods(entries);
    }

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        base.FeatureActivated(properties, true); // Call required for config update addition if inheriting from ConfigSPFeatureReceiver AND overriding this method

        // Do anything else you want to on Activation here

    }

    public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
    {
        base.FeatureDeactivating(properties); // Call required for config update removal if you inherit from ConfigSPFeatureReceiver AND overriding this method

        // Do anything else you want to on Deactivation here

    }
}

 

That all for now. In Part 2 I'll look a the makeup of my classes behind this, to better illustrate how to use the SharePoint config modifications yourself.

A quick update on part 2 progress:

Ryan McIntyre has also just blogged about similar things he did with this useful little class in the past, and I've been extending my solution a little also (to read/write the xml files to the feature's dir in the vs project and final 12 hive, and to read environment variables that can be pushed out by policies when the person installing the WSP needs different web.config settings per deploy). He mentions codeplex, so if others feel they might make use of a web config mods project, let us know. We'll try to put our heads together and come up with the best bits.

 

The end of another MOSSuMS post

Custom List Feature Definition

March 10th, 2008 by mikes

I'm a Feature Creature

I wanted to add a single basic Custom List Template and Instance to my Feature, based on the built in Generic List. No new pages, content types, site columns or the such. Of course, the simple way is to create it in SharePoint and export the STP, but that's not what I wanted (pity this fool). I wanted to achieve the same result using my xml definitions and schema, and keep the normal feature directory structure in the 12 hive.

I thought it was the simplest thing to add into a feature, but after getting annoyed with the MS Sharepoint Extensions for VS2005 not doing what I wanted (adding EditForm.aspx etc), and settling on the wonderful WSPBuilder (http://www.codeplex.com/wspbuilder, http://keutmann.blogspot.com/2008/02/visual-studio-addin-and-templates-for.html) for my solutions, I had to resort to my fellow bloggers as usual. BTW I've been told by MrCleverWorkArounds  – and I believe him as always – that STSDEV is the way to go for my WSP solutions, so I'll try that out next time and report back!

So firstly I'd like to thank the following for pointing me in the right general direction – all excellent reading, especially if your situation doesn't exactly match mine:
http://www.heathersolomon.com/blog/articles/1300.aspx
http://jopx.blogspot.com/2007/05/sharepoint-2007-how-to-create-custom.html

MS also provides this, but most people have issues there because of missing and ambiguous information.

I'm not going to go through all the heartache involved, but here are the salient parts that got me my result and actually displayed the fields I added. There may be things in here I didn't need to do, but life is too short for me to work out exactly which ones!

So off we go.I have this structure in VS 2005. Don't worry about bloggers who say you have to have items in a flatter structure at the feature level, as we'll edit the schema appropriately later. I like this structure, and so does WSP builder, so I'd recommend you stick with it.

project – mrwaUtils
      folder – 12
            folder – TEMPLATE
                  folder – FEATURES
                        folder – mrwaUtils
                              file – feature.xml
                              folder – lSettings
                                    file – ListTemplateElements.xml
                                    file – ListInstanceElements.xml
                                    file – schema.xml
 

My feature.xml looks like this:

<?xml version="1.0" encoding="utf-8"?>
<
Feature xmlns=http://schemas.microsoft.com/sharepoint/
         Id="place-a-new-feature-GUID-here"

        
Scope="Site"
         Hidden="FALSE"
         Title="mrwaUtils Lists"
         Version="1.0.0.0"
         DefaultResourceFile="core"
         ReceiverAssembly="mrwaUtils, Version=1.0.0.0, Culture=neutral, PublicKeyToken=hexKeyTokenValue, processorArchitecture=MSIL"
         ReceiverClass="mrwaUtils.UtilsFeatureReceiver">

     <ElementManifests>
          <
ElementManifest Location="lSettingsListTemplateElements.xml" />
          <
ElementManifest Location="lSettingsListInstanceElements.xml" />      
          <
ElementFile Location="lSettingsschema.xml" />
     </
ElementManifests>
</
Feature>
 

The ListTemplateElements.xml file has a type of 100 (we aren't defining our own type, so don't need a number greater than 10000), I didn't want any attachments to my list so have disabled them, and I also set the feature Id as I use that to delete the list instance(s) on deactivation in the feature receiver class:  

<?xml version="1.0" encoding="utf-8"?>
<
Elements Id="place-a-new-template-elements-GUID-here"
          xmlns=http://schemas.microsoft.com/sharepoint/>
     <ListTemplate Name="lSettings"
               
   
DisplayName="lSettings"
                   Type="100"
                   Description="Single list of settings, their value and if they are active"
                   BaseType="0"
                  
OnQuickLaunch="TRUE"
                  
SecurityBits="11
                   Sequence="410"
                  
Image="/_layouts/images/itgen.gif"
                   Unique="True"
              
   
DisableAttachments="True"
                   FeatureId="place-your-existing-feature-xml-GUID-here" />
</Elements>

.and the ListInstanceElements.xml file, with a single example entry populating the three fields I'll add to my schema file next:
 

<?xml version="1.0" encoding="utf-8"?>
<
Elements Id="place-a-new-instance-elements-GUID-here"
          xmlns="http://schemas.microsoft.com/sharepoint/">
     <
ListInstance Title="lSettings"
                   Description="Singleton list instance of lSettings template" 
                   Url="Lists/lSettings"
                   FeatureId="place-your-existing-feature-xml-GUID-here"
                   OnQuickLaunch="True"
                   TemplateType="100">
          <
Data>
               <
Rows>
                    <
Row>
                         <
Field Name="Title">mrwaUtils.Log+DestTypes.EventLog</Field>
                         <
Field Name="Value">MACHINE01</Field>                            
                         <
Field Name="Active">True</Field>                    
                    </
Row>                       
                    …               
               </
Rows>           
          </
Data>    
     </
ListInstance>
</
Elements>
 

Notice that the URL falls under Lists/, but unlike mine, your list instance name doesn't have to be the same as your list template name.I've also set the featureId owning guid – I'm not sure if SharePoint will do this for me anyway – but with this here it is an easy task to remove any list instances from the feature in the receiver deactivating event using a common piece of code.

And now to the monster, the schema.xml Ick!.

I found the best place to start was that installed with the MS SP extensions, so take a copy from your install. In my case that was 'C:Program FilesMicrosoft Visual Studio 8Common7IDEProjectTemplatesCacheExpCSharpSharePointListDefinition.zipListDefinitionsGenericListschema.xml'.

The first things that need changing in the schema.xml file are the List element properties:  

<?xml version="1.0"?>

<!– Removed for lSettings –>
<!–
<List xmlns:ows="Microsoft SharePoint" Title="$projectname$"
FolderCreation="FALSE" Direction="$Resources:Direction;"
Url="Lists/$safeprojectname$" BaseType="0"
xmlns="http://schemas.microsoft.com/sharepoint/">
–>
<!–
/Removed for lSettings –>

<!– Added for lSettings –>
<!–
This schema.xml is based on the generic list schema found at %ProgramFiles%Microsoft Visual Studio 8Common7IDEProjectTemplatesCacheExpCSharpSharePointListDefinition.zipListDefinitionsGenericListschema.xml –>
<
List xmlns:ows="Microsoft SharePoint"
      Title="lSettings"
     
FolderCreation="FALSE"
      Direction="$Resources:Direction;"
      Url="Lists/mwaUtils/lSettings"
    
 BaseType="0"
      Name="lSettings"
      Id="place-your-existing-template-elements-GUID-here"
      Type="100"
    
 xmlns="http://schemas.microsoft.com/sharepoint/">
<!– /Added for lSettings –>
<!–
_filecategory="ListDefinition" _filetype="Schema" _filename="schema.xml" _uniqueid="$guid10$" –>
<
MetaData>

The important points in bold above are: set the list name and title to be that of your list template (which is the same as the features sub-folder we chose), add your feature name and title into the Url, place your feature TEMPLATE GUID as the Id, and add a Type in of 100.

Now we'll move down a line to the content types section: 

<ContentTypes>
     <!–
Removed for lSettings –>                  
     <!–
<ContentTypeRef ID="0×01">                      
               <Folder TargetName="Item"/>               
     </ContentTypeRef>             
     <ContentTypeRef ID="0×0120"/>
–>               
     <!–
/Removed for lSettings –>
</ContentTypes>

Just comment it all out – easy. On to the next line, Fields:  

<Fields>               
     <!–
Added for lSettings –>
     <!– More Info: http://msdn2.microsoft.com/en-us/library/aa543477.aspx –>             
     <
Field List="lSettings"
            Name="Value"
            Title="Value"
            StaticName="Value"
            Type="Text"
            MaxLength="255"
            DisplayName="Value"
            ID="{place-a-new-field-GUID-here}"
            FillInChoice="TRUE"
            ShowInDisplayForm="TRUE"
            ShowInListSettings="TRUE"
            ShowInViewForms="TRUE"
            ShowInNewForm="TRUE"
            Sealed="TRUE"
            ReadOnly="FALSE"
            ShowInEditForm="TRUE"
           
Viewable="TRUE"
            Hidden="FALSE"
            SourceID=http://schemas.microsoft.com/sharepoint/v3
           
ColName="nvarchar3"
            RowOrdinal="0" />

     <Field List="lSettings"
            Name="Active"
            Title="Active"
            StaticName="Active"
            Type="Choice"
            DisplayName="Active"
            ID="{place-a-new-field-GUID-here}"
            FillInChoice="FALSE"
            ShowInDisplayForm="TRUE"
            ShowInListSettings="TRUE" 
            ShowInViewForms="TRUE"
            ShowInNewForm="TRUE"
            Sealed="TRUE"
            Required="TRUE"
            ReadOnly="FALSE"
            ShowInEditForm="TRUE"
            Viewable="TRUE"
            Hidden="FALSE"
            SourceID="http://schemas.microsoft.com/sharepoint/v3"
           
ColName="nvarchar4"
            RowOrdinal="0" >                       
          <
CHOICES>
               <
CHOICE>Yes</CHOICE>
               <
CHOICE>No</CHOICE>                      
          </
CHOICES>                   
          <
MAPPINGS>                         
               <
MAPPING Value="true">Yes</MAPPING>                        
               <
MAPPING Value="false">No</MAPPING>                  
          </
MAPPINGS>                  
          <
Default>Yes</Default>             
     </
Field>               
     <!–
/Added for lSettings –>
</
Fields>  

Now, I know I've set almost every property under the sun for my fields, but I was playing safe! Many of these aren't needed, but I'm not strong enough to face working out which ones.

Apart from the default title field (which we don't need to add), I wanted a simple text field called Value, and a yes no choice field called active equating to the values true/false. Have a look at http://msdn2.microsoft.com/en-us/library/ms437580.aspx for a list of types and the other properties you can set here.

OK, we're half way there with this schema monster. Now we've got to find the first of two entries called <ViewFields> in the first of our two views (titled <View BaseViewID="0" Type="HTML">) – use find as it's about halfway through the file:  

<ViewFields>                             
     <
FieldRef Name="LinkTitleNoMenu"></FieldRef>                           
     <!–
Added for lSettings –>                         
     <
FieldRef  Name="Value" ></FieldRef>                             
     <
FieldRef  Name="Active" ></FieldRef>                            
     <!–
/Added for lSettings –>

</ViewFields>

All I've done is add my two field names as fieldrefs under the existing field ref entry. Move down to the next line and change the base view order (if you want to):
  

<Query>                            
     <
OrderBy>
          <!–
Removed for lSettings –>                                   
          <!–
<FieldRef Name="Modified" Ascending="FALSE"></FieldRef> –>                               
          <!–
/Removed for lSettings –>                                  
          <!–
Added for lSettings –>                                
          <
FieldRef Name="Title" Ascending="TRUE"></FieldRef>                                
          <!–
/Added for lSettings –>                        
     </
OrderBy>

</Query>

I've replaced the modified sort with a title sort. We need to alter the next line as well to create a default AllItems view:  

<!– Removed for lSettings –>
<!–
<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx"><!– _locID@DisplayName="camlidCu2" _locComment=" " –>               
<!–
/Removed for lSettings –>                
<!–
Added for lSettings –>             
<
View BaseViewID="1"
      Type="HTML"
      WebPartZoneID="Main"
      DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;"
      DefaultView="TRUE"
      ImageUrl="/_layouts/images/generic.png"
     
Url="AllItems.aspx"
      SetupPath="pagesviewpage.aspx" >

<!– /Added for lSettings –> 

I added the setup path pagesviewpage.aspx – that will give us the default generated forms, rather than having to code our own. Now find the next/last <ViewFields> entry: 

<ViewFields>     
     <!–
Removed for lSettings –>                             
     <!–
<FieldRef Name="Attachments"></FieldRef> –>                            
          <!–
/Removed for lSettings –>                            
     <
FieldRef Name="LinkTitle"></FieldRef>                           
     <!–
Added for lSettings –>                         
     <
FieldRef  Name="Value" ></FieldRef>                             
     <
FieldRef  Name="Active" ></FieldRef>                            
     <!–
/Added for lSettings –>

</ViewFields>

Once again, paste in your new fields. Put them under the existing linkTitle fieldref. Also, as I didn't want attachments, I commented out the attachments field ref. Now move to the next line in the file:  

<Query>
     <
OrderBy>                                
          <!–
Removed for lSettings –>                                   
         
<FieldRef Name="ID"></FieldRef> –>                               
          <!–
/Removed for lSettings –>                                  
          <!–
Added for lSettings –>                               
          <
FieldRef Name="Title" Ascending="TRUE"></FieldRef>                                
          <!–
/Added for lSettings –>                        
     </
OrderBy>

</Query>

I wanted my entries ordered by title, so adding this gives me the sort I want in the AllItems view. If you don't care about ordering don't edit that bit. Lastly, the next few lines need editing:  

     </View>          
</
Views>         
<
Forms>
     <!–
Removed for lSettings –>                 
     <!–
<Form Type="DisplayForm" Url="DispForm.aspx" WebPartZoneID="Main"/>                 
     <Form Type="EditForm" Url="EditForm.aspx" WebPartZoneID="Main"/>             
     <Form Type="NewForm" Url="NewForm.aspx" WebPartZoneID="Main"/>
–>                 
     <!–
/Removed for lSettings –>                
     <!–
Added for lSettings –>             
     <
Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pagesform.aspx" WebPartZoneID="Main" />               
     <
Form Type="EditForm" Url="EditForm.aspx" SetupPath="pagesform.aspx" WebPartZoneID="Main" />                 
     <
Form Type="NewForm" Url="NewForm.aspx" SetupPath="pagesform.aspx" WebPartZoneID="Main" />                 
     <!–
/Added for lSettings –>            
</Forms>

I've added the setup path property, as I want the system generated forms, rather than the new copies of the forms that the MS extensions give you by default – too much duplication if you don't need any changes to those!

Phew – that's it. Run `ol WSPBuilder over it, install and deploy your solution, and activate the feature. Just remember that deactivating a feature doesn't remove its list instances – so you might want to do that in the feature receiver class.

Next time, slightly delayed I'm sorry, but as promised, I'll be sharing my SPConfigUpdate code. I hope you'll like it, as it's a really cool, but rarely used, feature of SharePoint.

Flags Attract Attention, look at used car lots!

February 26th, 2008 by mikes


Hoist those flags!

In the course of some work on WSS utils (a log class) quite a while ago, a fellow developer asked me about the Flags attribute I was using. Now, rightly or wrongly, I tend to festoon my code with these so it looks more like bunting, and I just love them. I think I may be a repressed used car salesman. So in honour of the recently passed Australia Day, and our recovery from it, I'd like to promote some jingoistic Green and Gold FlagsAttribute waving. Drinks

The easiest way to think of flags, (if you don't know dip switches, chmod and the like), is a pattern of bits in the base 2 orders of magnitude, where 1 is on and 0 is off:

 1    2    4    8   16  32
 0    1    0    1    0    0
 off  on  off  on  off  off

Hang out the bunting:

So to promote the use of these little beauties, I'll give a quick low-down here. I'll cover more of the wonders of attributes and reflection in another post, when I blog on my my web config updater in a few days.

How do you string them over your code? Easy:

[

Flags]
public enum DestinationTypes : int { EventLog=1, File=2, Email=4, SharePoint=8, SysLog=16, Log4Net=32};

That's looking more festive already, fluttering at the top of the class. Why the base 2 enum values? Well, that gives us that wonderful ability to turn on, or off, any number of these brightly coloured triangles without getting them all twisted up. What's more, you could even add a composite type into the definition like this:

 

public enum DestinationTypes : int { EventLog = 1, File = 2, Email = 4, SharePoint = 8, SysLog = 16, Log4Net = 32, NonMS = SysLog | Log4Net };

Just be careful with those composite flags, as it's easy to get tangled in the implications of turning on what looked like a single flag!

Seeing as I'm ambidextrous, here's how to turn two flags on:

public

static DestinationTypes NotYetImplemented = DestinationTypes.SysLog | DestinationTypes.Log4Net;

..and it just as easy as the above 'bitwise or' if you have an existing value and you want to flick a single switch in there - just use the old = assignment shortcut notation with it:

 

DestinationTypes addDestinationsToMe = DestinationTypes.SysLog | DestinationTypes.Log4Net;
addDestinationsToMe |=
DestinationTypes.SharePoint;

Lets save energy, and turn a flag off now. The 'bitwise and' is the go:

 

DestinationTypes takeDestinationFromMe = DestinationTypes.SysLog | DestinationTypes.Log4Net;
takeDestinationFromMe &=
DestinationTypes.SharePoint;

But how do I test if the sharepoint flag is on, or off, if I can't see the semaphore? Left Hug The bitwise operator boys come to our rescue again, in the form of the 'bitwise and'. Test for a value like this:

 

if ((addDestinationsToMe & DestinationTypes.SharePoint) == DestinationTypes.SharePoint)
{}

 Now there's only one thing left, and that's iterating through ALL types. You don't need to dig into reflection to do this – just use following simple snippet:

foreach

(DestinationTypes dest in Enum.GetValues(typeof(DestinationTypes)))

 So what does this darn FlagsAttribute do for me, as my enum would work fine without it, and it still sequentially numbers the elements, even with the flags attribute there, unless I set my own values?

Thats a good question. Apart from annotating the type for reflection, there is only one small difference…

class Program
{

  public enum DestinationTypesNoAttribute : int { EventLog = 1, File = 2, Email = 4, SharePoint = 8, SysLog = 16, Log4Net = 32 };

  [Flags]
  public enum DestinationTypes : int { EventLog = 1, File = 2, Email = 4, SharePoint = 8, SysLog = 16, Log4Net = 32 };

  static void Main(string[] args)
  {

    string name = typeof(DestinationTypes).FullName;
    // name = "ConsoleApplication1.Program+DestinationTypes"
    name = DestinationTypes.SharePoint.GetType().Name;
    // name = "DestinationTypes"
    name = DestinationTypes.SharePoint.GetType().FullName;
    // name = "ConsoleApplication1.Program+DestinationTypes"
    string strVal = DestinationTypes.SharePoint.ToString();
    // strVal = "SharePoint"
    int intVal = (int)DestinationTypes.SharePoint;
    // intVal = 8
    intVal = (int)(DestinationTypes.Email | DestinationTypes.SharePoint);
    // intVal = 12 (ie 001100 binary = 12 decimal)
    strVal = (DestinationTypes.SharePoint | DestinationTypes.Email).ToString();
    // *** strVal = "Email, SharePoint"Smile

    name =
typeof(DestinationTypesNoAttribute).FullName;
    // name = "ConsoleApplication1.Program+DestinationTypesNoAttribute"
    name = DestinationTypesNoAttribute.SharePoint.GetType().Name;
    // name = "DestinationTypesNoAttribute"
    name = DestinationTypesNoAttribute.SharePoint.GetType().FullName;
    // name = "ConsoleApplication1.Program+DestinationTypesNoAttribute"
    strVal = DestinationTypesNoAttribute.SharePoint.ToString();
    // strVal = "SharePoint"
    intVal = (int)DestinationTypesNoAttribute.SharePoint;
    // intVal = 8
    intVal = (int)(DestinationTypesNoAttribute.Email | DestinationTypesNoAttribute.SharePoint);
    // intVal = 12 (ie 001100 binary = 12 decimal)
    strVal = (DestinationTypesNoAttribute.SharePoint | DestinationTypesNoAttribute.Email).ToString();
    // *** strVal = "12"Confused
  }
}

 

 

So there you have it! When serialising, you get the CSV format, not the integer value – very nice if you need to decipher or edit a stream. Note the '+' annotation to show a field member of the class, but as you would expect, the value isn't considrered a type.

Finally, given that these are still enums, remember you can cast,test and use them as integers (although you might get compiler warnings you can ignore – just ensure you use valid values for your range).

Now there are no more excuses for your classes to be dull, just hang some flags up and flick the switch!

 

 

The end of another MOSSuMS post