Wednesday, August 15, 2007

Sharepoint Class Wrapper

Many sharepoint classes are not available to inherit from because the default contructor is hidden. The following (quick and untested!) code uses the CodeDom to generate a wrapper class.

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using Microsoft.SharePoint;
 
namespace DevHoleDemo.ClassWrapper
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassWrapper w = new ClassWrapper("DevHoleDemo", "SPWebWrapper", typeof(SPWeb));
            string s = w.GetCode();
        }
    }
 
    public class ClassWrapper
    {
        string m_namespace;
        string m_className;
        SortedDictionary<string, int> m_imports;
        SortedDictionary<string, int> m_modules;
        StringBuilder m_sb;
        Type m_t;
 
        public ClassWrapper(string targetNamespace, string targetClassName, object source)
            : this(targetNamespace, targetClassName, source.GetType())
        {
        }
 
        public ClassWrapper(string targetNamespace, string targetClassName, Type t)
        {
            m_namespace = targetNamespace;
            m_className = targetClassName;
            m_t = t;
            m_imports = new SortedDictionary<string, int>();
            m_modules = new SortedDictionary<string, int>();
        }
 
        public string GetCode()
        {
            return GetCode("CS");
        }
 
        public string GetCode(string language)
        {
            Generate(language);
            string results = m_sb.ToString();
            results = results.Replace("void implicit_operator_", "implicit operator ");
            return results;
        }
 
        protected void AddNamespace(Type t)
        {
            if (m_imports.ContainsKey(t.Namespace))
                m_imports[t.Namespace]++;
            else
                m_imports.Add(t.Namespace, 1);
            if (m_modules.ContainsKey(t.Module.Name))
                m_modules[t.Module.Name]++;
            else
                m_modules.Add(t.Module.Name, 1);
        }
 
        protected CodeTypeReference GetCodeTypeReference(Type t)
        {
            AddNamespace(t);
            if (t.IsByRef)
                return new CodeTypeReference(t.Name.TrimEnd('&'));
            else
                return new CodeTypeReference(t);
        }
 
        protected void Generate(string language)
        {
 
            m_sb = new StringBuilder();
            TextWriter tw = new StringWriter(m_sb);
            CodeDomProvider cdp = CodeDomProvider.CreateProvider(language);
            CodeTypeReferenceExpression tre = new CodeTypeReferenceExpression(m_t);
 
            //namespace
            CodeNamespace ns = new CodeNamespace(m_namespace);
 
            //class
            CodeTypeDeclaration class1 = new CodeTypeDeclaration(m_className);
            ns.Types.Add(class1);
            class1.IsClass = true;
 
            //base object field
            CodeMemberField cmf1 = new CodeMemberField(GetCodeTypeReference(m_t), "m_" + m_t.Name);
            class1.Members.Add(cmf1);
 
            //constructor
            CodeConstructor cc = new CodeConstructor();
            cc.Attributes = MemberAttributes.Public;
            CodeParameterDeclarationExpression cpde1 = new CodeParameterDeclarationExpression(GetCodeTypeReference(m_t), m_t.Name);
            cc.Parameters.Add(cpde1);
            CodeFieldReferenceExpression cfre1 = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_" + m_t.Name);
            cc.Statements.Add(new CodeAssignStatement(cfre1, new CodeArgumentReferenceExpression(m_t.Name)));
            class1.Members.Add(cc);
 
            //methods
            foreach (MethodInfo mi in m_t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy))
            {
                if (!mi.IsSpecialName)
                {
                    CodeMemberMethod meth = new CodeMemberMethod();
                    meth.Attributes &= ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask;
                    meth.Attributes |= MemberAttributes.Public;
                    if ((mi.Attributes & MethodAttributes.Static) == MethodAttributes.Static)
                        meth.Attributes |= MemberAttributes.Static;
                    if ((mi.Attributes & MethodAttributes.Virtual) != MethodAttributes.Virtual)
                        meth.Attributes |= MemberAttributes.Final;
                    meth.Name = mi.Name;
                    List<CodeExpression> prms = new List<CodeExpression>();
                    foreach (ParameterInfo ri in mi.GetParameters())
                    {
                        CodeParameterDeclarationExpression cpde2 = new CodeParameterDeclarationExpression(GetCodeTypeReference(ri.ParameterType), ri.Name);
                        CodeArgumentReferenceExpression care1 = new CodeArgumentReferenceExpression(ri.Name);
                        CodeDirectionExpression cde = null;
                        if (ri.IsOut)
                        {
                            cpde2.Direction = FieldDirection.Out;
                            cde = new CodeDirectionExpression(FieldDirection.Out, care1);
                        }
                        meth.Parameters.Add(cpde2);
                        if (cde == null)
                            prms.Add(care1);
                        else
                            prms.Add(cde);
                    }
 
                    CodeMethodReferenceExpression cmre1;
                    if ((mi.Attributes & MethodAttributes.Static) == MethodAttributes.Static)
                        cmre1 = new CodeMethodReferenceExpression(tre, mi.Name);
                    else
                        cmre1 = new CodeMethodReferenceExpression(cfre1, mi.Name);
 
                    if (mi.ReturnType == typeof(void))
                    {
                        meth.Statements.Add(new CodeMethodInvokeExpression(cmre1, prms.ToArray()));
                    }
                    else
                    {
                        meth.Statements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(cmre1, prms.ToArray())));
                    }
                    meth.ReturnType = GetCodeTypeReference(mi.ReturnType);
 
                    class1.Members.Add(meth);
                }
            }
 
            //properties
            foreach (PropertyInfo pi in m_t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy))
            {
                CodeMemberProperty prop = new CodeMemberProperty();
                prop.Name = pi.Name;
                prop.Attributes &= ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask;
                prop.Attributes |= MemberAttributes.Public | MemberAttributes.Final;
                List<CodeExpression> indices = new List<CodeExpression>();
                if (pi.GetIndexParameters().Length > 0)
                {
                    foreach (ParameterInfo pii in pi.GetIndexParameters())
                    {
                        CodeParameterDeclarationExpression cpde3 = new CodeParameterDeclarationExpression(GetCodeTypeReference(pii.ParameterType), pii.Name);
                        prop.Parameters.Add(cpde3);
                        CodeArgumentReferenceExpression care3 = new CodeArgumentReferenceExpression(pii.Name);
                        indices.Add(care3);
                    }
 
                }
                prop.Type = GetCodeTypeReference(pi.PropertyType);
                prop.HasGet = pi.GetGetMethod() != null;
                if (prop.HasGet)
                {
                    if (pi.GetIndexParameters().Length > 0)
                        prop.GetStatements.Add(new CodeMethodReturnStatement(new CodeArrayIndexerExpression(cfre1, indices.ToArray())));
                    else
                        prop.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(cfre1, pi.Name)));
 
                }
                prop.HasSet = pi.GetSetMethod() != null;
                if (prop.HasSet)
                {
                    if (pi.GetIndexParameters().Length > 0)
                        prop.SetStatements.Add(new CodeAssignStatement(new CodeArrayIndexerExpression(cfre1, indices.ToArray()), new CodePropertySetValueReferenceExpression()));
                    else
                        prop.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(cfre1, pi.Name), new CodePropertySetValueReferenceExpression()));
                }
                class1.Members.Add(prop);
            }
 
            //implicit casts
            CodeMemberMethod castFrom = new CodeMemberMethod();
            castFrom.Name = "implicit_operator_" + m_t.Name;
            castFrom.Attributes &= ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask;
            castFrom.Attributes |= MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
            CodeParameterDeclarationExpression cpde4 = new CodeParameterDeclarationExpression(new CodeTypeReference(m_className), m_className);
            castFrom.Parameters.Add(cpde4);
            CodeArgumentReferenceExpression care4 = new CodeArgumentReferenceExpression(m_className);
            CodeFieldReferenceExpression cfre3 = new CodeFieldReferenceExpression(care4, "m_" + m_t.Name);
            castFrom.Statements.Add(new CodeMethodReturnStatement(cfre3));
            castFrom.ReturnType = new CodeTypeReference(typeof(void));
            class1.Members.Add(castFrom);
 
            CodeMemberMethod castTo = new CodeMemberMethod();
            castTo.Name = "implicit_operator_" + m_className;
            castTo.Attributes &= ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask;
            castTo.Attributes |= MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
            CodeParameterDeclarationExpression cpde5 = new CodeParameterDeclarationExpression(new CodeTypeReference(m_t), m_t.Name);
            castTo.Parameters.Add(cpde5);
            CodeArgumentReferenceExpression care5 = new CodeArgumentReferenceExpression(m_t.Name);
            CodeObjectCreateExpression coce = new CodeObjectCreateExpression(new CodeTypeReference(m_className), care5);
            castTo.Statements.Add(new CodeMethodReturnStatement(coce));
            castTo.ReturnType = new CodeTypeReference(typeof(void));
            class1.Members.Add(castTo);
 
            //write imports
            foreach (KeyValuePair<string, int> kvp in m_imports)
            {
                ns.Imports.Add(new CodeNamespaceImport(kvp.Key));
            }
 
            //write modules comments
            ns.Comments.Add(new CodeCommentStatement("References to the following modules must be added:"));
            foreach (KeyValuePair<string, int> kvp in m_modules)
            {
                ns.Comments.Add(new CodeCommentStatement(string.Format("\t {0}", kvp.Key)));
            }
 
            cdp.GenerateCodeFromNamespace(ns, tw, null);
            tw.Close();
        }
    }
}

Tuesday, May 29, 2007

Web Configuration Namespace

If you've opened a sharepoint virtual directory as a file system web site using Visual Studio, watch out for the following gotcha (also discussed here):

If VS updates the web.config file, it will add the a reference to the xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0" namespace in the root configuration element.

This will lead to various "File not found" error messages with the log file showing entries like the following:

Could not load file or assembly 'Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'

Remove the namespace reference and the problem goes away.

Wednesday, March 28, 2007

Create Groups Programmatically

Reza Alirezaei has posted a helpful article on creating SharePoint sites with unique role assignments. The following simple demo class puts some of these ideas together. Like Reza's example, the code below changes the built in associated groups by using the spWeb property bag directly, rather than the properties exposed by spWeb - e.g. spWeb.AssociatedGroups.Add(group), or spWeb.AssociatedOwnerGroup = group.

using Microsoft.SharePoint;
 
namespace DevHoleDemo
{
    public static class GroupsHelper
    {
        public enum AssociatedGroupTypeEnum { Owners, Members, Vistors };
 
        public static void AddGroups(SPWeb spWeb, bool copyUsersFromParent)
        {
            spWeb.BreakRoleInheritance(false);
            SPGroup owners = AddGroup(spWeb, AssociatedGroupTypeEnum.Owners, copyUsersFromParent);
            SPGroup members = AddGroup(spWeb, AssociatedGroupTypeEnum.Members, copyUsersFromParent);
            SPGroup vistors = AddGroup(spWeb, AssociatedGroupTypeEnum.Vistors, copyUsersFromParent);
            SPGroup myGroup = AddGroup(spWeb, "My Group", "An example group.", null, null, null, false);
            SetAssociatedGroups(spWeb, new SPGroup[] { owners, members, vistors, myGroup });
        }
 
        public static void SetAssociatedGroups(SPWeb spWeb, SPGroup[] groups)
        {
            string formatString = "";
            object[] ids = new object[groups.Length];
            for (int i = 0; i < groups.Length; i++)
            {
                formatString += string.Format("{{{0}}};", i);
                ids[i] = groups[i].ID;
            }
            spWeb.Properties["vti_associategroups"] = string.Format(formatString.TrimEnd(new char[] { ';' }), ids);
            spWeb.Properties.Update();
        }
 
        public static SPGroup AddGroup(SPWeb spWeb, AssociatedGroupTypeEnum associateGroupType, bool copyUsersFromParent)
        {
            switch (associateGroupType)
            {
                case AssociatedGroupTypeEnum.Owners:
                    return AddGroup(spWeb, spWeb.Name + " Owners", "Use this group to give people full control permissions to the SharePoint site: {0}", "Full Control", "vti_associateownergroup", spWeb.ParentWeb.AssociatedOwnerGroup, copyUsersFromParent);
                case AssociatedGroupTypeEnum.Members:
                    return AddGroup(spWeb, spWeb.Name + " Members", "Use this group to give people contribute permissions to the SharePoint site: {0}", "Contribute", "vti_associatemembergroup", spWeb.ParentWeb.AssociatedMemberGroup, copyUsersFromParent);
                case AssociatedGroupTypeEnum.Vistors:
                    return AddGroup(spWeb, spWeb.Name + " Vistors", "Use this group to give people read permissions to the SharePoint site: {0}", "Read", "vti_associatevisitorgroup", spWeb.ParentWeb.AssociatedVisitorGroup, copyUsersFromParent);
                default:
                    return null;
            }
        }
 
        public static SPGroup AddGroup(SPWeb spWeb, string groupName, string descriptionFormatString, string roleDefinitionName, string associatedGroupName, SPGroup parentAssociatedGroup, bool copyUsersFromParent)
        {
            SPGroup owner = parentAssociatedGroup;
            if (associatedGroupName != "vti_associateownergroup")
                owner = spWeb.SiteGroups.GetByID(int.Parse(spWeb.Properties["vti_associateownergroup"]));
            spWeb.SiteGroups.Add(groupName, owner, null, string.Format(descriptionFormatString, spWeb.Name));
            SPGroup group = spWeb.SiteGroups[groupName];
            if (descriptionFormatString.IndexOf("{0}") != -1)
            {
                SPListItem item = spWeb.SiteUserInfoList.GetItemById(group.ID);
                item["Notes"] = string.Format(descriptionFormatString, string.Format("<a href=\"{0}\">{1}</a>", spWeb.Url, spWeb.Name));
                item.Update();
            }
            if (roleDefinitionName != null)
            {
                SPRoleAssignment roleAssignment = new SPRoleAssignment(group);
                SPRoleDefinition roleDefinition = spWeb.RoleDefinitions[roleDefinitionName];
                roleAssignment.RoleDefinitionBindings.Add(roleDefinition);
                spWeb.RoleAssignments.Add(roleAssignment);
            }
            if (copyUsersFromParent && parentAssociatedGroup != null)
                foreach (SPUser user in parentAssociatedGroup.Users)
                    group.AddUser(user);
            if (associatedGroupName != null)
            {
                spWeb.Properties[associatedGroupName] = group.ID.ToString();
                spWeb.Properties.Update();
            }
            spWeb.Update();
            return group;
        }
    }
}

Wednesday, January 31, 2007

Synchronous List Event Properties

Some people have posted (for example here) about not being able to access the properties of a list item in a synchronous list event (e.g. ItemAdding). Maybe this was fixed in the RTM but I'm not seeing this problem - the properties are actually there - they'll show up in the AfterProperties collection, although they can sometimes be a little hard to spot. One trap to watch out for is that the keys in the hashtable correspond to the fields' internal names, so that, for example, any spaces will be encoded as "_x0020_". To be safe, it pays to use a syntax something like the following:

string myFieldValue = Convert.ToString(properties.AfterProperties[properties.ListItem.Fields["My Field Name"].InternalName]);


An example of iterating through the SPItemEventDataCollection:
public override void ItemAdding(SPItemEventProperties properties)
{
    base.ItemAdding(properties);
    foreach (DictionaryEntry dictionaryEntry in properties.AfterProperties)
    {
    }
}

Wednesday, January 17, 2007

InfoPath Forms Services close button redirection

In my installation of MOSS 2007, editing a browser-enabled InfoPath form in a document library by choosing Edit in Browser from the context menu doesn't pass the 'Source' parameter (described in a post by Scott Jamison here) in the URL to the /_layouts/formserver.aspx page. The parameter is passed correctly when creating a new form in the document library. The result of this is two different behaviors when users are finished editing the form and click the close button. If it's a new form, there is a postback and the browser redirects to the document library. If it's an existing form that is being edited, the browser doesn't redirect, instead it shows the following dialog:

The form has been closed.


I came up with a work around - editing the custom action in the IPFS file:

%PROGRAMFILES%\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\IPFSSiteFeatures\FormServerEcbEntry\EcbEntry.xml


...and changing the appropriate UrlAction elements to include a source parameter, i.e.


<UrlAction Url="~site/_layouts/formserver.aspx?XmlLocation={ItemUrl}&amp;OpenIn=Browser&amp;Source=&lt;SCRIPT&gt;document.write(escapeProperly(window.location.href))&lt;/SCRIPT&gt;"/>

It's not the best solution however (because the file will get overwritten by any updates etc.) - but I haven't seen any other answers out there.