in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

This Blog

Syndication

News

Mar 17 2008   Commuting this AM I independently ran into 3 ex-colleagues from the same small firm. Now I know Perth isn't big, but that is just too weird & 'paranoidal'. So Chris, Dan and Sean - stop dabbling in Occult over IpEspFi V6 - it's freaking me out!

Feb 20 2008   Aussie day has been and gone and things have been hectic, but I'm now on a course with the irrepressible Clayton James - with many ideas to blog about!

Jan 21 2008   Back again, with the promised info on moving those web pages in code.

Jan 16 2008   I'm away for a few days for a Geraldton funeral (sniff), but back in Perth I'll have something to say about moving pages between webs...

Jan 14 2008   Happy New Year and a Happy New Blog to all - especially MOSS masters Mr P.CleverWorkarounds and Sezai "Yoda"!

Archives


G'Day MOSSuMS

 
View Mike Stringfellow's profile on LinkedIn  
 
 
Join me, Mike Stringfellow, in the upside down world of the baggy green MOSS. I hope you find my rantings useful, as I search out my own path through the challenges and opportunities that "the other MS" throws out.
 
  • So you think you can change your Web Config?

    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.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.

  • Custom List Feature Definition


    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="lSettings\ListTemplateElements.xml" />
              <
    ElementManifest Location="lSettings\ListInstanceElements.xml" />      
              <
    ElementFile Location="lSettings\schema.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 Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplatesCacheExp\CSharp\SharePoint\ListDefinition.zip\ListDefinitions\GenericList\schema.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 8\Common7\IDE\ProjectTemplatesCacheExp\CSharp\SharePoint\ListDefinition.zip\ListDefinitions\GenericList\schema.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="0x01">                      
                   <Folder TargetName="Item"/>               
         </ContentTypeRef>             
         <ContentTypeRef ID="0x0120"/>
    -->               
         <!--
    /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="pages\viewpage.aspx" >

    <!-- /Added for lSettings --> 

    I added the setup path pages\viewpage.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 -->                
         <!--