Showing posts with label sharepoint. Show all posts
Showing posts with label sharepoint. Show all posts

Friday, 21 September 2007

Comment roundup: SharePoint as a development platform

Looks like a couple of interesting posts on using SharePoint / WSS / MOSS as a development platform. I was originally going to send this as an email to a former colleague (hi mate!) but thought I would post it instead because I find the debate quite interesting. Remember, the discussion is about SharePoint as a development platform, not as a CMS or whatever else.

I first found Ayende's post on the topic, which is a response to Sahil Malik's response to Jeffrey Pallermo's post on the SharePoint development platform :)

Jeffrey's post raises the (IMHO) valid issue of the friction of using the SharePoint platform for development, particularly for deployment and builds. Unfortunately his point gets a bit diluted by focusing on the non-issue of being unable to install SharePoint on Vista/XP.

I think one of the stars of the comment trail on Jeffrey's post is Andrew Connell (MOSS MVP, and pretty decent bloke if his blog, comments and presentation at the APAC SharePoint 2007 conference in Sydney are anything to go by), who has added some very constructive comments pointing out the strengths of the platform lie in the out-of-the-box navigation, security, search, Web Part framework, WF hosting etc, as well as acknowledging several weaknesses. One of the things I found really interesting (although not too surprising) is this part of Andrew's comment:

"Over hyped? Really... so that's why it's the highest growth product ever in the history of the company and closing in on $1B in sales for the latest version in just a few months from now? That's ALL marketing hype with the other portal solutions and other content management applications out there? Um... I think business decision makers are a bit more savy than that."

For years now I have had the strong suspicion that people that look to things like OpenOffice.org as potential Office-killers are missing the main point of Microsoft's Office strategy. I'm sure they are not too fussed about losing a few home users -- they are targeting the bigger end of town with volume licencing. The integration, no, dependency (they are part of the same platform after all) between Office 2007 on the client and MOSS 2007 on the server is where the action is. The complete platform is going to guarantee them a nice revenue stream from medium, big and giant businesses for as long as they can keep pumping up version numbers.

As a further aside, Andrew also writes a  "Not every application should be built on SharePoint... no one is advocating that (no reasonable person that is)." Marcus makes a similar observation:

"I'm not sure you realize how many people right now are investing in SharePoint thinking that it is a "no-code" solution to replacing custom ASP.Net apps, or in some way making managability of those easier. ... It doesn't do that.  Its a content management system (and a good one at that)."

(Sorry, gentle dig... bygones! ;) :P)

The main complaints in the comments revolve around the poor tool support (agreed by pretty much everyone), the poor / barely existent documentation (ditto), and testability, deployment and configuration (over which there is much disagreement). The latter issue brings us back to one of Jeffrey's original points: the friction of using the platform.

I have personally found SharePoint development (as a MOSS newbie anyway) to be very high friction. The minute you want to go "out-of-the-box" at all you run into all sorts of issues. Even talking to the experts at the recent APAC SharePoint 2007 conference in Sydney exposed so many work-arounds and compromises just to comply with the platform. The session on packaging and deploying custom applications (run by a bloke who really knew his stuff) was filled with advice like "this bit doesn't work properly, so you need to manually edit this XML, you can copy it from another file in the 12 folder, then adjust the package names and then...".

Andrew suggests that this is simply a cost of using any platform:

"So I want to build add-ins for Visual Studio. Is it easy? Nope... because I need this and that and have all this work to do just to get my environment ready to build a plugin.

To me the issue isn't with SharePoint, it is jumping into working within and integrating with platform unlike pure ASP2 development where you building from the ground up."

I am unconvinced. Yes, I agree there will be a cost associated with using and complying with any platform, but a platform really exists to make your life easier. SharePoint's big failing in this department IMHO is that the compliance costs are very high, and the platform is too broad. By trying to do everything, it does some things well and some things badly, and in the latter cases it can be very difficult to go outside of the platform. In these cases you start wondering if the whole thing would have been better off in regular ASP.NET.

There are a long list of great comments on Jeffrey's post, and they make a very good read, if you are interested in such things.

Wednesday, 11 July 2007

Matching a business entity without using the address book

In MOSS 2007 you can use Business Data Catalogs (BDCs) to expose business entities. For example, you can add a column type that lets you choose one of your company's products.

I had a BDC file that *almost* worked. It let you choose a product using the address book, but would not match the product when it was entered in the text area. In turns out you need to specify that the FilterDescriptor on your finder method is UsedForDisambigution. Clear as mud right? An extract from the BDC XML is shown below:

<Method Name="GetTradeProducts">
  <FilterDescriptors>
    <FilterDescriptor Type="Wildcard" Name="TradeName">
      <Properties>
        <Property Name="UsedForDisambiguation" Type="System.Boolean">true</Property>
      </Properties>
    </FilterDescriptor>
  </FilterDescriptors>
  <Parameters>
    <Parameter Direction="In" Name="tradeNameStartsWith">
      <TypeDescriptor TypeName="System.String" Name="TradeName" AssociatedFilter="TradeName" />
    </Parameter>
    <Parameter Direction="Return" Name="TradeProducts">
      ...
    </Parameter>
  </Parameters>
  <MethodInstances>
    <MethodInstance Type="Finder" ReturnParameterName="TradeProducts" ReturnTypeDescriptorName="ArrayOfTradeProduct" ReturnTypeDescriptorLevel="0" Name="GetTradeProductsInstance" />
  </MethodInstances>
  ...

Tuesday, 10 July 2007

Sending an email to a group with a SharePoint workflow

Update 2008-05-21: Mark Deraeve left a comment about a much better solution... changing the group permissions to let SPD send the email natively. Thanks Mark! I've kept the rest of the post intact as it does give a walkthrough of creating custom designer actions.

As you might be able to tell from my some of my recent posts, I have been mucking around with a SharePoint Designer (SPD) workflow. This post serves as a complete example, building on some of the information previously posted:

Background

We had managed to create a complete document approval workflow using only MOSS 2007 and SPD (part of project manager's aim for the project was to see how much could be done without coding). The workflow assigned tasks to a single user or a site group by looking up a custom list that mapped a field from the document list to the required approver/approvers. The aim was then to create a very simple notification workflow to email the user or group that was assigned the task to let them know they had work to do. The Send Email task worked great for single users, but failed horribly when the task's AssignedTo property was a group.

After a bit of research, talking to partners, and laptop bludgeoning, it was finally decided to implement this tiny bit of functionality using a custom task. 

False start

The first attempt was to create a custom email task that would send to multiple recipients. This took a lot of mucking around. It sort of worked. It would send the appropriate email, but as Todd Baginski noted it would not replace the lookup fields in the email body with the correct values. So I decided to try making a custom activity to lookup the user or group matching the AssignedTo property, and return the relevant email addresses to a workflow variable using an output parameter.

The GetEmailAddressActivity

The first step in coding our activity is to expose the required properties, which is what the GetEmailAddressActivity class does. It delegates the real work to the NameToEmailResolver class (covered below).
using System;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using Microsoft.SharePoint.WorkflowActions;

namespace MyCustomActivityLibrary {
public partial class GetEmailAddressActivity : SequenceActivity {
public GetEmailAddressActivity() {
InitializeComponent();
}

public static DependencyProperty NameToLookupProperty =
DependencyProperty.Register("NameToLookup", typeof(String), typeof(GetEmailAddressActivity));
public static DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(String), typeof(GetEmailAddressActivity));
public static DependencyProperty __ContextProperty =
DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(GetEmailAddressActivity));

[ValidationOption(ValidationOption.Required)]
public string NameToLookup {
get { return (string) base.GetValue(NameToLookupProperty); }
set { base.SetValue(NameToLookupProperty, value); }
}

[ValidationOption(ValidationOption.Required)]
public string Target {
get { return (string) base.GetValue(TargetProperty); }
set { base.SetValue(TargetProperty, value); }
}

[ValidationOption(ValidationOption.Required)]
public WorkflowContext __Context {
get {
return (WorkflowContext) base.GetValue(__ContextProperty);
}
set { base.SetValue(__ContextProperty, value); }
}

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
NameToEmailResolver resolver = new NameToEmailResolver(__Context);
Target = resolver.GetEmailAddressesFromName(NameToLookup);
return ActivityExecutionStatus.Closed;
}
}
}

NameToEmailResolver

Here is where the real work is done. It takes the a name, which could be a user's login name or a site group name, and puts it into a semi-colon delimited string of email addresses. If you wanted to provide a wrapper around the WorkflowContext this would be a great place to insert some unit tests.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;

namespace MyCustomActivityLibrary {
public class NameToEmailResolver {
private WorkflowContext context;
public NameToEmailResolver(WorkflowContext context) {
this.context = context;
}

public String GetEmailAddressesFromName(String name) {
StringBuilder resolvedAddresses = new StringBuilder();
IEnumerable addresses = getEmailAddressesFromName(name);
foreach (string address in addresses) {
if (!String.IsNullOrEmpty(address)) {
resolvedAddresses.AppendFormat("{0};", address);
}
}
return resolvedAddresses.ToString();
}

private IEnumerable<string> getEmailAddressesFromName(string name) {
SPGroup group = getGroup(name);
if (group != null) {
return getEmailAddressesFromGroup(group);
}
SPUser user = getUser(name);
if (user != null) {
return new string[] {user.Email};
}
return new string[0];
}

private IEnumerable<string> getEmailAddressesFromGroup(SPGroup group) {
List<string> addresses = new List();
foreach (SPUser user in group.Users) {
addresses.Add(user.Email);
}
return addresses;
}

private SPGroup getGroup(string name) {
foreach (SPGroup group in context.Web.SiteGroups) {
if (String.Equals(group.Name, name, StringComparison.InvariantCultureIgnoreCase)) {
return group;
}
}
return null;
}

private SPUser getUser(string name) {
foreach (SPUser user in context.Web.SiteUsers) {
if (String.Equals(user.LoginName, name, StringComparison.InvariantCultureIgnoreCase)) {
return user;
}
}
return null;
}
}
}

Create .ACTIONS file

To get SPD to recognise your activity you need an .ACTIONS file.

<?xml version="1.0"?>
<workflowinfo language="en-us">
<!--
.ACTIONS files live here:
C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow
Recognised after iisreset.
-->
<actions>
<action name="Store Email Address" classname="MyCustomActivityLibrary.GetEmailAddressActivity" assembly="MyCustomActivityLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<strong>(your token here)</strong>" appliesto="all" category="My Custom Activities">
<ruledesigner sentence="Store email address for %1 in %2.">
<fieldbind id="1" field="NameToLookup" text="this user or group" designertype="TextArea" operatortypefrom="FieldName">
<fieldbind id="2" field="Target" text="variable" designertype="ParameterNames" operatortypefrom="Variable">
</ruledesigner>
<parameters>
<parameter name="NameToLookup" type="System.String, mscorlib" direction="In">
<parameter name="Target" type="System.String, mscorlib" direction="Out">
<parameter name="__Context" type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" direction="In">
</parameters>
</action>
</actions>
</workflowinfo>
The tricky parts (well, for me) was the DesignerType and OperatorTypeFrom values. The current values give you the lookup for the NameToLookup value, and the workflow variables UI selector for the Target parameter. The final parameter is the WorkflowContext.

Deployment

You need to install the DLL containing your activity into the GAC on your WSS/MOSS server, which means your assembly needs to be signed/strongly named. Step 7 of Lee's post has a good run down of how to do this if you are unfamiliar with it (in fact he covers the whole deplyoment thing really well). You can then drag your DLL into the GAC (C:\WINNT\ASSEMBLY). You also need to copy your .ACTIONS file to the ...12\TEMPLATE\1033\Workflow directory. Finally you need to tell SPD to trust the DLL in the application's web.config file. Here is the XML fragment:

<?xml version="1.0"?>
<!-- Add authorised type to relevant web.config:
C:\Inetpub\wwwroot\wss\VirtualDirectories\(dir for app)\web.config
-->
<configuration>
<System.Workflow.ComponentModel.WorkflowCompiler>
<authorizedTypes>
<authorizedType Assembly="MyCustomActivityLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=(your public key)"
Namespace="MyCustomActivityLibrary" TypeName="*" Authorized="True" />
</authorizedTypes>
</System.Workflow.ComponentModel.WorkflowCompiler>
</configuration>

After an iisreset your activity is ready to go.

Setting up the workflow in SPD

The final step is the SPD workflow itself. Create a new workflow and attach it to your task list, to be started whenever a new task is created.

The first task is to initialise the relevant variables. In the screen shot below you can see the custom task highlighted / hovered over. Our custom task will map the name in Tasks:AssignedTo to a single email (when assigned to a single user), or a semi-colon delimited list of emails (when assigned to a group), and will then store this value in a variable.

The final step in the workflow is to use the standard email task, but set the To: field to our workflow variable initialised in the previous step.

Finished!

The final result? You can assign a task to a single user or a site group in a different workflow, then ensure all users get notified when a task is assigned to them or a group they are in. A few disclaimers: there is no error handling in the code; the code will almost definitely break when a multi-valued field is used for the name (although this should be straight forward to support with a few modifications); the code is not particularly pretty and probably violates several laws of nature; the example is meant to be illustrative only and not fit for any specific purpose (i.e. you're crazy if you use it); and my head has a laptop-shaped bruise in it.

Hope this helps someone!

XSD for .ACTIONS file Intellisense

John Holliday has a nice article on Custom Workflow Activities to SharePoint Designer 2007, which also includes a link to a WSSACTIONS.xsd file that enables Intellisense for .ACTIONS files in VS 2005. John's example is writing the value of a parameter to the event log.

SharePoint DLLs (WSS 3.0 / MOSS 2007)

c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI

I always forget that :$ You can get your Microsoft.SharePoint.dll, Microsoft.Office.Server.dll (for MOSS), Microsoft.SharePoint.WorkflowActions.dll et al. from there.

SharePoint aware workflow activities

When creating a custom workflow activity for MOSS 2007 in VS 2005, it would be great to be able to get the current SharePoint web or site that the workflow is attached to. SPContext.Current will not work, as it is null within an activity. If only we could get some sort of context for the workflow...

Enter WorkflowContext (in Microsoft.SharePoint.WorkflowActions namespace, in the DLL of the same name). Unfortunately this is not trivial to access via code -- you need to pass it as a parameter to your activity and put in the necessary plumbing code to store it. First you need to setup a DependencyProperty and related property in your activity.

public static DependencyProperty __ContextProperty = 
  DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(MyCustomActivity));

[ValidationOption(ValidationOption.Required)]
public WorkflowContext __Context {
  get { 
    return (WorkflowContext) base.GetValue(__ContextProperty);
  }
  set { base.SetValue(__ContextProperty, value); }
}

You then need to pass the parameter in through the relevant .ACTIONS file:

<Parameter Direction="In"
  Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" 
  Name="__Context" />

You can then use the __Context to access the SharePoint site and web associated with this workflow activity. You can do similar things with the current SharePoint list and list item id. Thanks to these posts for the information!

Monday, 9 July 2007

Custom SharePoint Designer Actions using VS 2005

Lee Richardson has a walk-through of creating a custom SharePoint Designer action to send an email to multiple recipients. Lee based his code on an article by Todd Baginski.

Monday, 2 July 2007

Emailing form links using SharePoint Designer

It is surprisingly difficult to email a link to a relevant input form in a SharePoint Designer workflow. There is no built in property, not even a nice way of getting server name/domain. The only option I have found is to build up the string.

I have tended to put an "Initialise variables" task as the first step in my workflow, then use the Build Dynamic String action. I can then use this dynamic string in my workflow emails and feel slightly less dirty about hard coding in domains and so forth :)

Here are the basic steps to configure this in the "Initialise variables" task (the link above has some nice pictures of the relevant screens):

  • Create a new variable, serverUrl, or similar.
  • First action, set the variable to the domain: http://myserver/
  • Add a "Build Dynamic String" action as the second action. For the string to store, I had something like this:
    [%Variable: serverUrl%][%Tasks:Path%]/DispForm.aspx?ID=[%Tasks:ID%]
  • Store this dynamic string in another variable, such as displayFormUrl.

The above example builds a link to the display form of the current item, which is a task in this case. You can then use the displayFormUrl variable within your email task.

Wednesday, 27 June 2007

Business Data Catalog blog post series by Sahil Malik

Sahil Malik has a series of posts on MOSS 2007 Business Data Catalogs (aka BDCs).

Wednesday, 20 June 2007

MVP and SharePoint Web Parts

Bil Simser writes about Model-View-Presenter Pattern with SharePoint Web Parts. It is a simple, well explained example, and tying it back to Web Part development instead of the standard ASPX development is nice.

Friday, 16 March 2007

Removing chrome from SharePoint page

MS Dynamics CRM has some info on removing chrome from Web Part pages and document libraries. This is useful when combined with the Page Viewer Web Part so you can embed a list into another page. These methods involved mucking around with HTML, embedding styles, or embedding javascript to muck with styles and HTML.

An example of an embedded style is given in a comment posted to the first link first link. This involves adding the following to a content editor web part:

<style>
.ms-navframe {
  display:none;
}
.ms-bannerframe {
  display: none;
}
.ms-titleareaframe {
  display:none;
}
</style>

Another way is using WSS RPC methods with the dialogview parameter as described here. This method involves using owssvr.dll to view the information via a URL in this format:

http://[Server]/[Site Path]/_vti_bin/owssvr.dll?dialogview=FileOpen&location=Shared%20Documents

I'm a bit surprised there isn't a simple querystring parameter or similar to do this.