Wednesday 7 December 2011

Wrapping Images using the Html Agility Pack, TinyMce, Umbraco


Here is an Umbraco related rendering task that most people would probably do in the DOM.

You have an image that was inserted in tinymce. You want to wrap that image tag in some custom markup.

I used the Html Agility pack to do just that (code below)!

I coded in <figure> but you can use anything you like.

In a razor script I just did this:
var newBodyHtml = WrapImages(bodyHtml, "your outer css class", "your inner css class")

/// <summary>
/// Wraps body images in custom markup
/// E.g.
/// Turns <img src="" /> into  <figure class="<cssclassouter>"><a href=""><img src="" class="<cssclassinner>" /></a></figure>
/// </summary>
/// <param name="body"></param>
/// <returns></returns>
string WrapImages(string bodyHtml, string cssClassOuter, string cssClassInner)
{
 var doc = new HtmlAgilityPack.HtmlDocument();
 doc.LoadHtml(bodyHtml);

 // traverse html to find and replace image node
 var q = new Queue<HtmlNode>();
 q.Enqueue(doc.DocumentNode);
 while (q.Count > 0)
 {
  var item = q.Dequeue();
  HtmlNode node = null;

  // found image, need to wrap it!
  if (item.Name == "img")
  {
   // get image src from item
   string imageUrl = item.Attributes["src"].Value;

   // create new node
   node = HtmlTextNode.CreateNode("<figure class=\" + cssClassOuter + "\"><a href=\"" + imageUrl + "\" ><img src=\"" + imageUrl + "\" class=\" + cssClassInner + \" /></a></figure>");

   // set new html
   item.ParentNode.InnerHtml = node.OuterHtml;
  }
  else
  {
   // traverse children
   foreach (var child in item.ChildNodes) { q.Enqueue(child); }
  }
 }
 return doc.DocumentNode.OuterHtml;
}

Tuesday 1 November 2011

Image Gen, Image Cropper, DAMP, and Sencha with Umbraco


In this post I just want to share some tricks that have helped me with Umbraco and images.

I've been using Image Gen in conjunction with the Umbraco Image Cropper. It's great because you can specify a crop (with Image Cropper), then apply as many sizes of that crop as you want using Image Gen. Imagine you have a 16:9 crop and 4 different sizes of them.

I've also discovered the magic of Digibiz Advanced Media Picker (DAMP). This is a great package which allows you to upload and select images when using documents. Unfortunately the image cropper (when inserted in a document) does not directly work with DAMP, because DAMP saves as XML, and Image Cropper requires a url (eg. /media/123456/file.jpg). So there is a little trick to get that wired up.

Finally, when I was working for Cogworks, a requirement was to use Sencha for device specific image optimisation. This is a great easy to use online service which will download your image, then pass it to the brower in an optimised size for the device.

In this post I will show how to
- Wire up the Umbraco Image cropper to use the Digibiz Advanced Media Picker
- Use ImageGen in a cshtml file to make different sizes for those cropped images
- Use Sencha to make images device optimised

Update: This post assumes you have put your Image Cropper in a document (not media).

Wiring up the Umbraco Image cropper to use the Digibiz Advanced Media Picker 
The Umbraco Image Cropper requires a url (eg. /media/123456/file.jpg). DAMP stores XML. E.g:
<DAMP fullMedia="">
  <mediaItem>
 <Image id="1489" version="23113919-ca7a-4ab2-9e0e-3160cb3168af" parentID="1488" level="3" writerID="0" nodeType="1032" template="0" sortOrder="1" createDate="2011-08-12T12:06:09" updateDate="2011-08-12T12:05:52" nodeName="promo-img" urlName="promo-img" writerName="admin" nodeTypeAlias="Image" path="-1,1243,1488,1489">
   <umbracoFile>/media/36523/promo-img.jpg</umbracoFile>
   <umbracoWidth>1200</umbracoWidth>
   <umbracoHeight>519</umbracoHeight>
   <umbracoBytes>118873</umbracoBytes>
   <umbracoExtension>jpg</umbracoExtension>
   <imageCaption />
 </Image>
  </mediaItem>
</DAMP>
To work around this, all you need is to add a property (in my case a label), and assign the value of umbracoFile to that property on the AfterSave event. Then you can crop as normal.
void Document_AfterSave(Document sender, SaveEventArgs e)
{
 // make sure "umbracoFile" has value of the DAMP parsed umbracoFile
 string dampString = sender.getProperty("dampImage").Value.ToString();
 if (dampString.Contains("<DAMP"))
 {
  string url = XDocument.Parse(dampString).Descendants("umbracoFile").Single().Value;
  sender.getProperty("umbracoFile").Value = url;
 }
 else
 {
  sender.getProperty("umbracoFile").Value = string.Empty;
 }
} 

Now that you have your image uploaded and cropped, wouldn't it be great to be able to render your image like this?
@RenderPage("ShowImage.cshtml", node.Id, "dampImage", "some suffix", "152", "115", "some css class", "some title")
The parameters: node Id, the property alias (DAMP or normal file upload), a suffix for the image, width, height, a css class, a title for the image.



The magic script
The following razor script allows you to render DAMP images using ImageGen and Sencha! The great thing about this script is that it be called from another script or from macro markup. It will give you a default image if the image does not exist. It allows you to pass in a file suffix (useful because image cropper creates new files), define width and height, add css class, and a title! AND it will use Sencha to optimise your images depending on the viewing device!


@{ /* Displays a list of news items. */}
@using System.Linq;
@using umbraco.MacroEngines;
@using System.Xml.Linq;
@{
    DynamicNode d;
    string alias;
    string suffix;
    string width;
    string height;
    string cssClass;
    string title;
      
    if (PageData.Count > 0){
        // get values from PageData   
        d = new DynamicNode(PageData[0]);
        alias = PageData[1];
        suffix = PageData[2];
        width = PageData[3];
        height = PageData[4];
        cssClass = PageData[5];
        title = PageData[6];
    }
    else{
        d = new DynamicNode(@Model.Id);
        alias = @Parameter.PropertyAlias; 
        suffix = @Parameter.Suffix;
        width = @Parameter.ImageWidth;
        height = @Parameter.ImageHeight;
        cssClass = @Parameter.ImageClass;
        title = @Parameter.ImageTitle
    }
   
    string url = string.Empty;
    if(d.GetProperty(alias) != null)
    {
        // get image url or DAMP xml
        string storedUrlValue = d.GetProperty(alias).Value;
        if (!string.IsNullOrEmpty(storedUrlValue))
        {
            if (storedUrlValue.Contains("<DAMP"))
            {
                // parse DAMP xml to get image url 
                url = XDocument.Parse(storedUrlValue).Descendants("umbracoFile").FirstOrDefault().Value.GetUrl(suffix);
            }
            else
            {
                // not DAMP XML, just a url
                url = storedUrlValue.GetUrl(@suffix);
            }
        }
        else
        {
            // no value so render a default image
            <img src="/images/no-image.png"  alt="No-Image" />
            return;
        }

        // add image gen parameters
        url += "&width=" + width + "&height=" + height + "&constrain=true&compression=100";

        // get host - check for https if you wish
        var host = string.Format("http://{0}/", HttpContext.Current.Request.ServerVariables["HTTP_HOST"]);

        // finally render the image
        <img src="http://src.sencha.io/jpg95/@host/ImageGen.ashx?image=@url" alt="@title" class="@cssClass" />
    }
    else
    {
        <img src="/images/no-image.png"  alt="No-Image" />
    }
}

Here is the extension method for adding a suffix to the filename
public static string GetUrl(this string s, string suffix)
{
     return string.Format("{0}/{1}{2}{3}", Path.GetDirectoryName(s).Replace("\\", "/"), Path.GetFileNameWithoutExtension(s), suffix, Path.GetExtension(s));
}
I hope you find this useful. I definitely use it in every project... with the odd tweak here and there :)

Monday 17 October 2011

Getting Umbraco radiobutton list values and prevalues


You know how umbraco gives you an int when you're using a radiobutton list. Don't you wish it would give you the value selected in c#... Even possibly the values which are defined so you can check against them?

I discovered a (possibly convoluted) way of getting these. Note: the node object in the example below is of type Document

// get the key values defined in the datatype for property "myProperty"
var keyValues = ((KeyValuePrevalueEditor)node.getProperty("myProperty").PropertyType.DataTypeDefinition.DataType.PrevalueEditor.Editor).PrevaluesAsKeyValuePairList;

// get the value which maps to the int which Umbraco saves when you select the radiobutton
var value = umbraco.library.GetPreValueAsString(int.Parse(node.getProperty("myProperty").Value.ToString()))

Saturday 8 October 2011

Umbraco Model and DynamicNode overhead part 2


The results from the previous post were from a page which had a few macros on it. In true scientific fashion I decided to isolate the issue. I downloaded a fresh copy of Umbraco from the web matrix and ran it on iis express.

I made a doctype with nothing except a title. I made 2 templates and 2 macroscripts. One using Model, the other using DynamicNode.


Here are the macroscripts:

Model macroscript
@{
@Model.title
}

DynamicNode macroscript
@using umbraco.MacroEngines
@{
DynamicNode d = new DynamicNode(Model.Id);
@d.GetProperty("title");
}


The 2 following images are example traces (Model followed by DynamicNode). I am interested in the time it takes for the macro to finish loading. Ie. the last line Loading IMacroEngine script [done]

Model




Dynamic Node




Here is a table of results showing the Loading IMacroEngine script [done] line:


Dynamic node loaded on average 0.000041646741092 seconds faster than Model.

This does not seem like much, but it is interesting that instantiating a DynamicNode and getting a value from it is quicker than getting a value from Model. I was told that Model is dynamic but is actually a DynamicNode also. It sort of makes sense that a dynamic object would be more intensive than a typed one (even of the same type).

I'm wondering if there is some sort of effect of having multiple scripts (or nested scripts, or even possibly nested masterpages) on the page which compounds the time taken to load a single macro, and hence causing the script to load slower in the previous post.

I would be interested if people had some input.



UPDATE...
I decided to run the tests again except checking if the value is null.

Quick results...
Average (Model)
0.005558425393959
Average (DynamicNode)
0.00527851060727


Difference
0.000279914786689


So the difference is larger when you start doing things with Model.


Friday 7 October 2011

Umbraco Model and DynamicNode overhead





Just a quick post to show something interesting I discovered with Umbraco 4.7.1.


These 3 if statements do the same thing. However the first 2 turn out to be 2-3 times more expensive in terms of execution time.


if (Model.title == null)
{}





if (Model.title is umbraco.MacroEngines.DynamicNull)
{}





var current = new DynamicNode(Model.Id);
if (current.GetProperty("title") != null)
{}




What's more, is that if you call AnscestorOrSelf() on Model vs current (my instantiated DynamicNode) there is a doubling of those results.

It seems that there is some extra overhead going on with Model.



Friday 30 September 2011

Rental scam


So I've been trying to find a new place to live. I found a really nice property and contacted the individual. It was apparent from this response that it was a scam (probably from a hacked account).

Below is the email, followed by my response.


Hello Anthony,
Thank you for your prompt response, it's my pleasure. I previously live in the flat before i was transferred to Newcastle due to my job but there's no problem in inviting you for the viewing so I will need to take a day off from work in other to come for the viewing. The lady is currently on Holiday and she will be back on 10th of October. I have had lots of bad experience from potential tenants who will asked me to travel to London for the viewing but they will fail to show up, some will even asked me to wait after the viewing because they don't have cash on them and they will not come back. I have consulted my lawyer and we have resolved that any potential tenants should be able to prove in the following way in order to determine who is financially buoyant and that he or she can stick to appointment when scheduled and able to pay his/her rent when due.

So before I can come to London to show you the flat, I will need to see a proof that you are financially alright, truly interested in my flat and not a time waster, I will not ask you to email me your bank statement or payment slip because i have been fooled with fake bank statement and payslip. what you are going to do is this; you will make the transfer of one month rent plus security deposit(£1000) via western union money transfer by using your own name as the sender while the name of your friend or relative as the receiver. here is how you do the transfer; you will go to any nearby western union outlet/shop very close to you with £1000, when you get there, you will tell them you want to make a transfer using their service and you will be given a payment slip to fill, below is what you are going to fill on it;

Sender's name: Your own name
Receiver's name: Either your friend or relative as the receiver
Sender's address: Your own address
Receiver's address:London, United Kingdom
Amount to transfer: £1000

Once you fill it, you will give the form to the agent with the £1000 cash and he/she will give you a payment receipt, you will scan the payment receipt and email to me for confirmation and i will call western union customer care here in Newcastle to confirm if you truly do the transfer and once I confirm that the money is available to be pick up by the receiver, I will buy my train ticket to London immediately, email it to you so that you can know the day and time I shall be arriving for the viewing.
I will come and pick you and the person you transfer the money(either your friend or relative) at the tube station, take you to the flat for the viewing and after the viewing if you are ok with the flat, you will sign all the contract agreement forms and your friend will collect the money out from western union and pay me in cash. the flat keys and all the flat documents will be handed over to you immediately.
A little amount of money would be charge for the transfer but i don't want you to worry about that because my lawyer had advised me to be responsible for it so that you will not have anything to loose in the transaction just that I need to make sure you will not make me waste my time if I should come to London for the viewing, it will be refunded back to you immediately we meet for the viewing, so either you rent my flat or not, the charges would be given back to you. I will be happy if you can email me back now so that i can know how to schedule my time in other to meet you for the viewing, i hope to read back from you as soon as possible.

Cheers
Darren




MY RESPONSE - an email to abuse@hotmail.com ...

to: abuse@hotmail.com
cc: ********@hotmail.co.uk
(I removed the email address in case this is actually a hacked account)


Hi Hotmail

There is a scam coming from this account. Below is a Bing search on one of the sentences in the email (lower). As you can see it's a commonly indexed scam.

Please block this account.

And to Darren (unlikely that it is your real name)... you can go fuck yourself :)

Many thanks Hotmail dudes.


















Thursday 1 September 2011

How to parse Flickr Image Urls


I had to deal with parsing the multiple flickr urls that exist to access an image.

Did you know that there are many different Url's for a single photo?

Here are the one's I've discovered:

flickr.com/photos/userId/id/in/set-setId/
flickr.com/photos/userId/id/in/photostream/
flickr.com/photos/userId/id/someone else's list/
flickr.com/photos/userId/id/datetaken/
flickr.com/photos/userName/id/in/set-setId/
flickr.com/photos/userName/id/in/photostream/
flickr.com/photos/userName/id/someone else's list/
flickr.com/photos/userName/id/datetaken/
flickr.com/photo.gne?id=id

And here is some code to strip all these suckers to get the photo Id:


public string GetFlickrPhotoId(string url)
{
string s = url.ToLower();

// remove everything after /in/ if it exists
s = s.Substring(0, s.IndexOf("/in/") > 0 ? s.IndexOf("/in/") : s.Length);

// remove url, user name, userid, photo.gne?id=id, "/"
s = s.Substring(s.Contains("/") ? s.LastIndexOf("/") : 0, s.Length - (s.Contains("/") ? s.LastIndexOf("/") : 0));
s = s.Replace("photo.gne?id=", string.Empty);
s = s.Replace("/", string.Empty);
return s;
}



Wednesday 3 August 2011

Email scams

I guess this vaguely relates to a technical blog. If anything it should be a lesson to my uncle (and everyone else who goes on the internet) to install some anti-malware software.

Anyway, today I received an email from my uncle's email address. It was clear to me from the beginning that this was a scam. Below is the email correspondence. My replies are in bold.

Here it goes...
...
Aug 3, 2011 4:11 AM
I am sorry I did not mention my travel to the UK.I am at the Heathrow Airport having problems with the management because my luggage has exceeded weight limit.I will like you to help me with a loan of 700pounds to sort my self out.Please get back to me if you can help.
Thanks.
...

3 August 2011 08:22
Either this message is from a hacked email account or fees for airlines are beyond affordability.
The latter is unlikely.
...

3 August 2011 09:28
Thanks very much.I do not have access to a mobile phone and limited access to internet.You can send the money via western union money transfer to my personal details below.
Address-43 Belgrave Square, London, SW1X 8PA
United Kingdom.
Thanks for everything.
...

3 August 2011 10:28
How about you come meet me today and I can give you the cash.
...

3 August 2011 10:32
I am not where you are and I cant move as i dont have cash on me.Please try to help me send it via western union money transfer.

...


3 August 2011 10:39

Prove you are who you say you are and give me a call

02 **** **** ext 2011

...


3 August 2011 10:54

I have tried to explain to you that I have no access to a mobile phone and limited access to internet.Please try to see what can be done for me.** September 19** is ******'s date of birth.
...

3 August 2011 10:55

Please try to help me with this loan.

...


3 August 2011 11:24
Tell me exactly where you are. I can come to you. Or I have 1000s of friends and colleagues all over the world who can come to you to help.
...

3 August 2011 11:48
Heathrow Airport.
...

3 August 2011 13:00

I work near there.

Wait for me at terminal 4. I will be there on 20mins

...


3 August 2011 13:26
If I dont see you in 20 mins.I will take this as you are playing with me.
...

3 August 2011 14:07

Apparently they evacuated terminal 4 and moved everyone to terminal 5. I went to terminal 5 and you were not there.

What are you wearing? Are you dressing like a woman when you travel?

...


3 August 2011 21:57

Hi uncle

I guess I missed you. I was looking for a 6 foot asian man dressed in drag, with long curly platinum blonde hair. You still have your curls right?

Anyway, I could not transfer the money. But I went back to Heathrow and have left the £700 at the Western union desk with an albino African man with a Mexican accent, named rumplestiltskin. He will be waiting at the Heathrow desk at terminal 16. It is right next to the home office, border agency and MI5. Just get the piccadilly line to terminal 3 and walk west.

Good luck and I hope that you end up in jail for your scams.




Saturday 23 July 2011

Umbraco empty recycle bin hangs

So after crawling through forums I found a solution. The issue is to do with stray nodes.

Run this script and all your problems go away:


DELETE FROM umbracoNode
WHERE
(
id IN
(
SELECT umbracoNode_1.id
FROM umbracoNode AS umbracoNode_1 CROSS JOIN cmsContent
WHERE (umbracoNode_1.nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972'
)
AND
(
umbracoNode_1.id NOT IN
(
SELECT nodeId
FROM cmsContent AS cmsContent_1
)
)
)
)



Update:
If that doesnt work you can also try this:


Tuesday 28 June 2011

uBlogsy on Umbraco Starter kits

I'm happy to announce that uBlogsy is now featured in the starter kit section of Umbraco packages:






I've had a lot of support and suggestions from the moment this package was released, and it's been great. Stay tuned for a bunch of new features :)





Tuesday 7 June 2011

uBlogsy 1.2 Import by RSS video

This is a demo video for uBlogsy's import by RSS functionality in v1.2



uBlogsy - 1.2

So it's been about a week since I launched uBlogsy and I'm already up to version 1.2.

v1.01 was the first update which added famfamfam icons to the content nodes. It makes everything damn pretty to look at.

v1.1 added optional month folders after @dascoba mentioned them. This version also added an SEO tab. When the SEO properties are left empty, uBlogsy uses the title and body of posts.

v1.2 is todays release. It includes blog import from an RSS url, an alternate archive layout for FarmFreshCode related posts, and minor tweaks.

Thursday 2 June 2011

uBlogsy - A blog package for Umbraco using Razor

So I decided to create a blog using Umbraco.

I'm a huge fan of Razor, and since Umbraco supports Razor these days, uBlogsy was super easy to make.








ps. If anyone wants to contribute to making skins for uBlogsy, please get in touch.

Saturday 7 May 2011

uHidesy 1.1 - A way to show and hide properties and tabs in content nodes

After some helpful feedback from Chris Evans and Lennart Stoop, I have spent the last 2 days adding new functionality to uHidesy.


New in this version:

  • uHidesy by default now "tags" tabs and properties which will be hidden from the CMS editor
  • A checkbox has been added to allow hiding of tabs and properties (not tagging)


Download it here:






Thursday 5 May 2011

uHidesy - A way to show and hide properties and tabs in content nodes

Yesterday I released an awesome new Umbraco package called uHidesy.

uHidesy is a datatype, which when added, allows you to show and hide tabs and properties in content nodes.

Uses...

Scenario 1:
You make document types for a purpose. Then that purpose changes and you must add more properties and tabs. The annoying thing is that it messes up the elegant interface you created for your cms editor. Now you have properties and tabs in some content nodes which are never used. Dont you wish you could hide them?

Scenario 2:
You are told "the client doesnt need that functionality". You KNOW that they will ask for it eventually because IT JUST MAKES SENSE. So you add the properties and tabs to the document type anyway because it will save you a world of pain having to do it later. But dont you wish you could hide those properties and tabs?

Is there a way to show and hide properties and tabs in a content node? There is now!

Simply add the uHidesy data type to the Properties tab of the document types you wish to have control over. In the content node Properties tab, uHidesy lists the tabs and properties you can click on to toggle. Click save (or publish) and you're done!


Download it here:




Monday 11 April 2011

Mars rover exercise

A recruiter wanted to put me forward for a contract. Below is the programming test that they wanted me to perform. Further down is my approach to solving the problem.

Keep in mind that the recruiter told me that the “test” is likely not do-able in 1 hour, and that the company was more interested in my approach to the problem.

I'd like to point out that this is a serious solution and approach to the problem. It is a real world solution to a problem which was not do-able in the given time. I think it shows ingenuity and thinking out of the box.


Here is the programming task:


Software Engineer MARS Rover Exercise

As a guide you should spend 1 hour working on this task.

1. MARS Rover

Objective

The purpose of this test is to enable you to demonstrate your proficiency in solving problems using software engineering tools and processes.

Read the specification below and produce a solution. Your solution should be in the form of completed code.

If you are unsure of any part of the specification then you should ask to speak to a member of the Software Engineering team either via your recruitment agent or HR contact.

The problem specified below requires a solution that receives input, does some processing and then returns some output. You are free to implement any mechanism for feeding input into your solution. You should provide sufficient evidence that your solution is complete by, as a minimum, indicating that it works correctly against the supplied test data. Using a unit testing framework would satisfy these requirements.

The interviewer will be interested in:

· Your ability to read and interpret the specification below.

· The architectural design of your solution.

· The readability of your code.

· Your overall approach to this exercise.

Specification

A squad of robotic rovers are to be landed by NASA on a plateau on Mars.

This plateau, which is curiously rectangular, must be navigated by the rovers so that their on board cameras can get a complete view of the surrounding terrain to send back to Earth.

A rover's position is represented by a combination of an x and y co-ordinates and a letter representing one of the four cardinal compass points. The plateau is divided up into a grid to simplify navigation. An example position might be 0, 0, N, which means the rover is in the bottom left corner and facing North.

In order to control a rover, NASA sends a simple string of letters. The possible letters are 'L', 'R' and 'M'. 'L' and 'R' makes the rover spin 90 degrees left or right respectively, without moving from its current spot.

'M' means move forward one grid point, and maintain the same heading.

Assume that the square directly North from (x, y) is (x, y+1).

Input:

The first line of input is the upper-right coordinates of the plateau, the lower-left coordinates are assumed to be 0,0.

The rest of the input is information pertaining to the rovers that have been deployed. Each rover has two lines of input. The first line gives the rover's position, and the second line is a series of instructions telling the rover how to explore the plateau.

The position is made up of two integers and a letter separated by spaces, corresponding to the x and y co-ordinates and the rover's orientation.

Each rover will be finished sequentially, which means that the second rover won't start to move until the first one has finished moving.

Output:

The output for each rover should be its final co-ordinates and heading.

Test Input:

5 5

1 2 N

LMLMLMLMM

3 3 E

MMRMMRMRRM

Expected Output:

1 3 N

5 1 E



My approach to the Mars Rover Excercise

Upon reading the task, it dawned on me that a complete elegant solution might not be possible within the time frame of 1 hour. So I did what any good developer would do... check to see if there already existed a solution to my problem which I could re-implement. So I googled "Mars Rover Exercise".

The first link that appeared was a codeplex project:

http://marsroverexercise.codeplex.com/

Upon reading the description, it seemed that the task was EXACTLY the same as the one I was given. Upon inspection, it seemed that the code did what I needed it to do, so I downloaded the source.

I do not have Visual Studio 2010 at my current place of work, so I created a new 2008 solution, loaded in the files, did some minor refactoring to get the solution neat, and then built the solution.

I ran the project in debug mode and stepped through everything. The solution seemed to be the “solution” to my problem.

So then I zipped it up, wrote this document and presented it.

Time spent: 30mins


Saturday 12 March 2011

Export Umbraco members as a CSV




Last week I had to write a tool to get Umbraco members as a CSV file.

I started looking into the database tables and decided very quickly that raw SQL was going to be a pain. So I used Linq to SQL :)

This is a quick example of what was required.
There are a few things you have to be careful about. Look at what order your member properties are in. For instance, in my example I know that data[0].dataNvarchar is the first name. I used the dataNvarchar property because I know it's a string. For data[3] I used the dataInt property because it was a bool.


Here's the code.



public string GetMemberCSV()
{
MyDataContext db = new MyDataContext();
var members = (from m in db.cmsMembers select m);
StringBuilder sb = new StringBuilder();
sb.Append("Firstname, Lastname, Email, Opt-In, Verification Id, Verified, Created\n");

List emails = new List();

foreach (var m in members)
{
// get the properties
var data = m.umbracoNode.cmsPropertyDatas;
var firstName = data[0].dataNvarchar;
var lastName = data[1].dataNvarchar;
var email = data[2].dataNvarchar;
var optIn = data[3].dataInt ?? 0;
var verificationGuid = data[4].dataNvarchar;
var verified = data[5].dataInt ?? 1;
var created = m.umbracoNode.createDate.ToString();

if (!string.IsNullOrEmpty(email) && !emails.Contains(email))
{
sb.Append(string.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}\n", firstName, lastName, email, optIn, verificationGuid, verified, created));
emails.Add(email);
}
}

return sb.ToString();
}



I hope that was helpful.