Joel's SharePoint Architect Blog

SharePoint 2010, MOSS & WSS Tips and Consultancy Tales

Subscribe Subscribe  View Joel Jeffery's profile on LinkedIn
joelblogs.co.uk | joelj.co.uk | joeljeffery.co.uk | jfdiphoenix.co.uk

Posts Tagged ‘SharePoint Development’

Despite protests from music lovers, here is another of the songs from my SharePoint Show.

This is a mix from several live versions. In this song we visit what you can and can’t do in the Sandbox, and the several ways in which the User Code Service protects our server from the evils of any user code that tries to run a “Bad Command”.

Apologies to Lady Gaga, and all those who have been affected by issues in this video.

Technorati Tags: SharePoint, SharePoint 2010 Training, SharePoint Administration, SharePoint Architecture, SharePoint Development, SharePoint Videos

SharePoint 2007 had a commonly used feature that enabled users to create views on lists that grouped by Content Type.

For some reason, this feature was removed from the user interface in SharePoint 2010.

Solution 1: The Easy Method

If you wish to do this today, you can do this using SharePoint Designer to create a view and then change the Xsl to specify a different field name to group by (e.g. “ContentType”).

Overriding the Field Used for Grouping

Solution 2: The Better Method

Alternative, we could try and get our options added to the ViewEdit.aspx page. Options aren’t great for this as it’s a _layouts (application) page, and therefore we can’t just edit it in the browser or SharePoint Designer.

You could add a piece of JavaScript to do this though. Plan a) would be to add this to the bottom of you v4.master, and customise this for the whole site/site collection.

Plan b) would be to create something like a sandbox solution that deploys a “scriptlink” element, placing the script on every page that gets rendered.

I’ve create a CodePlex project for plan b). Here’s some of the code. Firstly, here’s the JavaScript I’d like to run on every page. It simply creates a new <option> tag in HTML and adds it to the drop down list if it exists on the page. Let’s call it “ListViewEdit.js”.

_spBodyOnLoadFunctionNames.push("jbCTFix");

function jbCTFix() {
    jbCTKludge('idGroupField1');
    jbCTKludge('idGroupField2');
}
function jbCTKludge(selName) {
    var sel = document.getElementById(selName);
    if (sel) {
        if (sel.selectedIndex >= 0) {

            var o = document.createElement('option');
            o.text = 'Content Type';
            o.value = 'tp_ContentType';

            var prev = sel.options[sel.selectedIndex];
            try {
                sel.add(o, prev);
            }
            catch (ex) {
                sel.add(o, sel.selectedIndex);
            }
        }
    }
}

Next, here’s the element manifest to apply this on each page in the site collection.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="Ribbon.Library.Actions.Scripts"
              Location ="ScriptLink"
              ScriptSrc="~site/ListViewEdit/ListViewEdit.js" />
  <Module Name="ListViewEdit">
    <File Path="ListViewEdit\ListViewEdit.js" Url="ListViewEdit/ListViewEdit.js" />
  </Module>
</Elements>

You can download the full project and source code for the SharePoint 2010 ViewEdit Group by Content Type project from the CodePlex project here: sp10ctgrouping.codeplex.com

Technorati Tags: Content Types, SharePoint, SharePoint 2010, SharePoint Development

One of my students tonight asked if it was possible to add a condition to a SharePoint Designer 2010 declarative workflow to detect if the initiating user is a member of a particular audience.

There’s nothing built-in to deliver this in SharePoint 2010.

So I knocked-up the following solution based upon the excellent reference implementations of workflows from the SharePoint Prescriptive Guidance Pack at spg.codeplex.com.

I’ve put the full version of my source code and a completed release up on CodePlex.

Firstly, the .Actions file, which must be deployed to 14\\Template\\Xml\\1033\\Workflow:

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
  <Conditions And="and" Or="or" Not="not" When="If" Else="Else if">
    <Condition Name="User is member of audience"
        FunctionName="IsUserMemberOfAudienceCondition"
        ClassName="joelblogs.co.uk.WorkflowActivities.AudienceActivity.AudienceMemberActivity"
        Assembly="joelblogs.co.uk.WorkflowActivities.AudienceActivity,
          Version=1.0.0.0, Culture=neutral, PublicKeyToken=d926d259b11539d4"
        AppliesTo="all"
            UsesCurrentItem="True">
      <RuleDesigner Sentence="The user is a member of audience %1">
        <FieldBind Id="1" Field="_1_" Text=""/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name="_1_" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Condition>
  </Conditions>
</WorkflowInfo>

Next, the workflow activity class itself:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;
using Microsoft.Office.Server.Audience;
using System.Workflow.ComponentModel;
using System.ComponentModel;

namespace joelblogs.co.uk.WorkflowActivities.AudienceActivity
{
    /// <summary>
    /// Windows Workflow Activity for SharePoint 2010. Checks if
    /// Initiating User is a member of the specified Audience.
    /// Written by Joel Jeffery, 2011-10-28.
    /// </summary>
    class AudienceMemberActivity : Activity
    {
        /// <summary>
        /// Returns whether the user exists in the specified audience or not
        /// -- signature to match SharePoint Designer Requirement
        /// </summary>
        /// <param name="workflowContext">Environment for activity</param>
        /// <param name="listId">ID of the list the workflow is running on (unused)</param>
        /// <param name="itemId">Item ID of the item the workflow is running on (unused)</param>
        /// <param name="siteUrl">The audience name to determine whether the user is in it or not</param>
        /// <returns>True if site exists, false if not </returns>
        public static bool IsUserMemberOfAudienceCondition(
            WorkflowContext workflowContext, string listId, int itemId, string audienceName)
        {
            string exception;
            return (IsUserMemberOfAudience(
                workflowContext.InitiatorUser.LoginName, audienceName, out exception));
        }

        /// <summary>
        /// Determines whether [is user member of audience] [the specified login name].
        /// </summary>
        /// <param name="loginName">Name of the login.</param>
        /// <param name="audienceName">Name of the audience.</param>
        /// <param name="exception">The exception.</param>
        /// <returns>
        ///   <c>true</c> if [is user member of audience] [the specified login name]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsUserMemberOfAudience(string loginName, string audienceName, out string exception)
        {
            try
            {
                exception = null;
                SPServiceContext context = SPServiceContext.Current;
                AudienceManager audManager = new AudienceManager(context);
                return audManager.IsMemberOfAudience(loginName, audienceName);
            }
            catch (Exception e)
            {
                exception = e.ToString();
                return (false);
            }
        }

        public static DependencyProperty AudienceNameProperty =
            DependencyProperty.Register("AudienceName", typeof(string), typeof(AudienceMemberActivity));

        [Description("The absolute URL of the site or site collection to create")]
        [Browsable(true)]
        [Category("joelblogs.co.uk Activities")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string SiteUrl
        {
            get { return ((string)base.GetValue(AudienceNameProperty)); }
            set { base.SetValue(AudienceNameProperty, value); }
        }

        public static DependencyProperty ExistsProperty =
            DependencyProperty.Register("Exists", typeof(bool), typeof(AudienceMemberActivity));
        [Description("The result of the operation indicating whether the site exists or not")]
        [Browsable(true)]
        [Category("joelblogs.co.uk Activities")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public bool Exists
        {
            get { return ((bool)base.GetValue(ExistsProperty)); }
            set { base.SetValue(ExistsProperty, value); }
        }

        public static DependencyProperty ExceptionProperty =
            DependencyProperty.Register("Exception", typeof(string), typeof(AudienceMemberActivity));
        [Description("The exception generated while testing for the existance of the site")]
        [Browsable(true)]
        [Category("joelblogs.co.uk Activities")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string Exception
        {
            get { return ((string)base.GetValue(ExceptionProperty)); }
            set { base.SetValue(ExceptionProperty, value); }
        }
    }
}

 

You’ll also need a terribly clever feature receiver implementation from the SPG that uses the SPWebConfigModification class to add AuthorizedType blocks to the web.configs throughout our farm, or our class won’t be loaded by WF.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace joelblogs.co.uk.WorkflowActivities.AudienceActivity.Features.AudienceTestActivity
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, 
    /// installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>

    [Guid("a91d2258-b39b-4ca4-8282-2565c061378d")]
    public class AudienceTestActivityEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPWebService contentService = SPWebService.ContentService;
                contentService.WebConfigModifications.Add(GetConfigModification());
                // Serialize the web application state and propagate changes across the farm. 
                contentService.Update();
                // Save web.config changes.
                contentService.ApplyWebConfigModifications();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                throw;
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPWebService contentService = SPWebService.ContentService;
                contentService.WebConfigModifications.Remove(GetConfigModification());
                // Serialize the web application state and propagate changes across the farm. 
                contentService.Update();
                // Save web.config changes.
                contentService.ApplyWebConfigModifications();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                throw;
            }
        }

        public SPWebConfigModification GetConfigModification()
        {
            string assemblyValue = typeof(AudienceMemberActivity).Assembly.FullName;
            string namespaceValue = typeof(AudienceMemberActivity).Namespace;

            SPWebConfigModification modification = new SPWebConfigModification(
                string.Format(CultureInfo.CurrentCulture,
                    "authorizedType[@Assembly='{0}'][@Namespace='{1}']" +
                    "[@TypeName='*'][@Authorized='True']", assemblyValue, namespaceValue),
                "configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes");

            modification.Owner = "joelblogs.co.uk";
            modification.Sequence = 0;
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value =
                string.Format(CultureInfo.CurrentCulture,
                "<authorizedType Assembly=\"{0}\" Namespace=\"{1}\" " +
                "TypeName=\"*\" Authorized=\"True\" />", assemblyValue, namespaceValue);

            Trace.TraceInformation("SPWebConfigModification value: {0}", modification.Value);

            return modification;
        }
    }
}

You can download the full source code to my SharePoint 2010 Audience Membership Workflow Activity (Full Trust) here: http://spamwaft.codeplex.com.

Technorati Tags: SharePoint, SharePoint 2010, SharePoint Designer 2010, SharePoint Development, Workflow

SharePoint 2010 Development Machine Spec

If you’re considering buying a new developer laptop or desktop for SharePoint 2010 development, I can thoroughly recommend getting the fastest and most capacious build you can justify.

Development Laptop Build

Here’s my laptop build, and is currently what I consider to be minimum developer spec for serious SharePoint 2010 development work.

Model: Dell XPS L702

CPU: Intel i7-2720QM

RAM: 16GB

Disk: 500GB SSD (Kingston), 500GB eSata

Operating Systems

I run Windows 7 as a native OS on my laptop. This means I can code whilst on the train, and cut-and-run without creating a boil-in-the-bag laptop. I like this feature.

This is great for ad hoc development, but for more formal scenarios, I have a Windows 2008R2 SP1 native boot virtual hard disk (.vhd) added to my boot menu. When I boot into it, I have the Hyper-V role enabled which means I can run my choice of server platforms to more accurately mimic the customer’s environment. The .vhd files loaded by Hyper-V are kept natively on the SSD drive.

For some projects that require a unique, but standalone server build, I have Oracle VirtualBox installed under Windows 7. This means I can have a functioning Windows 7 machine for email and casual use wrapped around a dedicated single Windows Server VM that has the customer environment and developer tools installed.

For the most power-hungry builds, I use another virtual native boot .vhd, with Windows Server 2008R2 SP1 and the development tools natively installed.

Technorati Tags: SharePoint 2010, SharePoint Development

I was recently asked how to remove the Editor Parts (Appearance, Layout, Advanced, etc) from custom Web Parts.

One strategy is to implement your own Editor Part and mark the built-in ones as not Visible.

Firstly, we need to create our Editor Part class:

class MyEditorPart : EditorPart
{
    protected override void CreateChildControls()
    {        // this line hides the default EditorParts
        Parent.Controls[2].Visible = false;
        base.CreateChildControls();
    }

    public override bool ApplyChanges()
    {
        // do stuff here
        return true;
    }

    public override void SyncChanges()
    {
        // do stuff here
    }
}

And here’s how we invoke our Editor Part from our Web Part:

public class MyWebPart : WebPart
{
    public override EditorPartCollection CreateEditorParts()
    {
        ArrayList aryParts = new ArrayList();

        MyEditorPart myEditor = new MyEditorPart();
        myEditor.ID = this.ID + "_myEditorPart";
        aryParts.Add(myEditor);

        return new EditorPartCollection(aryParts);
    }
    // do more stuff here...
}

Hopefully, when you edit the Web Part it should look something like this:

The next step would be to extend CreateChildControls(), ApplyChanges() and SyncChanges() to get and set any properties you’d like to allow users to edit.

Technorati Tags: Development, SharePoint, SharePoint 2010 Training, SharePoint Developer, SharePoint Development, WebParts