Prices including tax on uCommerce
Friday, December 14, 2012 4:03:30 PM (GMT Standard Time, UTC+00:00)
Most of our retailers price point their products to include tax i.e. a shirt that costs you £100 would include a proportion of VAT that the retailer would have to pay (at the moment about £16.67).
One frustration I've had for a while with uCommerce is that although you can opt to show prices with VAT (below), this just toggles the display and the calculations are just the same. This means the website administrator has to enter the prices excluding VAT (in this instance £83.33).

That's not only a laborious task, prone to error for the editor but as we found out on Staunton Moods the other day, can also cause rounding issues when ordering in multiples. On digging into it, the product prices are stored to 4 decimal places whereas the tax is only stored to 2 decimal places so we ended up with the following scenario:
?179.99 inc VAT @ 21% = ?147.9338842975207 exc VAT (or ?147.9339 when rounded to 4dp)
Total Without Tax ?147.9339 * 2 = ?295.8678
Tax Total ?31.07 * 2 = ?62.14
Grand Total ?295.8678 + ?62.14 = ?358.0078 (or ?358.01 when rounded)
Thankfully it's surprisingly easy to resolve in uCommerce. After a little a little playing around with IPricingService and ITaxService on simplygigabyte.co.uk (a blank demo store install) I managed to resolve the issue. The trick is not to override the ITaxService as this results in double calculations. Instead just roll out your own IPricingService like so:
namespace SimplyGigabyteCommon.Catalog
{
using System;
using UCommerce;
using UCommerce.Catalog;
using UCommerce.EntitiesV2;
public class TaxIncludedInPricePricingService : IPricingService
{
public Money GetProductPrice(Product product, PriceGroup priceGroup)
{
// Get the default pricing provider to get the product's price
var pricingService = new PricingService();
var incTax = pricingService.GetProductPrice(product, priceGroup);
// Calculate the tax part of the price
var tax = CalculateTax(priceGroup, incTax);
// To avoid rounding issues, subtract the tax value from the item's price
var excTax = incTax.Value - tax;
// Return the excluding tax part (the tax can be calculated as normal
// with the standard TaxService
return new Money(excTax, incTax.Culture, incTax.Currency);
}
private decimal CalculateTax(PriceGroup priceGroup, Money amount)
{
if (priceGroup == null)
throw new ArgumentNullException("priceGroup");
if (amount == null)
throw new ArgumentException("amount");
var incTax = amount.Value;
var taxRate = priceGroup.VATRate;
var priceTotal = 1 + taxRate;
var tax = (incTax / priceTotal) * taxRate;
return tax;
}
}
}
You'll then need to update your uCommerce configuration file to use your new IPricingService. In post v3 versions of uCommerce, the file is stored in /umbraco/ucommerce/configuration/Core.config.
Change:
<component id="PriceService" service="UCommerce.Catalog.IPricingService, UCommerce" type="UCommerce.Catalog.PricingService, UCommerce" />
To:
<component id="PriceService" service="UCommerce.Catalog.IPricingService, UCommerce" type="SimplyGigabyteCommon.Catalog.TaxIncludedInPricePricingService, SimplyGigabyteCommon" />
And that should be it -just make sure all your prices are updated to include tax in the admin area!
If you're running a pre v3 install then the logic is largely the same but instead of Money we've got PriceGroupPrice:
namespace StauntonMoods.Catalog
{
using System;
using UCommerce.Catalog;
using UCommerce.EntitiesV2;
public class TaxIncludedInPricePricingService : IPricingService
{
public PriceGroupPrice GetProductPrice(Product product, ProductCatalog catalog)
{
return this.GetProductPrice(product, catalog.PriceGroup);
}
public PriceGroupPrice GetProductPrice(Product product, PriceGroup priceGroup)
{
// Get the default pricing provider to get the product's price
var pricingService = new PricingService();
var incTax = pricingService.GetProductPrice(product, priceGroup);
// Calculate the tax part of the price
var tax = CalculateTax(priceGroup, incTax);
// To avoid rounding issues, subtract the tax value from the item's price
// you may also want to round the values here
var excTax = incTax.Price.Value - tax;
// Return the excluding tax part (the tax can be calculated as normal
// with the standard TaxService
return new PriceGroupPrice
{
Price = excTax,
PriceGroup = priceGroup,
Product = product
};
}
private decimal CalculateTax(PriceGroup priceGroup, PriceGroupPrice amount)
{
if (priceGroup == null)
throw new ArgumentNullException("priceGroup");
if (amount == null)
throw new ArgumentException("amount");
var incTax = amount.Price.Value;
var taxRate = priceGroup.VATRate;
var priceTotal = 1 + taxRate;
var tax = (incTax / priceTotal) * taxRate;
return tax;
}
}
}
Also, in pre v3 versions of uCommerce, the file is stored in /umbraco/ucommerce/configuration/Components.config.
Umbraco powered CheckBoxList or DropdownList nodes in uCommerce admin area
Saturday, May 12, 2012 2:53:24 PM (GMT Daylight Time, UTC+01:00)
uCommerce is a great e-commerce engine for Umbraco and high on our list of options when evaluating new e-commerce projects at The Site Doctor.
One thing that has always bugged me however is the lack of extensibility on the admin backend. This is something I've discussed with Søren in the past and I believe is on the cards to be resolved in the upcoming v3 release but we wanted to see if there was a way we could get it working in the current release. Thanks to Dan's digging we've found it is (ish).
Skip to the downloads.
A Little Background
Before I dive into the code and overview how you should get it all wired up, I think it's worth understanding how uCommerce is structured out of the box:
- Each uCommerce section has it's own folder under the "umbraco/uCommerce" folder so if you want to extend the functionality for the "Product Catalog" area then you will need to look in the "Catalog" folder.
- The ASPX files are the main containers (e.g. EditProduct.aspx) and don't generally include any logic as they load up the various UserControls (the ASCX files) as tabs.
- Adding custom tabs can be done through the uCommerce_AdminTab table but that's outside the scope of this post but Lasse has a good introduction on his blog.
As we plan to alter the product details edit functionality, we'll need to make a few changes to EditProductBaseProperties.ascx. It's worth taking a quick look around the file if you've not before as we've re-ordered the boxes, added classes and hidden the SKU field before, all of which is often useful. You'll notice at the end of the file there's a repeater "ProductPropertyRepeater" -this is where uCommerce outputs the various custom properties you've setup.
Why's it useful?
Have you ever felt limited by the options of Text/Boolean/Enum/Image/Number and RichText and wished you could implement your own cool control like an image cropper or checkbox list? What about driving that control with data from Umbraco or another data source? If you have, this is the blog post for you as it handles all those scenarios.
Getting Started
If you don't want to know how to do this yourself you can click here to skip to the downloads and get started straight away.
Caution: If you want a CheckBoxList or your own custom control, you need to be running v2.6 as it adds IWebControlAdapter. As of v2.6 I'm not convinced IWebControlAdapter is fully implemented as it allows you to retrieve the value from the control but we will need to override the UserControl which outputs the control which feels hacky.
Create a custom control
Unless you want a fairly standard control (TextBox, CheckBox, DropDownList or ContentPickerUCommerce) you'll need to create a control which uses the IWebControlAdapter interface. This will mean uCommerce is able to get the value from it. You will need to implement two methods:
- Adapts(Control) - allows you to specify whether it is a control you will be managing.
- GetValue(Object) - allows you to specify what value the control contains In it's entirety it could be pretty simple:
namespace TheSiteDoctor.Web.uCommerce.Admin.UI
{
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Linq;
using UCommerce.Presentation.Web.Controls;
public class UCommerceUmbracoDrivenCheckboxList : CheckBoxList, IWebControlAdapter
{
public bool Adapts(Control control)
{
return control is UCommerceUmbracoDrivenCheckboxList;
}
public object GetValue(object control)
{
var li = (UCommerceUmbracoDrivenCheckboxList)control;
var values = (from ListItem listitem in li.Items where listitem.Selected select listitem.Value).ToList();
return string.Join(",", values);
}
}
}
Create your own DataTypeControlFactory
uCommerce uses DataTypeControlFactory to determine what control to render in the editor so you'll need to override the default implementation because it doesn't look for IWebControlAdapters at the moment.
This is simple enough, it needs to accept a Page (the page you'll be adding the control to) and the work out what control it should output e.g.:
public class CustomDataTypeControlFactory : DataTypeControlFactory
{
Page _page;
public CustomDataTypeControlFactory(Page page)
: base(page)
{
_page = page;
}
public Control CreateCustomControl(IProductProperty productProperty)
{
if (productProperty == null)
throw new ArgumentNullException("productProperty");
return CreateCustomControl(productProperty.ProductDefinitionField, productProperty.Value);
}
private Control CreateCustomControl(ProductDefinitionField productDefinitionField, string value)
{
var fieldType = productDefinitionField.DataType.TypeName;
if (fieldType.StartsWith("Something"))
return base.CreateControl(productDefinitionField, value);
// Some logic to work out whether you should be creating the control
return customControl;
}
}
Add a custom ProductPropertyEditor.ascx
Now you'll need to create your own ProductPropertyEditor.ascx which calls your new DataTypeControlFactory:
public partial class CustomUcommerceProductPropertyEditor : ProductPropertyEditor
{
protected new void Page_Load(object sender, EventArgs e)
{
InitializeControl();
}
public override void DataBind()
{
base.DataBind();
}
private void InitializeControl()
{
Controls.Clear();
Control child = CreateControlToRender();
Controls.Add(child);
}
private Control CreateControlToRender()
{
if (ProductProperty == null)
{
throw new InvalidOperationException("Cannot create control, ProductProperty not set.");
}
var factory = new CustomDataTypeControlFactory(Page);
return factory.CreateCustomControl(ProductProperty);
}
}
You may notice that we're clearing the controls in the InitializeControl which is called from Page_Load whereas uCommerce makes the same call from DataBind(); this is intentional. I've not yet figured out why but if you don't do it from Page_Load, uCommerce still outputs a TextBox.
Wire it all up
To get this all outputting, you now need to replace the call to ProductPropertyEditor in EditProductBaseProperties.ascx with a reference to your own ASCX file, you can do this by updating this line:
<%@ Register Src="~/Umbraco/UCommerce/Controls/ProductPropertyEditor.ascx" TagPrefix="commerce" TagName="ProductPropertyEditor" %>
To:
<%@ Register Src="~/Umbraco/UCommerce/Controls/[YourFileNameHere].ascx" TagPrefix="commerce" TagName="ProductPropertyEditor" %>
Edit your Presenters.config
At this point if you were to load up your uCommerce backend you'd find that it outputs as intended but when you saved the file you'd either get an error or the value doesn't save. The reason for this is although you've replaced the display aspect of the editor, uCommerce is still looking at it's old "GetValue" implementation instead of your new control.
Open /umbraco/ucommerce/configuration/presenters.config and add a reference to your control which implements the IWebControlAdapters interface:
<component id="UCommerceUmbracoDrivenCheckboxList"
service="UCommerce.Presentation.Web.Controls.IWebControlAdapter, UCommerce.Presentation"
type="Your.Custom.uCommerce.Namespace.ControlName, Your.Custom.uCommerce.AssemblyName"
lifestyle="PerWebRequest"/>
That's it, you're done, you can now add your own controls to the backend of uCommerce.

Download
To make life easier I've packaged the files up into an Umbraco package and have also added the source:
Download the source code with references (C#) - 800KB
Download the Umbraco Package - 12KB
Instructions on using the package
If you download the source code you may have noticed we're doing some funky things with the DataTypeName. The idea behind the control is that it allows us to output a DropDownList or CheckBoxList containing values based on a Document Type and start node purely from the name of the DataType.
I can go into more detail about how we're doing this if it's of interest, just leave me a comment below but to start using it straight away you will need to use the following naming convention for your DataType's name:
- TSD_: This is the prefix we use to identify whether it's a control we should be handling
- Control Type: Currently this can either be "ddl" for a DropDownList or "chkl" for a CheckBoxList
- Start Node Id: This should be the id of the Umbraco parent node. You can cheat and use the root node but it's best to use the parent
- Document Type: This is the Document Type's alias to use
Example: if you wanted a list of checkboxes for ShapeType (as above) then your name would be: TSD_chkl_1234_ShapeType and a DropDownList would be: TSD_ddl_1234_ShapeType
What next?
This is something we're using more and more in uCommerce these days as it allows us to use the power of Umbraco to power uCommerce which is allowing us to do some really interesting things. Although we'll only be developing it as we need it at the moment we do have plans to add support for:
- Radio Button Lists
- Image Cropper
- Adding nesting to the list of items (to offer better support for hierarchical data)
- Powering it by the members and media sections
Taking UCommerce emails to the next level and include the order id in the subject, multiple recipients and Google click tracking
Friday, April 08, 2011 2:05:24 PM (GMT Daylight Time, UTC+01:00)
One of the things that I've felt has always been a little lacking in uCommerce was their email system, it's a great idea and nicely implemented but it was rather inflexible in earlier version -you couldn't send "other" emails easily etc.
Most of these bug bears have now been resolved however I still feel that even the latest v1.5 release is a few lacking features that we have tended to build into our email systems by default:
- No ability to use place holders e.g. include the uCommerce order id in the subject line or write "Dear John" as a greeting
- It didn't allow you to send to multiple CC or BCC recipients
- There's no click tracking built in
So what does it do?
In our recent project - www.ChalkboardsUK.co.uk, we extended the existing EmailService and "patched" the missing functionality. There's more we can (and will) do with this in the future but for now this should get you started.
By passing in a combination of QueryString and Placeholder parameters, you can send personalised emails to your customer e.g. have a subject line of "Your order with our store #1234" or start your email with "Dear John".

As well as enabling place holders it allows you to send your email to multiple recipients at the same time by simply separating the CC or BCC addresses with a semi-colon.
Finally (and I think this is pretty darn cool), it automatically tags the links within your email with the Google Analytics tracking code! By using the EmailProfile it will enable you to see whether customers are clicking through on links etc within your emails. Pretty cool eh!

The future
We're open to your thoughts on this and ideas for moving it forward but at the moment, we will be adding functionality:
- Pass in a core objects e.g. a purchase order to give them access to any aspect of the data
- Add the ability to format strings
- Repeating regions (though this should really be done within your XSLT) Let me know what you think by leaving a comment, tweeting @timgaunt or emailing me.
Download It
You can download the file right now by clicking here (TheSiteDoctor.UCommerce.EmailService.zip).
How to use it?
The use of this depends on your individual setup, in this post I'm going to assume you've got a separate assembly which you can include this in however I'll post another post soon which wraps this all up into a pipeline. We also use this for the customer "welcome" emails as well so we can send a pretty email welcoming them to the store.
uCommerce changes
Nothing needs to change in the way that you setup your emails in uCommerce. If you would like to send to multiple CC or BCC recipients, simply separate the addresses with a semi-colon (;) as you would in your standard email client:
s

Umbraco Changes
If you want your content editors to be able to include properties from the order in your email, they'll need to use place holders. At present, the place holders are fairly limited in that there's no repeating regions etc. You can inject anything you want (you'll just need to add the key to dictionary of place holders when constructing the email. The user can then use that value in the email by surrounding the key with square braces e.g. [Order.Total].
An example email:
Hi [Customer.FirstName]
Thank you for your order of £[Order.Total] on [Order.Date]. The details of your purchase are below.
Using it in your code
I would think the most common application for this at the moment will be within your own custom pipelines. If you've already used the UCommerce.Transactions.EmailService then you can retty much just replace the code. If you've not, here's an overview of how you can do it yourself:
// Create an instance of the EmailService
var service = new TheSiteDoctor.uCommerce.Transactions.EmailService();
try
{
// Get the current catalog's email context
var profile = SiteContext.Current.CatalogContext.CurrentCatalogSet.EmailProfiles.Single();
// Setup the QueryString Parameters for the page that's got the various content on -in this instance we're just getting an order confirmation so just pass in the order number
Dictionary<string, string> qs = new Dictionary<string, string>
{
{ "orderNumber", purchaseOrder.OrderGuid.ToString() }
};
// Add the various bits of information you want to be able to pass to the content
Dictionary<string, string> ph = new Dictionary<string, string>
{
{ "Customer.FirstName", customer.FirstName },
{ "Customer.LastName", customer.LastName },
{ "Customer.EmailAddress", customer.EmailAddress },
{ "Order.Number", purchaseOrder.OrderNumber },
{ "Order.Date", purchaseOrder.CompletedDate.Value.ToShortDateString() },
{ "Order.Total", purchaseOrder.OrderTotal.Value.ToString("f2") }
};
// Send the email
service.Send(profile, EmailTypeName, new MailAddress(customer.EmailAddress), qs, ph);
}
catch (Exception ex)
{
// Something "not good" happened so add any other info that might be of help
// Add the customer data
ex.Data.Add("Customer.FirstName", customer.FirstName);
ex.Data.Add("Customer.LastName", customer.LastName);
ex.Data.Add("Customer.EmailAddress", customer.EmailAddress);
if (purchaseOrder != null)
{
ex.Data.Add("OrderId", purchaseOrder.OrderId);
ex.Data.Add("Order.Number", purchaseOrder.OrderNumber);
ex.Data.Add("BasketId", purchaseOrder.BasketId);
}
// Send/log your alert
}
Beware! Context.RewritePath does not end the current execution path
Monday, February 21, 2011 8:07:07 AM (GMT Standard Time, UTC+00:00)
We've recently been working on an inherited project that's got some interesting "features", one of which is how they handle URL rewriting.
There are a number of HTTPHandlers that can be plugged into your application or ISAPI filters to enable URL rewriting in IIS (and now routes in ASP.Net 4 etc) but a fairly classic/old method was to handle it within your Global.asax's Application_BeginRequest method.
A Simple Example
The example below rewrites a path such as "/article/123.aspx" and transforms it to "/articledisplay.aspx?id=132"
<%@ Application Language="C#" %>
<script RunAt="server">
/// <summary>
/// Begins the application request
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">A System.EventArgs that contains the event data</param>
void Application_BeginRequest(Object sender, EventArgs e)
{
// Get the current path (this will be an root relative link e.g. /article/123.aspx)
string path = Request.Path.ToLower();
// Work out if we want to transform it
if (path.Contains("/article/"))
{
string id = path.Replace("/article/", String.Empty).Replace(".aspx", String.Empty);
Context.RewritePath(String.Concat("~/articledisplay.aspx?id=", id), false);
}
}
</script>
Ignoring the pro's and cons of using this method to rewrite the paths, one thing you should be aware of is that Context.RewritePath does not end the code execution.
Why's that important to know?
The problem with this is simple, if you have additional rules later in the code, they will also run -which could end up causing quite a lot of confusion.
Consider the following example Application_BeginRequest method (overlook the semantics of the code):
// Get the current path (this will be an root relative link e.g. /category/123.aspx)
string path = Request.Path.ToLower();
// It's a category request
if (path.Contains("/category/"))
{
// Use the id to perform some form of database lookup i.e. the category
int id = Category.GetIdByUrl(path);
// Redirect the user to the page to display the category
Context.RewritePath(String.Concat("~/rewritepath.aspx?type=category&id=", id), false);
}
// It's a product request
if (path.Contains("/product/"))
{
string id = path.Replace("/product/", String.Empty).Replace(".aspx", String.Empty);
// Redirect the user to the page to display the category
Context.RewritePath(String.Concat("~/rewritepath.aspx?type=product&id=", id), false);
}
Now think about where the visitor would end up if they go to: /category/product/123.aspx is it /rewritepath.aspx?type=category&id=123 or /rewritepath.aspx?type=product&id=123?
Due to the way Context.RewritePath works, it executes /rewritepath.aspx?type=product&id=123 so where's the issue?
The problem is that although the pages behind it don't actually get executed until the end of the Application_BeginRequest method, because the url satisfies both the url rules, the database call will happen -even though in reality, it's not needed. The result is that you could end up with a massive (and unnecessary) overhead to every request hitting the ASP.Net engine (and if you have wildcard mapping enabled bare in mind that's every request -including images/css/javascript).
So if you feel the need to include additional rules (or additional processing e.g. database calls), consider the order of your rules and return from the method as soon as you know your rules are getting fulfilled, the above example would then be written as:
// Get the current path (this will be an root relative link e.g. /category/123.aspx)
string path = Request.Path.ToLower();
// It's a product request
if (path.Contains("/product/"))
{
string id = path.Replace("/product/", String.Empty).Replace(".aspx", String.Empty);
// Redirect the user to the page to display the category
Context.RewritePath(String.Concat("~/rewritepath.aspx?type=product&id=", id), false);
// We know that the user is going to the right place so no more rules need to be executed -return
return;
}
// It's a category request
if (path.Contains("/category/"))
{
// Use the id to perform some form of database lookup i.e. the category
int id = Category.GetIdByUrl(path);
// Redirect the user to the page to display the category
Context.RewritePath(String.Concat("~/rewritepath.aspx?type=category&id=", id), false);
// There are no more rules so no need to return
}
Other things to note
Using my somewhat simplistic example, there are a number of other things you could do to improve the code's maintainability and performance:
- Consider using "StartsWith" instead of contains to make the match more specific (the code we had used "IndexOf() != -1" throughout -but then also had a couple of "IndexOf() == -1" to spice it up). There may be a minor overhead doing this however it makes it a lot easier to understand what you want to achieve.
- Try not to do anything other than "forward" the request on as quickly as possible i.e. no database calls!
- Add known exclusions at the start of the code where you know you don't need to rewrite the path i.e. the folders for CSS, images and JavaScript. You could also approach this by wrapping all rules in an inclusion i.e. if(path.StartsWith("category") || path.StartsWith("product"))
- You should write the rules not only in order of importance/process but while doing so, give consideration to which will be processed most frequently e.g. if you have a rule that's only use by 1 in 1000 calls as the first rule, this will be checked before the relevant rules 999 times in 1000 -so what may be a very small overhead could become a large bottleneck as soon as your site starts to grow in popularity
- Document each rule clearly i.e. explaining what url formats should trigger it
- Where rules are mutually exclusive, use "else if" rather than just "if" to make it clear that you don't expect the others to be run if the first is valid.
Retrieve the customer’s last address when logging into uCommerce
Tuesday, October 26, 2010 11:31:55 AM (GMT Daylight Time, UTC+01:00)
Probably one of the most common features of an ecommerce systems is to "retrieve my details" when logging in -after all that's why you create an account with the seller isn't it?
Out of the box, uCommerce has XSLT to retrieve the customer's last x addresses but one thing it didn't do was automatically re-assign the customer's details when logging in using the built in Umbraco membership code so we need to work around it ourselves -don't worry, it's not too hard (all the code is below for you).
Background
All customer addresses are stored in the uCommerce_Address table automatically, there should be one unique address per customer however if you're on an earlier release you may find you have several copies of the same address for each customer -this is a bug that's been sorted in v1.0.5.0 so upgrade if you can.
Now you'd be forgiven for thinking that you can just select the address from the uCommerce_Address table and then assign the id to the BillingAddressId property of your purchase order however if you do that, you'll find you get the error:
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_uCommerce_PurchaseOrder_uCommerce_OrderAddress".
The conflict occurred in database "CommsReadyCMS", table "dbo.uCommerce_OrderAddress", column 'OrderAddressId'.
The statement has been terminated.
You'll get this because there is also a second table involved -uCommerce_OrderAddress. uCommerce_OrderAddress stores the actual address used throughout the order process incase the customer changes an address in the future, the order will always have the correct address.
The Solution
Working around this isn't actually too difficult as mentioned before. The easiest solution is to create a new User Control in Visual Studio (I'll call mine login.ascx) and hook into the LoggedIn event. Once logged in, get the Umbraco member and from that, get the customer's billing address.
There's one caveat that I found with uCommerce and that's the way it gets the address. At the moment, there is a function on customer "GetAddress", this is great however if you check out the code it calls, it actually gets the customer's first address from the database -rather than the last address used. I don't think this is a bug as in most cases the first address you enter is your main address. I'll blog separately about managing a default address within the members section.
The code below however retrieves the most recently added address from the database
Login.ascx
<asp:literal runat="server" ID="litLoggedIn" />
<asp:literal runat="server" ID="litLoggedOut" />
<asp:Login runat="server" id="lgnForm" CssClass="checkout-details"
DisplayRememberMe="false" TitleText="" OnLoggedIn="lgnForm_LoggedIn"
UserNameLabelText="Email Address" />
Login.ascx.cs
protected void lgnForm_LoggedIn(object sender, EventArgs e)
{
//If the user has a basket, wire up the shipping address with their last order details
var basket = SiteContext.Current.OrderContext.GetBasket(true);
if (basket != null)
{
//Get the customers current order
var po = basket.PurchaseOrder;
//Look for a shipping address
var add = po.GetBillingAddress();
//We only need to assign the address if there isn't already one assigned to this order
if (add == null)
{
//Get the customer who's just logged in
var mem = Membership.GetUser(lgnForm.UserName);
//To be safe check that we have a member
if (mem != null)
{
//Find the customer
var customer = Customer.ForUmbracoMember(Convert.ToInt32(mem.ProviderUserKey));
if (customer != null)
{
//Get the customer's most recent address
var previousAddress = customer.Addresses.ToList().LastOrDefault(a => a.AddressName == "Billing");
//If you want to get the customer's first address just uncomment this line
//var previousAddress = customer.GetAddress("Billing");
//Populate the billing address with the address)
if (previousAddress != null)
{
OrderAddress address = new OrderAddress
{
FirstName = previousAddress.FirstName,
LastName = previousAddress.LastName,
EmailAddress = previousAddress.EmailAddress,
PhoneNumber = previousAddress.PhoneNumber,
MobilePhoneNumber = previousAddress.MobilePhoneNumber,
CompanyName = previousAddress.CompanyName,
Line1 = previousAddress.Line1,
Line2 = previousAddress.Line2,
PostalCode = previousAddress.PostalCode,
City = previousAddress.City,
State = previousAddress.State,
Attention = previousAddress.Attention,
CountryId = previousAddress.CountryId,
AddressName = "Billing",
OrderId = new int?(po.OrderId)
};
//Store the address in the database
address.Save();
//Assign the address to the purchase order
po.BillingAddressId = new int?(address.OrderAddressId);
//Save the purchase order (shopping cart)
po.Save();
}
}
}
}
}
}
Using Linq and Extension Methods to chunk large data sets
Thursday, August 12, 2010 9:32:44 AM (GMT Daylight Time, UTC+01:00)
Ever needed to take a large list and split it into smaller subsets of data for processing? Well this is the Extension Method for you. Tonight we had to split a small dataset (500 items) into even smaller sets of 10 so the provider’s web service wouldn’t timeout.
Seeing as I was going to miss out on my evening, I thought I’d see if I could do it a little differently using Linq and this is what I came up with:
/// <summary>
/// Simple method to chunk a source IEnumerable into smaller (more manageable) lists
/// </summary>
/// <param name="source">The large IEnumerable to split</param>
/// <param name="chunkSize">The maximum number of items each subset should contain</param>
/// <returns>An IEnumerable of the original source IEnumerable in bite size chunks</returns>
public static IEnumerable<IEnumerable<TSource>> ChunkData<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
for (int i = 0; i < source.Count(); i += chunkSize)
yield return source.Skip(i).Take(chunkSize);
}
It should extend any IEnumerable and allow you to split it into smaller chunks which you can then process to your heart’s content.
Here’s a quick example of it in use:
var list = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10" };
Console.WriteLine("Original list is {0} items", list.Count);
var chunked = list.ChunkData(3);
Console.WriteLine("Returned the data in {0} subsets", chunked.Count());
int i = 1;
foreach (var subset in chunked)
{
Console.WriteLine("{0} items are in subset #{1}", subset.Count(), i++);
int si = 1;
foreach (var s in subset)
Console.WriteLine("\t\tItem #{0}: {1}", si++, s);
}
And this will output
Original list is 10 items
Returned the data in 4 subsets
3 items are in subset #1
Item #1: Item 1
Item #2: Item 2
Item #3: Item 3
3 items are in subset #2
Item #1: Item 4
Item #2: Item 5
Item #3: Item 6
3 items are in subset #3
Item #1: Item 7
Item #2: Item 8
Item #3: Item 9
1 items are in subset #4
Item #1: Item 10
2 lines of code to do all that work -Neat
Error 11: An error occurred when merging assemblies: ILMerge.Merge: ERROR!!: Duplicate type 'xyz' found in assembly 'App_Web_rh2sxhkc'. aspnet_merge
Sunday, April 18, 2010 12:47:20 PM (GMT Daylight Time, UTC+01:00)
You may have come across this error once or twice while deploying your project if you develop using Web Deployment Projects. It's usually caused when you copy and paste a page and forget to update both the page declaration and code behind file.
But the website builds!?!
You don't usually get the ILMerge error until you build the web deployment project because when you build a website directly, it doesn't compile all the code into a single assembly so the class names are seen as different. Part of the web deployment process is to compile all the websites code into a single assembly hence the duplicate references.
What's the solution?
It's surprisingly simple, all you need to do is open up the offending aspx and aspx.cs files and update two lines:
1. In the code behind file, rename the partial class. By default Visual Studio will name the class FolderName_Pagename which should result in a unique name
2. The page declaration (first line of the page) in the aspx file. You have to make sure that both the Inherits attribute and CodeBehind reference are correct.
Tip: To avoid confusing yourself, open the files independently using the solution browser because if you open the aspx and press F7 to switch to the code behind file before updating the page declaration, you'll end up editing the page you copied rather than the copy.
C# FileInfo.MoveTo Cannot create a file when that file already exists exception
Tuesday, May 12, 2009 8:39:35 PM (GMT Daylight Time, UTC+01:00)
This was one of those irritating errors that you get when you're trying to do something quickly before you go home and you can't for the life of you fathom the issue.
I had the following code (simple enough):

FileInfo f =
new FileInfo(
"## File's Path ");
try
{...} 
{

f.MoveTo(
"## DROP OFF DIRECTORY ##"));

}
catch (
Exception e)
{...} 
{
//Log the exception here 
}
The fix was simple, you just have to remember to specify the new filename too. (DOH!). Here's the "correct" code.

FileInfo f =
new FileInfo(
"## File's Path ");
try
{...} 
{

f.MoveTo(Path.Combine(
"## DROP OFF DIRECTORY ##", f.Name));

}
catch (
Exception e)
{...} 
{
//Log the exception here 
}
Hope that helps you out ;)
Quick ASP.Net tip: Half your page size in ASP.Net instantly
Friday, April 17, 2009 3:53:05 PM (GMT Daylight Time, UTC+01:00)
Ok it might be a little less than half side but it's near enough. I've been sitting on this for a while and needed to reference it for someone so I thought I'd post quickly about it. One of the most common complaints about .Net is that you have a lot of hidden "content" by the way of hidden inputs and the likes throughout your site. This can easily get corrupt on postback/slowdown the page load times etc.
Really you should be optimising each control on the page (enabling/disabling where relevant) but if you want to cheat (lets face it, we all do):
- Download the files: PageStateAdapterv1.0.zip (3KB)
- Put PageStateAdapter.browser into your /App_Browsers/ folder (or create one and add it)
- Put TSDPageStateAdapter.dll into your website's /bin/ folder
- Load up your website and checkout your ViewState :)
Incase you're interested in the source for it:
PageStateAdapter.browser
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page" adapterType="TheSiteDoctor.PageStateAdapter.PageStateAdapter" />
</controlAdapters>
<capabilities>
<capability name="requiresControlStateInSession" value="true" />
</capabilities>
</browser>
</browsers> PageStateAdapter.cs
using System.Web.UI;
namespace TheSiteDoctor.PageStateAdapter
{...} 
{
public class PageStateAdapter : System.Web.UI.Adapters.PageAdapter
{...} 
{
public override PageStatePersister GetStatePersister()
{...} 
{
return new SessionPageStatePersister(
this.Page);

}

}

}
The best example of how much this reduces ViewState by is when you add a large DataGrid to your site.
Post files: PageStateAdapterv1.0.zip (3KB)
Update: Apologies to those of you who downloaded and found it wouldn't compile, the .browser file was a little off (missing the second "PageStateAdapter"). I've updated it and changed the zip file download. Enjoy!
Recent comments macro for DasBlog
Tuesday, February 17, 2009 9:25:05 AM (GMT Standard Time, UTC+00:00)
One of the issues I had with John Forsythe's Recent Comments macro for DasBlog was that the dasBlog recent comments weren't ordered by date (descending). I found that as people commented on older posts they were getting buried which irritated me as many were very still valid comments.
The fix was actually fairly simple, it was just a matter of adding a sort and thanks to Lamba expressions, this is something we can do fairly simply. If you want to add recent comments to your dasBlog installation, use the following macro:
Recent Comments Macro
public virtual Control RecentComments(
int count,
int adminComments,
int trimTitle,
int trimContent,
int trimAuthor,
bool showTitle,
bool showCommentText,
bool showCommentCount)
{...} 
{
int commentsToShow;
int totalComments;

CommentCollection allComments =
this.requestPage.DataService.GetAllComments();

totalComments = allComments.Count;
//Sort the comments in date order (descending) 
allComments.Sort((c1, c2) => c1.CreatedUtc.CompareTo(c2.CreatedUtc));
if (!
this.requestPage.HideAdminTools && SiteSecurity.IsInRole(
"admin"))

commentsToShow = totalComments - adminComments;
else 
commentsToShow = totalComments - count;
if (commentsToShow <
0)

commentsToShow =
0;

StringBuilder sb =
new StringBuilder();

sb.AppendLine(
"<div class=\"recentComments\">");
if (showCommentCount)

sb.AppendFormat(
"<div class=\"totalComments\">Total Comments: {0}</div>", totalComments);

sb.AppendLine(
"<ul class=\"comments\">");
Loop through the comments
#region Loop through the comments
for (
int i = totalComments -
1; i >= commentsToShow; i--)
{...} 
{

Comment current = allComments[i];
bool showComment;
if (!current.IsPublic || (current.SpamState == SpamState.Spam))
{...} 
{
if (!
this.requestPage.HideAdminTools && SiteSecurity.IsInRole(
"admin"))
{...}
else
{...} 
{

showComment =
false;
if (commentsToShow >
0)

commentsToShow--;

}

}
else
{...}
if (showComment)
{...} 
{
if ((current.SpamState == SpamState.Spam))

sb.Append(
"<li class=\"spam\">");
else if (!current.IsPublic)

sb.Append(
"<li class=\"hidden\">");
else 
sb.Append(
"<li>");
string link =
String.Format(
"{0}{1}{2}", SiteUtilities.GetCommentViewUrl(current.TargetEntryId),
"#", current.EntryId);
string title = current.TargetTitle;
string desc = current.Content;
string author = current.Author;
if (showTitle)
{...} 
{

sb.AppendFormat(
"<div class=\"recent{0}CommentsTitle\"><a href=\"{1}\">",

current.SpamState,

link

);
if ((title.Length > trimTitle) && (trimTitle >
0))

sb.AppendFormat(
"RE: {0}...", title.Substring(
0, trimTitle));
else 
sb.AppendFormat(
"RE: {0}", title);

sb.Append(
"</a></div>");

}
if (showCommentText)
{...} 
{

sb.AppendFormat(
"<div class=\"recentCommentsContent\"><a href=\"{0}\">",

link

);
if ((desc.Length > trimContent) && (trimContent >
0))
{...} 
{

sb.Append(desc.Substring(
0, trimContent));

sb.Append(
"...");

}
else
{...}

sb.Append(
"</a></div>");

}

sb.Append(
"<div class=\"recentCommentsAuthor\">");
if ((author.Length > trimAuthor) && (trimAuthor >
0))
{...} 
{
int num3 = (trimAuthor > author.Length) ? author.Length : trimAuthor;

sb.Append(
"by " + author.Substring(
0, num3));

sb.Append(
"...");

}
else
{...} 
{

sb.Append(
"by " + author);

}

sb.Append(
"</div></li>");

}

}
#endregion

sb.AppendLine(
"</ul>");

sb.AppendLine(
"</div>");
return new LiteralControl(sb.ToString());

}
I've since been working on extending it further so you can add a "All Comments" link which I'll post up later as it needs a little more work :)
If you want this wrapped up as a DLL let me know and I'll upload it.
Update 26th Feb 2009: You can download the dll here (it's also got a few other things in there if you want to look around).
Update 27th Feb 2009: I noticed that the above code was messing up everynow and again so I've updated it to use Linq instead which seems to work well. I've updated the DLL but not the source yet.
Custom Visual Studio Build Events
Monday, December 15, 2008 6:21:37 PM (GMT Standard Time, UTC+00:00)
I thought I'd share some festive "fun" today. For quite some time now I've hooked into the build events within Visual Studio but I think this is just taking it too far...

Basically the script now increments the version number of the DLL (if relevant) automatically, times the time for the build and adds it to a total build time to date (stored in an external txt file) and finally prints out whether the build was a success or not -basically some of our projects are taking a while to build and I wanted something that was "clearer" ;)
I'm thinking about adding Christmas trees next, anyone know how to do them in ASCII?
The remote host closed the connection. The error code is 0x80072746
Wednesday, November 05, 2008 2:03:40 PM (GMT Standard Time, UTC+00:00)
We've just moved a couple of our sites onto a new server and have intermittently started to receive the error "The remote host closed the connection. The error code is 0x80072746.", usually around the same time as "Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.".
As the infrastructure hadn't changed we were able to rule out network issues, hardware issues (it was an upgrade) and nothing had changed on the code level so we put it down to the new backup routine.
On a little Googling, I found a few sites that were pointing to (among other things) the Request length and Executiuon time which got me thinking, in this particular site there is a lot of data being sent/retrieved and it could be maxing out the request. After a little more digging I found this article from Microsoft about the httpRuntime Element and I quote
This time-out applies only if the debug attribute in the compilation element is False. To help to prevent shutting down the application while you are debugging, do not set this time-out to a large value.
Although I didn't recall making any changes to the site, when updating the database configuration settings, I did change the compilation element to false. So far increasing the executionTimeout value appears to have fixed the issue.
WSDL returning a valid response but object is null
Saturday, November 01, 2008 6:28:49 PM (GMT Standard Time, UTC+00:00)
We had an interesting problem the other day while integrating with a web service hosted on an apache server. Every time we called the WSDL methods we could see a valid request being sent and a valid response being returned (ok there were a few syntax errors but nothing that would stop it converting) but despite this every time, without fail, it would return null at code level.
The solution was actually amazingly simple. In short, since our last flurry with web services we've upgraded to Visual Studio 2008 and installed ReSharper (see below for how that's relevant). In VS2008 they've sneakily added a new menu "Add Service Reference" and although it's in the same menu location and a similar wording etc does something different. If you add your WSDL reference this way it will more than likely fail.
To add a "Web Reference" in Visual Studio 2008 follow the instructions below and hopefully you won't go down the same routes that I did trying to debug the XML...
Click the "Add Service Reference" menu option (obviously!)

The screen below should appear, instead of entering the address of the web service into the address bar, click the advanced button at the bottom (highlighted)

That'll then bring up the screen below, on which you need to click the "Add Web Reference" button at the bottom.

You should then be presented with the usual "Add Web Reference" screen (see below).

Why did I mention ReSharper? Well I've notice that as well as adding to the IDE they've also updated some of the menu items, most noticeably "Go to Definition" which is now "go to Declaration" (see below) so I figured this new menu option was thanks to them.
ReSharper: 
Without ReSharper: 
So the next time your WSDL is returning a null object even though a valid response is being retrieved, check how you added it in Visual Studio,.
Clean out unused media items from Umbraco media folder
Wednesday, September 03, 2008 5:15:14 PM (GMT Daylight Time, UTC+01:00)
When uploading some new media items for a client today we noticed that if you selected "Remove" before saving, it doesn't actually remove the file from the FileSystem. Having a quick look around the forums I saw there are a few posts already pointing this out so I thought I'd fix it.
This is a little application that simply checks the media items in the database and then compares it against a folder you select on your machine. If the file is in use according to the database then it's ignored otherwise it will remove it.
To use:
- Enter your server's login details
- Click "Test Connection"
- Select the relevant database from the drop down
- Check the "Media Folder Name" matches your Umbraco's installation
- Locate your Media Folder on your computer
- Click "Check Media Folder" -this will then list all the orphan files
- If it looks right, click "Delete" -with caution
- Job done
There are a few checks in place to avoid mishap but it's not 100% foolproof as I needed something rough and ready to sort a couple of installations out. If this is something that's seen as useful I'll extend it a touch, some ideas I've got already:
- Check that the selected media folder matches that of the database
- Check that the media id's are the same (to avoid wiping another installation)
- Save config settings for easy re-use
- Use webservices rather than a direct connection to the database
- Enable FTP useage
Please note: I accept no responsibility if anything was to go horribly wrong with this. I would backup your folder first just in case!
You can download the MediaFolderCleaner application here
Identify IIS Sites and Log File locations for WWW and FTP –the source
Friday, July 25, 2008 3:52:37 PM (GMT Daylight Time, UTC+01:00)
Exactly a year ago today I posted a little application that output the sites in IIS to a text file and as a few days ago Lars asked for the source, I thought it would be a nice thing to release it exactly a year later.
I didn't plan it that way, it just happened! Cool :)
Identify IIS Sites and Log File locations for WWW and FTP source
using System;
using System
.DirectoryServices;
using System
.IO;
using System
.Collections;
namespace IISSites
{...} 
{
class Program
{...} 
{
static string fileToWrite
= String.Empty;

[STAThread]
static void Main(
string[] args)
{...} 
{

fileToWrite
= String.Format(
"IISExport{0:dd-MM-yyyy}.txt", DateTime
.Today);
if (args
!= null && args
.Length
> 0)
{...} 
{

fileToWrite
= args[
0];

}

SortedList www
= new SortedList();

SortedList ftp
= new SortedList();
try
{...} 
{
const string FtpServerSchema
= "IIsFtpServer";
// Case Sensitive
const string WebServerSchema
= "IIsWebServer";
// Case Sensitive
string ServerName
= "LocalHost";

DirectoryEntry W3SVC
= new DirectoryEntry(
"IIS://" + ServerName
+ "/w3svc",
"Domain/UserCode",
"Password");
foreach (DirectoryEntry Site
in W3SVC
.Children)
{...} 
{
if (Site
.SchemaClassName
== WebServerSchema)
{...} 
{
string LogFilePath
= System
.IO
.Path
.Combine(

Site
.Properties[
"LogFileDirectory"]
.Value
.ToString(),
"W3SVC" + Site
.Name);

www
.Add(Site
.Properties[
"ServerComment"]
.Value
.ToString(), LogFilePath);

}

}

DirectoryEntry MSFTPSVC
= new DirectoryEntry(
"IIS://" + ServerName
+ "/msftpsvc");
foreach (DirectoryEntry Site
in MSFTPSVC
.Children)
{...} 
{
if (Site
.SchemaClassName
== FtpServerSchema)
{...} 
{
string LogFilePath
= System
.IO
.Path
.Combine(

Site
.Properties[
"LogFileDirectory"]
.Value
.ToString(),
"MSFTPSVC" + Site
.Name);

ftp
.Add(Site
.Properties[
"ServerComment"]
.Value
.ToString(), LogFilePath);

}

}
int MaxWidth
= 0;
foreach (
string Site in www
.Keys)
{...} 
{
if (Site
.Length
> MaxWidth)

MaxWidth
= Site
.Length;

}
foreach (
string Site in ftp
.Keys)
{...} 
{
if (Site
.Length
> MaxWidth)

MaxWidth
= Site
.Length;

}

OutputIt(
"Site Description".PadRight(MaxWidth)
+ " Log File Directory");

OutputIt(
"".PadRight(
79,
'='));

OutputIt(
String.Empty); 
OutputIt(
"WWW Sites");

OutputIt(
"=========");
foreach (
string Site in www
.Keys)
{...} 
{
string output
= Site
.PadRight(MaxWidth)
+ " " + www[Site];

Console
.WriteLine(output);

OutputIt(output);

}
if (ftp
.Keys
.Count
> 0)
{...} 
{

OutputIt(
String.Empty); 
OutputIt(
"FTP Sites");

OutputIt(
"=========");
foreach (
string Site in ftp
.Keys)
{...} 
{
string output
= Site
.PadRight(MaxWidth)
+ " " + ftp[Site];

OutputIt(output);

}

}

}
// Catch any errors
catch (Exception e)
{...} 
{

Console
.WriteLine(
"Error: " + e
.ToString());

}
finally
{...} 
{

Console
.WriteLine();

Console
.WriteLine(
"Press enter to close/exit...");
//Console.Read(); 
}

}
static void OutputIt(
string lineToAdd)
{...} 
{

Console
.WriteLine(lineToAdd);
if (
!String.IsNullOrEmpty(fileToWrite))
{...} 
{

StreamWriter SW;

SW
= File
.AppendText(fileToWrite);

SW
.WriteLine(lineToAdd);

SW
.Close();

}
else
{...} 
{

Console
.WriteLine(
"locationToOutput is Null or String.Empty please supply a value and try again.");

}

}

}

}
How to: Convert Hexadecimal Strings
Tuesday, July 15, 2008 10:25:56 AM (GMT Daylight Time, UTC+01:00)
As it's my Birthday today I thought I'd post a silly ditty. I'm currently altering Protx's old ASP.Net library to accommodate their changes in regards 3D Secure and while reflecting some of the code came across an enum with their number representations as Hexadecimal strings. I needed to convert these to decimals so thought I'd share a quick and easy way to do it.
Open up Window's Calculator (Windows Key + R then type in calc) under the View menu select "Scientific". Press the F5 key to switch over to Hex entry. Type in the value after the 0x and hit F6
Simple, easy and will help you convert all those Hexadecimal strings (ones that look like this: 0x01 or 0x1a).
Right, time for a coffee :)
Setting the page title with ASP.Net
Thursday, July 10, 2008 11:57:57 AM (GMT Daylight Time, UTC+01:00)
We've been setting up the page's title using ASP.Net for quite some time now, we tend to word it: Product Name | Category | Site Name as this IMO is the most comprehensive naming convention (though the pipe (|) gets converted to a space for the bookmarks).
When editing one of our sites today though I noticed that the title was resorting to Avant Garde hair salons -which was setup as the brand's name. Looking into it I found that if you set the <title> tag within the page or master page, ASP.Net doesn't override it from the codebehind so watch out!
For those of you who don't know how to set the title of your page from codebehind it's simple:
this.Page.Title =
"Put your title here";
A seriously elegant SQL Injection -how it was sorted
Thursday, May 29, 2008 3:32:33 PM (GMT Daylight Time, UTC+01:00)
Doug Setzer posted this comment in response to my recent "A seriously elegant SQL Injection" post and I thought it may be of interest to others so have promoted it to a post...
Well, I'll step up and say that I am the "mate" who had this done. Tim's right - *always* sanitize your inputs. In my defence, this was a site that I inherited from a previous contractor. I'm not entirely absent of blame, I still should have done a security sweep through the code.
I'd like to document the steps that I went through once this was identified to try and avoid this kind of thing in the future.
- Edit every web page that executes a query to sanitize any parameters that are passed in. Since the site was classic ASP, I used my "SQLStringFieldValue" function:
www.27seconds.com/kb/article_view.aspx?id=50 - Modify the DB user account that is used to have *read only* access to the database
- Modify the pages that DO write to the database to have *read/write* access to the specific tables that are being changed. This limits the number of places that SQL Injection can occur to a smaller set than was previously possible. I still sanitize all of my input, but I'm extra spastic in these database calls.
- Add database auditing (triggers writing to mirror tables with audit event indicator & date/time) to see when data changes occur. This is still problematic with the pages that have "write" permissions to the tables, but again- that footprint is much smaller.
My future plans are to move to a view/stored procedure based architecture. I can then limit write permissions to just the stored procedures and read permissions to just the views. My grand gusto plans are to move to using command objects & parameters, but I'd sooner re-write the entire site.
Although Doug's attack wasn't the same nihaorr1.com attack that's going around atm it was similar so I would imagine other's will find this useful.
It still amazes me how many developers still fail to sanitise strings, only last week I came across another site (in PHP) that was allowing simple SQL injections to be used to log into their administration system. It was down to a problem with the sanitization string, but why not at least check your site before it goes live? It takes 2 minutes and even less to fix...
For those of you who need a few pointers, there's a good discussion or two about sanitising strings on the 4 Guys From Rolla site.
What have I been up to?
Friday, September 21, 2007 11:20:01 PM (GMT Daylight Time, UTC+01:00)
It's been rather quiet on my blog recently, if you're wondering why (and don't chat to me on/off-line) I thought I would share with you what we've been working on recently.
For the past month or so The Site Doctor has been developing a new web site (www.wineandhampergifts.co.uk) for Porter and Woodman Gifts Ltd - a local company that produces personalised corporate hampers and gifts. It's been quite a challenge as they have a rather unusual ordering system that allows multiple recipients/addresses multiple items. Looking at it now, it's not so complicated but the delivery charge calculations and initial specs took a while to fully grasp. It's been really enjoyable.
I'll probably cover aspects of the site over the forthcoming months but there are a few really nice features to the Wine and Hamper Gifts site (or at least I think so), some of which the end user will never know about such as the use of generics to calculate the address/recipient/gift variations) and those that they may -for instance the use of the JavaScript1 Zoom function on the product details page (courtesy of LuckyZoom), also the design created by our excellent designer Gareth Brown all adds up to what has to be one of the best sites I've developed to date.
1 Yes, I did just say I've integrated some JavaScript into the site ;)
I doubt most of my readers are interested on the in's and out's of the project itself but from an SEO perspective, I for one am expecting pretty decent results. We opted to use the URL Rewriting ISAPI from Helicon this time round over our usual IISMods URL Rewriting ISAPI as for some reason the IISMods site has been offline for a while (and checking now has been converted into a very weird site).
Another aspect that some people may be unaware of is that the majority of the Wine and Hamper Gifts site operates the same without JavaScript as it does with JavaScript, this is important not only for screen readers but also search engines. There is only one area of the Wine and Hamper Gifts site that I'm aware of that doesn't operate without JavaScript and that is the "Personalise this gift" link on the cart page that allows the user to either edit the existing message or add one that doesn't already exist, that's because it uses a LinkButton, but I may find a way around that later.
Other features that I really like are little things like the way the drop down lists on the left hand menu are created -they're not actually drop down lists but unordered lists that are then manipulated using JavaScript, I think the JavaScript could do with a little tweaking but the result is superb. The Wine and Hamper Gifts site also creates a PDF receipt for the user which is emailed to them, this is something I've been meaning to look into for some time but haven't had the chance, luckily while I was developing the site, Sean Ronan posted to the MsWebDev list about an ASP.Net PDF library iTextSharp (a port from a Java library) which, despite a few oddities from the POV of the Java port does exactly what I wanted. The library is pretty easy to use once you get your head around it and certainly produces some nice results.
There's still more work that's needed to finalise the content and various aspects of the Wine and Hamper Gifts website but if you have a chance, check out the new Porter and Woodman Gifts Ltd Wine and Hamper Gifts website and leave a comment here letting me know what you think :D
Oh, and they've given us a pretty high target to get before Christmas so if you're thinking about treating your customers to a personalised corporate hamper or gift give a little thought to using www.wineandhampergifts.co.uk
The Controls collection cannot be modified because the control contains code blocks
Friday, August 24, 2007 10:49:56 AM (GMT Daylight Time, UTC+01:00)
Server Error in '/' Application.
The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.HttpException: The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).
Source Error:
| Line 132: metaKey.Name = "keywords"; Line 133: metaKey.Content = p.MetaKeywords; Line 134: this.Page.Header.Controls.Add(metaKey); Line 135: } Line 136: if (!String.IsNullOrEmpty(p.MetaDescription)) |
Source File: a:\xyz\ContentHandler.aspx.cs Line: 134
Stack Trace:
| [HttpException (0x80004005): The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).] System.Web.UI.ControlCollection.Add(Control child) +2105903 ContentHandler.Page_Load(Object sender, EventArgs e) in a:\xyz\ContentHandler.aspx.cs:134 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +15 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +34 System.Web.UI.Control.OnLoad(EventArgs e) +99 System.Web.UI.Control.LoadRecursive() +47 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1061 |
Version Information: Microsoft .NET Framework Version:2.0.50727.832; ASP.NET Version:2.0.50727.832
Another day, another issue ;)
This had me going around in circles for a while until I realised what it was, if you're getting this error you can bet your bottom dollar that you have <%= %> somewhere in your page's header -furthermore I'd hazard a guess that you've got it in some JavaScript to reference an ASP.Net control on the page- and then you're trying to add a control to the header programmatically (or a custom control from someone like Telerik is trying to). Am I right1?
1 I'm not allowed to ask you to so I won't, but if I was right, then spend that bottom dollar clicking on one of the Google Ads :P
I can't tell you exactly why this occurs but my understanding of it is that ASP.Net can't re-create the header if it has Response.Write somewhere in the header (<%=) -most likely due to when the header is created it's not available (will look into it). No doubt you want to know the fix?
The Fix
The fix is simple, remove the inline code blocks and JavaScript and move it to your code behind i.e.:
Update: Thanks to Julian Voelcker for sending me this alternative "fix" for the problem, can't say I like it though ;) basically instead of using <%= ... %> you would write the databinding expression of: <%# ... %>
String.Format- Exception of type System.Web.HttpUnhandledException was thrown.
Wednesday, August 22, 2007 9:43:12 PM (GMT Daylight Time, UTC+01:00)
Type
System.FormatException
Message
Exception of type 'System.Web.HttpUnhandledException' was thrown.
StackTrace
at System.Text.StringBuilder.FormatError()
at System.Text.StringBuilder.AppendFormat(IFormatProvider provider, String format, Object[] args)
at System.String.Format(IFormatProvider provider, String format, Object[] args)
Error Line
0
Just got that message (or at most "Exception of type 'System.Web.HttpUnhandledException' was thrown")? Puzzled? I was the first time I got it, I've been meaning to post about it for quite some time now so seeing as I got it again today I took the hint.
The error is horrifingly obvious when you know about it, in short, you've no doubt got some code that looks like this:
String.Format(
"<html><head><style type=\"text/css\">body{color: #fff;}</style><body>...");
Can you spot it now? Notice your style declaration is using the curly brackets? Basically String.Format is interpreting that as a placeholder i.e. {0} and is throwing a wobbly.
The solution is simple too, just replace all opening/closing brackets with two i.e:
String.Format(
"<html><head><style type=\"text/css\">body{{color: #fff;}}</style><body>...");
I hope that helps someone out there :)
P.S. Watch out for methods that use String.Format as they may catch you out in the same way -i.e. Subject of System.Net.Mail.MailMessage
Accessing properties by a string name
Friday, March 09, 2007 5:12:02 PM (GMT Standard Time, UTC+00:00)
This morning Julian Voelcker came to me with an interesting issue that I’ve looked into before but I’ve never really looked into a re-useable solution. Seeing as it’s fun Friday I thought why not ;)
The scenario: I would like to offer my users a custom mail merge facility where by they can insert values stored in the database such as their name. The selection of columns is unlikely to be changed and if it does then I’ll be the one to do it. There are about 20 fields to choose from.
Easy enough, in the past I’ve kept it to a minimum and then just done a simple find and replace on the body i.e.:
//Create a dataset and add some test columns
DataTable dt =
new DataTable();

dt.Columns.Add(
"Name");

dt.Columns.Add(
"Email");
Add some test data
#region Add some test data


DataRow dr = dt.NewRow();

dr[
"Name"] =
"Julian";

dr[
"Email"] =
"julian@email.com";

dt.Rows.Add(dr);


dr = dt.NewRow();

dr[
"Name"] =
"Tim";

dr[
"Email"] =
"tim@email.com";

dt.Rows.Add(dr);

#endregion
Create the example email body
Do the work
#region Do the work

//Loop through the rows
for (
int i =
0; i < dt.Rows.Count; i++)
{...}

{
//Get the data row for this instance
DataRow row = dt.Rows[i];

//Create a new body as this'll be updated for each user
string body =
String.Empty;

//Update the body
body = emailBody.Replace(
"##Name##", row[
"Name"]);

body = body.Replace(
"##Email##", row[
"Email"]);


litOutput.Text +=
String.Format(
"{0}<hr />", body);

}

#endregion
The issue I see with this however is (among others) having 20 fields is a lot to be doing with a find/replace statement as it wouldn’t be very elegant and a nightmare to manage. Sticking with this method of using a dataset I suggested we use a regular expression to match the field delimiters and do a replace that way:
//Create a dataset and add some test columns
DataTable dt =
new DataTable();

dt.Columns.Add(
"Name");

dt.Columns.Add(
"Email");
Add some test data
#region Add some test data


DataRow dr = dt.NewRow();

dr[
"Name"] =
"Julian";

dr[
"Email"] =
"julian@email.com";

dt.Rows.Add(dr);


dr = dt.NewRow();

dr[
"Name"] =
"Tim";

dr[
"Email"] =
"tim@email.com";

dt.Rows.Add(dr);

#endregion
Create the example email body
Do the work
#region Do the work

//Loop through the rows
for (
int i =
0; i < dt.Rows.Count; i++)
{...}

{
//Get the data row for this instance
DataRow row = dt.Rows[i];


MatchEvaluator replaceField =
delegate(Match m)
{...};

{
return row[m.Groups[
1].ToString()].ToString();

};

//Create a new body as this'll be updated for each user
string body =
String.Empty;
//Find the fields
Regex r =
new Regex(
@"{{(\w{0,15}?)}}");

body = r.Replace(emailBody, replaceField);


litOutput.Text +=
String.Format(
"{0}<hr />", body);

}

#endregion
This is alright and in many ways very scaleable. I’m not a fan of DataSets but in this instance it works nicely and does mean expanding the available fields at a later date would just be a matter of adding columns to the query.
How does this relate to accessing a property of an object using a string value instead? Well there was a catch, Julian wasn’t using a DataSet and didn’t want to, he had a collection of custom objects all ready and waiting. As he uses a code generator to generate his Data Access Layer and Business Logic Layer there was a method already exposed allowing you to search for a property by string but it's not a standard .Net method so I decided to work out how it was done.
The solution it turned out was a really rather elegant solution IMHO. Using reflection you can use the same concept as above but with custom objects and Robert is your father’s wife’s sister:
Reflection.aspx.cs
using System;
using System.Data;
using System.
Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Reflection;

public class TestObject
{...}

{
private int __Id;
private string __Name;
private string __Email;
private DateTime __JoinedDate;

public int Id
{...}
public string Name
{...}
public string Email
{...}
public DateTime JoinedDate
{...}

public TestObject(
int id,
string name,
string email, DateTime joinedDate)
{...}

{

__Id = id;

__Name = name;

__Email = email;

__JoinedDate = joinedDate;

}

public bool GetPropertyValueByName(
string propertyName)
{...}

{
object obj =
null;
return this.GetPropertyValueByName(propertyName,
false,
ref obj);

}

public bool GetPropertyValueByName(
string propertyName,
ref object val)
{...}

{
return this.GetPropertyValueByName(propertyName,
false,
ref val);

}

public bool GetPropertyValueByName(
string propertyName,
bool caseInsensitive,
ref object val)
{...}

{
PropertyInfo p =
null;
BindingFlags flags =
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;

//If it's a case-insensitive search then add the flag
if (caseInsensitive)

flags = flags |
BindingFlags.IgnoreCase;


p =
this.GetType().GetProperty(

propertyName,

flags,
null,
null,

Type.EmptyTypes,
null);

//Check the property exists and that it has read access
if (p !=
null && p.CanRead)
{...}

{
//There is a property that matches the name, we can read it so get it
val =
this.GetType().InvokeMember(

propertyName,
BindingFlags.GetProperty | flags,
null,
this,
null);

//We return true as the user may just want to check that it exists
return true;

}

return false;

}

}

public partial
class Reflection : System.Web.UI.Page
{...}

{
protected void Page_Load(
object sender,
EventArgs e)
{...}

{
if (!Page.IsPostBack)
{...}

{
Create the example email body

}

}

protected void btnSubmit_Click(
object sender,
EventArgs e)
{...}

{
if (Page.IsValid && !
String.IsNullOrEmpty(txtEmailBody.Text))
{...}

{

litOutput.Text =
"<h2>Output</h2>";
Perform some basic tests
#region Perform some basic tests

litOutput.Text +=
"<h3>Perform some basic tests:</h3>";
TestObject testObject =
new TestObject(
1,
"Tim",
"tim@email.com", DateTime.Today);

object obj =
null;
if (testObject.GetPropertyValueByName(
"id",
false,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

if (testObject.GetPropertyValueByName(
"name",
true,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

if (testObject.GetPropertyValueByName(
"joineddate",
true,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

if (testObject.GetPropertyValueByName(
"nothere",
true,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

#endregion
Create a collection and add a couple of items
#region Create a collection and add a couple of items


List<TestObject> testObjects =
new List<TestObject>();

testObjects.Add(
new TestObject(
1,
"Tim",
"tim@email.com", DateTime.Parse(
"01/02/2007")));

testObjects.Add(
new TestObject(
2,
"Jim",
"jim@email.com", DateTime.Parse(
"20/02/2007")));

testObjects.Add(
new TestObject(
3,
"John",
"john@email.com", DateTime.Parse(
"02/03/2007")));

testObjects.Add(
new TestObject(
4,
"Gill",
"gill@email.com", DateTime.Parse(
"01/04/2007")));

testObjects.Add(
new TestObject(
5,
"Bill",
"bill@email.com", DateTime.Parse(
"11/02/2007")));

#endregion
Do the work
#region Do the work

//Format it with <pre> for simplicity
litOutput.Text +=
"<h3>Now for the reflection example:</h3><hr /><pre>";

//Loop through the rows
foreach (
TestObject t
in testObjects)
{...}

{

MatchEvaluator replaceField =
delegate(Match m)
{...};

//Create a new body as this'll be updated for each user
string body =
String.Empty;

//Find the fields within the main body -this can be any of the properties of the object
Regex r =
new Regex(
@"({{)(\w{0,15}?)(}})");

body = r.Replace(txtEmailBody.Text, replaceField);
//Output the example content (HtmlEncoded so not to hurt us!!)
litOutput.Text +=
String.Format(
"{0}<hr />", Server.HtmlEncode(body));

}


litOutput.Text +=
"</pre>";

#endregion
}

}

}
I’ve thrown up a quick demo if you want to test it out. I think in the longer run I’m going to look into having it generate some form of reporting system as that’d be seriously nice, but the suns out and I need to go for a paddle so that’ll have to wait for another day! So that's my first delve into reflection and so far I love it!