Tuesday 22 October 2013

How to "fiddle" SOAP messages and WCF endpoints.

Today I needed a nice easy way to look at the raw SOAP data being sent over the wire between the WCF test client and a custom WCF endpoint. For a quick peek, I figured I’d turn to fiddler for this job, but found very quickly that fiddler didn’t capture the traffic – most likely because fiddler drops itself in as a proxy that your browser uses, but clearly the WCF test client doesn’t.

The fix though is easy.

In WCF tester, add your reference to your service, say http://localhost:12345/myservice.svc. Then, right click on the “Config File” node in WCF tester and select to edit with SvcConfigEditor. Find your endpoint under client -> endpoint, and then change http://localhost:12345/myservice.svc to http://localhost.fiddler:12345/myservice.svc and hey presto, the traffic gets pushed through fiddler.

You might wonder why I didn’t just add the service reference as http://localhost.fiddler:12345/myservice.svc - well, when I did that, any method invocations didn’t seem to trace in fiddler. May have been  user error on my part, but the above worked for me and I could diagnose the raw SOAP messages.

Thursday 15 November 2012

Windows 8 Store: Your Purchase Couldn’t be Completed

I've been struggling with this problem - I try to install a free windows 8 store app like trello and it just tells me that a problem occurred and my purchase couldn't be completed.

After googling around and finding a few solutions, none of which worked, I noticed that I had disabled the windows update service (I don't recall doing that but, hey). Enabling and starting the windows update service and magically the windows store worked again.

Friday 28 September 2012

Using SharePoint 2010 feature upgrades to update existing sites (Part II)

For the background to this, read my earlier post – feature upgrades part 1.

To summarise, we have an existing SharePoint portal, used for managing cases, where each case has it’s own site. We now need to update all case sites to add a web part to the landing page, and to add a new page entirely. Existing sites should be updated, and new sites being provisioned should have the new part and page.

For the source code, go grab it from codeplex here: http://featureupgradesample.codeplex.com/. There are 3 folders within the source, representing the solution at the beginning (001 - Start), after adding the web part upgrade (002 – Adding a web part to existing pages), and after adding the new page upgrade (003 – Adding a new page).

The starting point

The solution at the beginning is a simplified version of my client’s solution – it has a site definition for a case site, which references several standard features (publishing etc), and our “CaseSiteContent” feature.

image

The case site content feature contains a module – which in turn provisions a landing page on the case site with some prescribed web parts.

On our SharePoint portal, for the purposes of this exercise, I’ve provisioned a publishing site as the host, and within that, two case sites based on this starting solution.

A case site presently looks like this;

image

Exciting huh? The site contains one page – the landing page, with one content editor web part at the moment.

Adding the new web part

What we need to do now is the first part of the problem – add a new web part to these existing sites, and any future ones. Again, I’ll just be adding a content editor web part, to keep life simple.

Note, we aren’t going to touch the site definition. It’s already in use, and therefore shouldn’t be changed. We’re going to apply the updates against the feature.

We start by adding an extra element manifest to the case site content module. At the moment, the elements.xml in this module looks like this;

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Module Name="CaseSiteContent" Url="$Resources:cmscore,List_Pages_UrlName;" Path="CaseSiteContent\Pages">
        <File Url="default.aspx" Type="GhostableInLibrary">
            <Property Name="Title" Value="Landing Page" />
            <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/BlankWebPartPage.aspx" />
            <Property Name="ContentType" Value="$Resources:cmscore,contenttype_welcomepage_name;" />
 
            <AllUsersWebPart WebPartZoneID="Header" WebPartOrder="1">
                <![CDATA[
                <WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
                  <Title>Hello World</Title>
                  <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
                  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
                  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor">
                    <p>This is a simple case site example with a <strong>Hello World</strong> webpart in it. Soon it will be updated
                       via upgrades to add more pages and web parts</p>
                  </Content>
                </WebPart>
              ]]>
            </AllUsersWebPart>
        </File>
    </Module>
</Elements>

Now we could just add a new web part in there, and that would work for new sites being provisioned, but wouldn’t help us with the upgrade of existing sites. So, we’re going to put the new web part in a separate manifest, included in this module (for new sites), but also reference that separate manifest from the upgrade process.

Copy (just ctrl-drag) the elements.xml to elements.01.01.xml, and make sure you set it’s deployment type to ElementManifest;

image

The content of this new manifest should be as follows (it looks kinda similar to the original, because we’ve just added a slightly different content editor web part).

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Module Name="CaseSiteContent" Url="$Resources:cmscore,List_Pages_UrlName;" Path="CaseSiteContent\Pages">
        <File Url="default.aspx" Type="GhostableInLibrary">
            <Property Name="Title" Value="Landing Page" />
            <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/BlankWebPartPage.aspx" />
            <Property Name="ContentType" Value="$Resources:cmscore,contenttype_welcomepage_name;" />
 
            <AllUsersWebPart WebPartZoneID="Header" WebPartOrder="2">
                <![CDATA[
                <WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
                  <Title>New web part</Title>
                  <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
                  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
                  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor">
                    <p>This is a whole new web part</p>
                  </Content>
                </WebPart>
              ]]>
            </AllUsersWebPart>
        </File>
    </Module>
</Elements>

Now, the one thing I’m not sure about here is the impact of defining the same file (default.aspx) in the manifest. When we deploy this, it works as expected, but I’m not sure if there are any other ramifications of doing this. If there are, then using the API to add a web part would probably be a better solution.

After adding the new manifest, you should find that it is included in the containing feature automatically by visual studio. Confirm this by opening the case site content feature, and clicking manifest at the bottom – it should look like this;

<Feature xmlns="http://schemas.microsoft.com/sharepoint/" Description="Provisions the content necessary for a case site definition." Id="48002b3b-317b-4224-bb9d-b1716de3bcdd" Scope="Web" Title="CaseSiteContent">
  <ElementManifests>
    <ElementManifest Location="CaseSiteContent\Elements.xml" />
    <ElementFile Location="CaseSiteContent\Pages\default.aspx" />
    <ElementManifest Location="CaseSiteContent\Elements.01.01.xml" />
  </ElementManifests>
</Feature>

If we deployed now, and created a new site, it would work, but we’re not done yet. First of all we need to add a version number to the feature, and then define how to get from the current version (which will be 0.0.0.0) to our new version.

Open the feature again, and in the properties inspector, set the version property to 1.1.0.0;

image

Next, we’ll tell SharePoint how to go from version 0.0.0.0 to our current version. From the feature designer, select “Manifest”, then click “Edit Options” from the bottom of the screen, which presents some XML that will be merged with the main feature manifest. Click “Open in XML Editor” to make your life easy and enter the following;

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/">
    <UpgradeActions>
        <VersionRange BeginVersion="0.0.0.0" EndVersion="1.1.0.0">
            <ApplyElementManifests>
                <ElementManifest Location="CaseSiteContent/Elements.01.01.xml"/>
            </ApplyElementManifests>
        </VersionRange>
    </UpgradeActions>
</Feature>

This is saying, when this feature is upgraded from any version between 0.0.0.0 and up to, but not including 1.1.0.0, apply the new element manifest. So now we have a scenario where, new sites will run this new element manifest in anyway, and set it’s version to 1.1.0.0, and existing sites that aren’t patched up, will, during the upgrade process, run in just this extra manifest.

If we deploy this to SharePoint now, we can create a new case site and it contains our web part:

image

However, our existing sites still aren’t upgraded, and that’s because, the upgrade process doesn’t happen automatically. We need to get each of the sites that uses this feature, get the feature itself and call upgrade on it. We can do this in a number of ways – using powershell, writing a console application, or using Chris O’Briens feature upgrade toolkit. For the purposes of this demo though, we’re going to upgrade sites individually using powershell.

Let’s take a look at the features on the case sites so far. I now have 3 case sites at;

We can confirm this with powershell – the following script will allow you to list all the features on a given site, along with the running version and the feature version.

Param(
    [Parameter(Mandatory=$true)]
    [string] $site,
    [string] $filter)

# Load sharepoint powershell if not already loaded
$spSnapIn = Get-PSSnapIn | where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}
if( $spSnapIn -eq $null )
{
    Add-PSSnapIn Microsoft.SharePoint.PowerShell
}

# Get the sharepoint web
$web = get-spweb $site
if( $filter ) 
{ 
    $features = $web.features | where {$_.Definition.DisplayName -match $filter} 
}
else 
{ 
    $features = $web.features 
}

$features | select @{Name="Feature";Expression={$_.Definition.DisplayName}}, Version, @{Name="Feature Version";Expression={$_.Definition.Version}}

Save the above as “ListFeatureVersionsOnSite.ps1” (It’s also in the source code), and run it as follows;

.\ListFeatureVersionsOnSite.ps1 http://[server]/c1

You can also pass a filter parameter to only show features containing the filter value. So in our case, we run it with;

.\ListFeatureVersionsOnSite.ps1 http://[server]/c1 CaseSite

And we get this:

image

As you can see, they all report the feature version as being 1.1, but the first two sites are presently at 0.0.0.0, whilst the new one is up to date. As expected.

So, to upgrade the site, we just need to get the SPWeb object, get the appropriate SPFeature and then call upgrade on it. We can do that with this powershell script.

Param(
    [Parameter(Mandatory=$true)]
    [string] $site,
    [Parameter(Mandatory=$true)]
    [string] $filter
    )

# Load sharepoint powershell if not already loaded
$spSnapIn = Get-PSSnapIn | where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}
if( $spSnapIn -eq $null )
{
    Add-PSSnapIn Microsoft.SharePoint.PowerShell
}

# Get the sharepoint web
$web = get-spweb $site

# Get our feature
$feature = $web.features | where {$_.Definition.DisplayName -match $filter}
if( -not($feature))
{
    Write-Host "Feature $filter not found"
}
else
{
    # Does it need an upgrade?
    if( $feature.Version -lt $feature.Definition.Version )
    {
        Write-Host "Upgrading $feature.Definition.DisplayName from" $feature.Version "to" $feature.Definition.Version
        $feature.Upgrade($false)
    }
    else
    {
        Write-Host "$feature.Definition.DisplayName is already up to date at" $feature.Version
    }
}

Run it as UpgradeFeatureOnSite.ps1 [site] [featurefilter]. Here’s it running for case site one, which needed an upgrade:

image

Out of interest, running it against case site 3 which is already at v1.1.0.0 does this;

image

We run the script in against all of our sites, and then we can confirm the feature versions again using the script above. All the sites should report as now being at version 1.1.0.0

image

If we then take a look at our older case sites – we see the new element manifest has been applied and our new web part appears;

image

Adding the new page

The process to add a new page would be the same, I’ve gone ahead and demonstrated that in the source code, but I’m not going to detail it here. It’s exactly the same;

  1. Add another element manifest
  2. Increase the version of the feature (1.2)
  3. Add a new version range to the upgrade manifest, starting at 1.1 and ending at the new version (1.2), and reference the same element manifest.
  4. Deploy it
  5. Run the upgrade process across the existing sites

Source code

Is available here: http://featureupgradesample.codeplex.com/

Footnotes and disclaimers

Here’s the “it works on my machine” bit!

  • I’m still not 100% about the duplicate file being put in the pages library across separate manifests. I’m hoping someone can assure me this is ok, otherwise, my plan would be to instead;
    • Put the new web part in the main manifest file (for new sites)
    • Use the custom upgrade action to trigger the feature upgrading code in a feature receiver, which will use the limited web part manager to inject the new web part.

  • A caveat.
    • If you have sites with a customised page that is checked out/not published yet which the upgrade process is targeting, the upgrade manifest won’t work. If you have this scenario, it might be an idea to use code, to ensure the page is checked in and published before it changes it.

And lastly, for upgrading features en-masse, take a look at the feature upgrade tool from Chris O’Brien – it’s on codeplex too, here: http://spfeatureupgrade.codeplex.com/

Using SharePoint 2010 feature upgrades to update existing sites.

One of my clients has a large SharePoint application, used for managing cases. As each case is raised, a new SharePoint site is provisioned and there, collaborators work together to move the case through to a resolution. Ultimately, the SharePoint site is used as the collaboration portal, the document store, and to surface an MVC application that provides the bulk of the functionality and process management through K2 workflow.

The system does a lot more than that, but for the purposes of this post, that’s enough background. Suffice it to say, it’s 3+ years old now and contains ten’s of thousands of case sites, and they need some changes.

Two of these changes are actually what I’d imagine are quite common scenarios. They need to;

  • Create some new functionality, and provision a new web part on all existing case sites (and have the new web part on new one’s too of course)
  • Add a new page to all case sites, with another set of web parts.

I wasn’t sure how to do this initially. If you know me, you’re aware that my speciality is application development, so the MVC part of this project, and I know enough SharePoint to get by, but this one had me stumped for a day.

The structure of the SharePoint solution is quite straight forward – for a case site, we have;

  • A “case site” site definition.
  • Several “features” associated with the site definition (not stapled, just each feature activated through the site definition).
  • One of these features contains a module, which provisions the pages of the case site and the web parts contained therein.

 

The first experiment

The first thing I tried, was just updating the page provisioning module to add the web parts. As I figured, this failed – it worked fine for new sites, but existing sites didn’t have the new web parts added to them, so I started reading up on the feature upgrade capabilities of SharePoint. I found these articles useful:

 

Solving it

So, armed with my new-found knowledge (ha), I finally got this working. In my next post I’ll address two scenarios, and provide all the source code that demonstrates the approach I settled on.

  • Adding a web part to a sharepoint page, and upgrading the one’s already in use.
  • Adding a new page to a sharepoint site, and adding it to existing sites.

Wednesday 27 June 2012

MSFT Frustrations

Anyone else wondering who’s calling the shots over in Redmond at the moment? I wonder if they realise just how hard they are making things for us developers? Things that are bugging me at the moment include;

  • They still think IE is king and continue to build products like CRM to be IE only.
  • They don’t force a better world by making IE updates be “required” on windows update – imagine if IE7 disappeared overnight!
  • Stupid decisions like the one to not support IE9 on XP.
  • Marketing hype – like CRM is a development platform, when there’s no development story around it at all.

The reason I’m so narked today is, I’m working on a project that features significant integration to CRM Online, which has limited options for heavy customisation. To do anything useful, you either need to use Silverlight or an externally hosted ASP.NET app (on azure in my case) and use single sign on between the two. And this presents a set of problems;

  • This is an application within a large enterprise.
  • They still have thousands of desktops using XP
  • They still have thousands of desktops with IE7
  • They also have chrome on all these desktops.
  • The requirement is for a rich, HTML5 UI
    • Think single page apps, backbone, moustache, rest interfaces and an MVC application marshalling requests for data to CRM and a bunch of external services.

You can probably see where I’m going with this.

  • A HTML5 UI won’t work on IE7 – it’s not compliant with many of the standards needed, meaning we need to work in lots of IE7 specific mark-up and styling.
  • To test IE7, I need a virtual environment.
  • I can’t switch to chrome as dynamics thinks it’s a mobile browser.
  • I can’t upgrade to IE9 as it’s not available on XP.
  • Upgrading thousands of desktops to Windows 7 and IE9 isn’t going to happen overnight.
  • I can’t use Silverlight – it’s dead technology. (We all know there isn’t going to be a Silverlight 6)

It’s not the end of the world, but I am confused by their lack of co-operation across divisions.

Why couldn’t they have just put the effort in to make dynamics cross browser? Why don’t they just force us all to upgrade to the latest and greatest through windows update? Why don’t they learn from chrome and make IE9 available on XP? Why kill off a perfectly good RIA technology for LOB applications.

To their credit, as far as CRM is concerned, there is an update coming – the Q2 2012 update for CRM will be cross browser, so I guess I should be thankful there, but we’re going live on Monday, so that doesn’t help with immediate concerns.

I look forward to the day when CRM has a real developer story similar at least to SharePoint solutions, to when cross domain calls are properly nailed and ubiquitous in the browser, where single sign on is a breeze and where everyone’s browser is fully compliant with the latest standards.

I can dream….

Monday 28 November 2011

NHibernate query patterns redux– what does 3.2 bring to the table?

Some time ago, I wrote a brief cookbook of NHibernate query patterns. In this post I’m going to look at the same query patterns, but using some of the new features of NHibernate 3 – specifically the baked in Linq provider that makes querying a breeze. I’ve published the code for this to codeplex here.

If you want to follow along, you’ll need the adventure works sample database set up on your SQL server. I’m also going to ignore the mapping configuration for now, but in the sample code, I’ve used the HBM mapping format – if I were doing this “for real” though, I’d be using the new “Loquacious” configuration features to map up my domain classes – why it’s called Loquacious, I have no idea, but it’s basically a baked in replacement for fluent NHibernate.

Ok, so the tables we’re interested in for this exercise are shown below;

image

Find all addresses in the city of London.

Starting out nice and easy;

   1: var query = from a in _session.Query<Address>() 
   2:             where a.City == "London" 
   3:             select a;
Find all addresses in London, where postcode starts with SW

Still pretty simple, but a good test of how the provider translates the string operation “StartsWith” into SQL;

   1: var query = from a in _session.Query<Address>() 
   2:             where a.City == "London" && a.PostCode.StartsWith("SW") 
   3:             select a;

Find all addresses with a parent state/province that has a country/region code of GB

Querying down a join is simplistic;

   1: var query = from a in _session.Query<Address>() 
   2:             where a.StateProvince.CountryCode == "GB" 
   3:             select a;

Find all addresses with a parent state/province that has a country/region code of GB or FR

Still straight forward;

   1: var query = from a in _session.Query<Address>() 
   2:             where a.StateProvince.CountryCode == "GB" || a.StateProvince.CountryCode == "FR" 
   3:             select a;

Find all customer accounts with a "Home” address in the region of GB

Getting a bit more complex, we’re joining in the chain from customer to address to do some filtering. The query this generates is still nice and efficient;

   1: var query = from c in _session.Query<Customer>()
   2:             join ca in _session.Query<CustomerAddress>() on c equals ca.Customer
   3:             join a in _session.Query<Address>() on ca.Address equals a
   4:             where ca.Type.Name == "Home" && a.StateProvince.CountryCode == "GB"
   5:             select c;

Find all customer accounts with a “Home” address in the region of GB with more than one order

Now, this sounds complex, but actually, it’s the same query as above, but with some projections. No need to join the orders table in;

   1: var query = from c in _session.Query<Customer>()
   2:             join ca in _session.Query<CustomerAddress>() on c equals ca.Customer
   3:             join a in _session.Query<Address>() on ca.Address equals a
   4:             where ca.Type.Name == "Home" && a.StateProvince.CountryCode == "GB" && 
   5:                   c.Orders.Count() > 1
   6:             select c;

Find all customer accounts with a “Home” address in the region of GB with more than 2 orders and a total spend over $6000

Building on the last query, let’s do some more projections for our query.

   1: var query = from c in _session.Query<Customer>()
   2:             join ca in _session.Query<CustomerAddress>() on c equals ca.Customer
   3:             join a in _session.Query<Address>() on ca.Address equals a
   4:             where ca.Type.Name == "Home" && a.StateProvince.CountryCode == "GB" && 
   5:                   c.Orders.Count() > 2 && c.Orders.Sum( ov => ov.Total ) > 6000
   6:             select c;

In closing….

In my opinion, this makes life much easier than trying to remember how to use aliases, detached criteria and so on – although if you prefer that mechanism, it’s all still there, please yourself Smile

Tuesday 27 September 2011

DRL DevJam 2011

As you may or may not know, I changed jobs in June (around the time of my last post) and my new employer has been keeping me pretty much swamped. If you’d like to complain about the lack of Orchard posts and other general guff, you can now do it in person! We’re hosting a developer evening at our head office here in Bolton – come along and not only can you beg my boss to give me some time to build more samples and posts, but also we’ve got a pretty decent evening planned including;

  • A coding challenge competition with prizes!
    • £100 amazon vouchers
    • Super top range mouse and keyboard
    • Command launcher USB pens
  • Fun
  • Lots of discussion about technology, kanban, lean and other agile-y topics
  • Fun
  • Food and drinks (there will be a healthy option if you want it!)
  • Fun
  • A caricature to remind you of your evening with us.
  • And more fun…

This is a new thing for us, it might become a regular gathering, or it might be a one off, we’ll see how it goes, but part of the reason for the event is, we’re looking for bright individuals like you to join our teams and we figured holding a nice geek out party (or DevJam as we like to call it) would be a great way to get to know you and you us.

So, if you’re interested, the date is 24th October 2011, from 6:30pm until 9pm. You can get more information about the event, our company and to register for your free place here: http://www.eventbrite.com/event/2173571210/efbnen

Hope to see you there!

Wednesday 8 June 2011

Real world Orchard CMS–part 7–finding content

As I said in my earlier post, this series is about creating a real world site with Orchard CMS, I’m not covering using Orchard to manage the site, just the technical development parts. All of the code is available on codeplex here : http://orchardsamplesite.codeplex.com/ (the code for this post is in part-07 subdirectory)

Preamble

In this post I want to take a look at how we can use the orchard API to discover content. Continuing in the series with our fictitious product company, we want our product pages to be able to easily display a list of related content. We’ll find this content using the built in tags module that orchard provides and we’ll build a widget that allows us to find content with specific tags and display it as a list. Our spec is pretty simple;

  • Allow administrator to drop a widget on a layer to find content that has specific tags.
  • Allow the widget to be configured to require that for content to match it only needs to have one of the tags
  • Allow the widget to be configured to require that for content to match it needs to have all of the tags
  • Allow the widget to control whether or not the current page/content should be excluded if it were to match the tags
  • Allow the widget to be configured to only retrieve N content items.
  • Allow the widget to be configured to present a simple list of content item titles as links or to display the content item in summary form.

Doing this we can use the widget for a variety of purposes;

  • Display a list of featured content on the homepage (content tagged “featured” and “home”)
  • Display a list of related content within a product page (content tagged “product-X”)
  • Display a list of FAQ articles and posts within a product page (content tagged “product-X” and “faq”)
  • etc…

Setting up

Well, we’ve got to start with our old friends that make up the overall widget the record, part, handler, driver, migration, placement.info, module.txt and editor and display templates (phew! a lot of plumbing huh?). I’ve covered these topics in a fair amount of detail already and the docs are already quite extensive so for this post I’ll discuss mainly the driver in more detail as this is where the guts of the content discovery goes on;

First of all, let’s start with the record: in the SampleSiteModule project, create /models/RelatedContentWidgetRecord.cs;

using Orchard.ContentManagement.Records;
using Orchard.Environment.Extensions;

namespace SampleSiteModule.Models
{
    [OrchardFeature("RelatedContent")]
    public class RelatedContentWidgetRecord : ContentPartRecord
    {    
        public virtual string TagList { get; set; }
        public virtual int MaxItems { get; set; }
        public virtual bool ExcludeCurrentItemIfMatching { get; set; }
        public virtual bool MustHaveAllTags { get; set; }
        public virtual bool ShowListOnly { get; set; }
    }
}

Remember our module is exposing all of it’s different components as features that can be independently turned on/off, hence why all of our code is marked with the OrchardFeature attribute. The record describes all the settings we’ll use for our widget – the TagList property will be a comma separated list of tags to search for, MaxItems will control how many items we get at most, and the three bools control the widget behaviour – ExcludeCurrentItemIfMatching will strip out the current content item if it matches the rules (so a link to the current page doesn’t appear in the widget’s list), MustHaveAllTags controls whether all the specified tags must be present on the content items to be deemed a match or whether if can just contain at least one of them and ShowListOnly will allow the widget to render a simple list of links or a full blown summary view of the content items.

Next, we need to wrap the record up in a part - /models/RelatedContentWidgetPart.cs;

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.Environment.Extensions;

namespace SampleSiteModule.Models
{
    [OrchardFeature("RelatedContent")]
    public class RelatedContentWidgetPart : ContentPart<RelatedContentWidgetRecord>
    {
        [Required]
        public string TagList
        {
            get { return Record.TagList; }
            set { Record.TagList = value; }
        }

        [Required]
        [DefaultValue(5)]
        public int MaxItems
        {
            get { return Record.MaxItems; }
            set { Record.MaxItems = value; }
        }

        [DefaultValue(true)]
        public bool ExcludeCurrentItemIfMatching
        {
            get { return Record.ExcludeCurrentItemIfMatching; }
            set { Record.ExcludeCurrentItemIfMatching = value; }
        }
        
        [DefaultValue(false)]
        public bool MustHaveAllTags 
        { 
            get { return Record.MustHaveAllTags; }
            set { Record.MustHaveAllTags = value; }
        }

        [DefaultValue(true)]
        public bool ShowListOnly
        {
            get { return Record.ShowListOnly; }
            set { Record.ShowListOnly = value; }
        }
    }
}

Nothing groundbreaking in there, so moving swiftly on, the handler – /handlers/RelatedContentWidgetRecordHandler.cs

using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using SampleSiteModule.Models;

namespace SampleSiteModule.Handlers
{
    [OrchardFeature("RelatedContent")]
    public class RelatedContentWidgetRecordHandler : ContentHandler
    {
        public RelatedContentWidgetRecordHandler(IRepository<RelatedContentWidgetRecord> repository)
        {
            Filters.Add(StorageFilter.For(repository));
        }
    }
}

And the driver – for now, just create the following shell and we’ll build it up in a second – /drivers/RelatedContentWidgetDriver.cs

using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Environment.Extensions;
using SampleSiteModule.Models;

namespace SampleSiteModule.Drivers
{
    /// <summary>
    /// Content part driver for the related content widget
    /// </summary>
    [OrchardFeature("RelatedContent")]
    public class RelatedContentWidgetDriver : ContentPartDriver<RelatedContentWidgetPart>
    {
        protected override DriverResult Display(RelatedContentWidgetPart part, string displayType, dynamic shapeHelper)
        {
            var list = shapeHelper.List();

            return ContentShape("Parts_RelatedContentWidget",
                () => shapeHelper.Parts_RelatedContentWidget(
                        ShowListOnly : part.ShowListOnly,
                        ContentItems : list
                        ));
        }

        protected override DriverResult Editor(RelatedContentWidgetPart part, dynamic shapeHelper)
        {
            return ContentShape("Parts_RelatedContentWidget_Edit",
                () => shapeHelper.EditorTemplate(
                    TemplateName: "Parts/RelatedContentWidget",
                    Model: part,
                    Prefix: Prefix));
        }

        protected override DriverResult Editor(RelatedContentWidgetPart part, IUpdateModel updater, dynamic shapeHelper)
        {
            updater.TryUpdateModel(part, Prefix, null, null);
            return Editor(part, shapeHelper);
        }
    }
}

Let’s setup a migration (note: in the code on codeplex, because I built this up in stages, the migration is a little different as it has a create and two updates to progressively add features) – create /Migration-RelatedContentWidget.cs;

using System.Data;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
using Orchard.Environment.Extensions;
using SampleSiteModule.Models;

namespace SampleSiteModule
{
    [OrchardFeature("RelatedContent")]
    public class MigrationsRelatedContentWidget : DataMigrationImpl
    {
        public int Create()
        {
            // Define the persistence table as a content part record with
            // my specific fields.
            SchemaBuilder.CreateTable("RelatedContentWidgetRecord", 
                table => table
                    .ContentPartRecord()
                    .Column("TagList", DbType.String, a => a.Unlimited())
                    .Column("MaxItems", DbType.Int32)
                    .Column("ExcludeCurrentItemIfMatching", DbType.Boolean)
                    .Column("MustHaveAllTags", DbType.Boolean)
                    .Column("ShowListOnly", DbType.Boolean)
                    );

            // Tell the content def manager that our widget is attachable
            ContentDefinitionManager.AlterPartDefinition(typeof(RelatedContentWidgetPart).Name,
                builder => builder.Attachable());

            // Tell the content def manager that we have a content type 
            // the parts it contains and that it should be treated as a widget
            ContentDefinitionManager.AlterTypeDefinition("RelatedContentWidget",
                cfg => cfg
                    .WithPart("RelatedContentWidgetPart")
                    .WithPart("WidgetPart")
                    .WithPart("CommonPart")
                    .WithSetting("Stereotype", "Widget"));

            return 1;
        }
    }
}

All of that should be pretty familiar, creates the record table, defines the widget part and the widget type with appropriate stereotype etc. See earlier posts in this series if you’re not sure what this is doing.

Next, we need to update placement.info so our parts will appear;

<Placement>
    <Place Parts_TwitterWidget="Content:1"/>
    <Place Parts_TwitterWidget_Edit="Content:7.5"/>

    <Place Parts_RelatedContentWidget="Content:1"/>
    <Place Parts_RelatedContentWidget_Edit="Content:7.5"/>

    <!-- Synopsis -->
    <!-- Note the summary view is the only display mode... -->
    <Match DisplayType="Summary">
        <Place Parts_Synopsis="Content:10"/>
    </Match>
    <Place Parts_Synopsis_Edit="Content:8"/>
</Placement>

We need to define the editor template next, in /Views/EditorTemplates/Parts/RelatedContentWidget.cshtml

@model SampleSiteModule.Models.RelatedContentWidgetPart

<fieldset>
    <legend>Related Content</legend>
    <div class="editor-label">@T("Tags to find (comma separated)"):</div>
    <div class="editor-field">
        @Html.TextBoxFor( m => m.TagList )
        @Html.ValidationMessageFor( m => m.TagList )
    </div>

    <div class="editor-label">@T("Items must have all tags"):</div>
    <div class="editor-field">
        @Html.CheckBoxFor(m => m.MustHaveAllTags)
    </div>

    <div class="editor-label">@T("Exclude current content if matches?"):</div>
    <div class="editor-field">
        @Html.CheckBoxFor(m => m.ExcludeCurrentItemIfMatching)
    </div>
    
    <div class="editor-label">@T("Number of items to find"):</div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.MaxItems)
        @Html.ValidationMessageFor(m => m.MaxItems)
    </div>

    <div class="editor-label">@T("Show as list only? (Setting this will show just a basic list, otherwise the full summary view will be rendered for each item)"):</div>
    <div class="editor-field">
        @Html.CheckBoxFor(m => m.ShowListOnly)
    </div>
</fieldset>

and the display template in /Views/Parts/RelatedContentWidget.cshtml;

@using Orchard.ContentManagement;
@{
    IEnumerable<object> latest = Model.ContentItems;
}
@if (latest == null || latest.Count() < 1) {
<p>@T("No related content.")</p>
}
else {
    <ul class="content-items">
    @foreach (dynamic item in latest) 
    {
        string title = item.Title;
        ContentItem contentItem = item.ContentItem;
        <li class="content-item-summary">
        @if( Model.ShowListOnly )
        {
            @Html.ItemDisplayLink(title, contentItem)
        }
        else
        {
            @Display(item)
        }
        </li>
    }
    </ul>
}

We could have just left the default widget and list templates to render everything for us, but we wanted control over the list formatting and whether or not to just show links or full summary content, so in this case, we created the above template. Note the condition on ShowListOnly – if it’s not going to just show a list, we simply call @Display(item) and this will take care of  rendering out the content item in summary format (regardless of it’s type).

The last thing to do now is update module.txt to reflect our new feature;

Name: Product Types
Category: Sample Site
Description: Content types for product list and main product page
Dependencies: Contrib.Reviews, Mello.ImageGallery
AntiForgery: enabled
Author: Tony Johnson
Website: http://www.deepcode.co.uk
Version: 1.0
OrchardVersion: 1.1
Features:
    TwitterWidget:
        Name: Twitter Widget
        Category: Sample Site
        Description: Widget for latest tweets
    Synopsis:
        Name: Synopsis
        Category: Sample Site
        Description: Allows synopsis to be added to types
    RelatedContent:
        Name: Related content widget
        Category: Sample Site
        Description: Widget to find content based on tags

Where are we

That’s a lot of stuff to put in place to get the widget ready and so far, it won’t do anything, which we’ll fix in a second, but you should now be able to enable this feature through the orchard admin interface and add a related content widget to your pages. You should see the following configuration;

image

and the display on the page will presently just show the no related content message (note here I’m using the default theme);

image

Updating the driver to get some content

This was one of the more difficult parts of this post, it took me an age to work out how the query interface worked, which was quite a surprise, but once you understand it, it’s pretty clear. Again, as ever, thanks to randompete and bertrandleroy on the forums for helping me out.

Getting content with one of the tags – the basic query

Lets start by just getting a list of content that has one of the tags, is ordered correctly and returns just the number of entries we’re looking for. To begin, we’re going to need an IContentManager injected by autofac that we can use to query content, so give the driver a constructor that takes one of these and store it into a private readonly field;

private readonly IContentManager _cms;

public RelatedContentWidgetDriver(IContentManager cms)
{
     _cms = cms;
}

We can now use this to query for content in our driver’s display method – change it as follows;

   1:          protected override DriverResult Display(RelatedContentWidgetPart part, string displayType, dynamic shapeHelper)
   2:          {
   3:              // Convert CSV tags to list
   4:              List<string> tags = new List<string>();
   5:              if (!String.IsNullOrWhiteSpace(part.TagList))
   6:              {
   7:                  Array.ForEach(part.TagList.Split(','), t =>
   8:                  {
   9:                      if (!String.IsNullOrWhiteSpace(t))
  10:                      {
  11:                          t = t.Trim();
  12:                          if (!tags.Contains(t))
  13:                              tags.Add(t);
  14:                      }
  15:                  });
  16:              }
  17:   
  18:              // If we have no tags.....
  19:              if (tags.Count < 1)
  20:              {
  21:                  return ContentShape("Parts_RelatedContentWidget",
  22:                      () => shapeHelper.Parts_RelatedContentWidget(
  23:                              ContentItems: shapeHelper.List()
  24:                              ));
  25:              }
  26:   
  27:              IEnumerable<TagsPart> parts = 
  28:                  _cms.Query<TagsPart, TagsPartRecord>()
  29:                  .Where(tpr => tpr.Tags.Any(t => tags.Contains(t.TagRecord.TagName)))
  30:                  .Join<CommonPartRecord>()
  31:                  .OrderByDescending(cpr => cpr.PublishedUtc)
  32:                  .Slice(part.MaxItems);
  33:   
  34:              // Create a list and push our display content items in
  35:              var list = shapeHelper.List();
  36:              list.AddRange(parts.Select(p => _cms.BuildDisplay(p, "Summary")));
  37:   
  38:              return ContentShape("Parts_RelatedContentWidget",
  39:                  () => shapeHelper.Parts_RelatedContentWidget(
  40:                          ShowListOnly : part.ShowListOnly,
  41:                          ContentItems : list
  42:                          ));
  43:          }

Ok, so the first section (lines 4-16) are just concerned with taking a CSV list from the widget’s part and splitting it down into a unique list of tags that we can search for. This takes into account spaces, empty items and so on, so just gives us a more sane list of tags to search for than what a user might have keyed. Lines 18-25 just deal with the condition where we have no tags at all, in which case we’re just returning the same as we did in our earlier example – an empty list that will just render the “no related content” message.

The code gets a little more interesting at line 27-32. Line 28 first of all starts a query against the TagsPart, so this will query against the properties exposed from any content item that has the tags part attached to it, on line 29 we then tell the query engine to build a query that looks at the tags associated with the content to determine if the tag name is in our provided list, if it is, it will be included in the results, if not, it won’t.

Line 30 then joins the common part onto the query for the next part of the query (this will do an inner join to bring the common part properties in) and line 31 orders the results, in descending order, by the published date exposed from the common part record. Finally, line 32 actually executes the query, getting just the first N rows only (MaxItems part property).

On line 35/36 we build a list shape and add the items we’ve found to it, asking the IContentManager to build the display shape for the item, in summary display mode (not necessarily what will be rendered, this just sets the shapes up into the shape tree).

Finally, line 38, we return the part shape as normal, containing the list we’ve build and the flag to indicate how we want the results formatted.

Excluding the current page from the results

Next we’ll get the widget to exclude the current page if that is appearing in the links. Interestingly there doesn’t appear to be any reliable way to ask orchard what the ID is of the current content item being rendered (if there is one, you won’t have one in a themed custom controller action perhaps), so, with a little help from randompete, I decided to take the current request URL and manually match it to the routepart urls to get the ID of the content item being rendered (most navigable content has the routepart).

With the content id in hand, it’s then trivial to add an exclusion to the above query so that item doesn’t get included. To start with though, we’ll need a way to get access to the current work context, so add a dependency and field on the driver for an IWorkContextAccessor;

private readonly IContentManager _cms;
private readonly IWorkContextAccessor _work;

public RelatedContentWidgetDriver(IContentManager cms, IWorkContextAccessor work)
{
    _cms = cms;
    _work = work;
}

And then, add a method to your driver that will get the current id of the page/content being rendered for the current url;

/// <summary>
/// Helper that will attempt to work out the current content id from the url
/// of the request.
/// </summary>
/// <param name="defaultIfNotFound"></param>
/// <returns></returns>
private int TryGetCurrentContentId(int defaultIfNotFound)
{
    string urlPath = _work.GetContext().HttpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2);

    var routableHit = _cms
        .Query<RoutePart, RoutePartRecord>(VersionOptions.Published)
        .Where(r => r.Path == urlPath)
        .Slice(1).FirstOrDefault();

    if (routableHit != null) return routableHit.Id;

    return defaultIfNotFound;
}

This isn’t doing anything particularly clever, it’s getting the URL of the current request (relative to the web app itself and stripping off the resultant ~/ from the beginning) and then performing another query against the RoutePart’s Path property to find the one that matches. If it is found, we return the id of the item that was found.

Back in our display method we can now use this to exclude the current item;

// Get current item
int currentItemId = -1;
if( part.ExcludeCurrentItemIfMatching )
    currentItemId = TryGetCurrentContentId(-1);

IEnumerable<TagsPart> parts =
    _cms.Query<TagsPart, TagsPartRecord>()
    .Where(tpr => tpr.Tags.Any(t => tags.Contains(t.TagRecord.TagName)))
    .Join<CommonPartRecord>()
    .Where(cpr => cpr.Id != currentItemId)
    .OrderByDescending(cpr => cpr.PublishedUtc)
    .Slice(part.MaxItems);

Extend the query to honour our MustHaveAllTags part property.

And finally, we’ll change our query according to the MustHaveAllTags property on our widget. The display method has changed a bit, so here it is in it’s entirety with the changed area highlighted;

protected override DriverResult Display(RelatedContentWidgetPart part, string displayType, dynamic shapeHelper)
{
    // Convert CSV tags to list
    List<string> tags = new List<string>();
    if (!String.IsNullOrWhiteSpace(part.TagList))
    {
        Array.ForEach(part.TagList.Split(','), t =>
        {
            if (!String.IsNullOrWhiteSpace(t))
            {
                t = t.Trim();
                if (!tags.Contains(t))
                    tags.Add(t);
            }
        });
    }

    // If we have no tags.....
    if (tags.Count < 1)
    {
        return ContentShape("Parts_RelatedContentWidget",
            () => shapeHelper.Parts_RelatedContentWidget(
                    ContentItems: shapeHelper.List()
                    ));
    }

    // See if we can find the current page/content id to filter it out
    // from the related content if necessary.
    int currentItemId = -1;
    if( part.ExcludeCurrentItemIfMatching )
        currentItemId = TryGetCurrentContentId(-1);

    // Setup a query on the tags part
    IContentQuery<TagsPart, TagsPartRecord> query = _cms.Query<TagsPart, TagsPartRecord>();

    if (part.MustHaveAllTags)
    {
        // Add where conditions for every tag specified
        foreach (string tag in tags)
        {
            string tag1 = tag; // Prevent access to modified closure
            query.Where(tpr => tpr.Tags.Any(t => t.TagRecord.TagName == tag1));
        }
    }
    else
    {
        // Add where condition for any tag specified
        query.Where(tpr => tpr.Tags.Any(t => tags.Contains(t.TagRecord.TagName)));
    }
            
    // Finish the query (exclude current, do ordering and slice max items) and execute
    IEnumerable<TagsPart> parts = 
        query.Join<CommonPartRecord>()
        .Where(cpr => cpr.Id != currentItemId)
        .OrderByDescending(cpr => cpr.PublishedUtc)
        .Slice(part.MaxItems);

    // Create a list and push our display content items in
    var list = shapeHelper.List();
    list.AddRange(parts.Select(p => _cms.BuildDisplay(p, "Summary")));

    return ContentShape("Parts_RelatedContentWidget",
        () => shapeHelper.Parts_RelatedContentWidget(
                ShowListOnly : part.ShowListOnly,
                ContentItems : list
                ));
}

As you can see, the only difference between “must have all tags” and “must have one of the tags” is the all tags adds a where clause for each tag directly – the rest of the query is unchanged.

Until next time

That’s it for this post – as ever, grab the code from codeplex: http://orchardsamplesite.codeplex.com under the part-07 folder (I’ve retired the final folder by the way as the posts are keeping up with the code now!). In the next post I’ll be looking at creating sub-navigation.