February 13, 2012

Brewver: Brewing Version Control (sorta)

by Greg Leppert — Categories: Brewer, Software Development, Technology — Tags: , , , , , Leave a comment

For those of you who may or may not read my Twitter feed, I just announced the existence of my new project, Brewver.

I call it Brewver: Brewing Version Control. And while it’s not so much version control as a recipe database, it’s how I thought up the cool-ass name. So, shut up. But big plans are big. I’d really like for this to become the community for home brewers (such as myself).

It’s not version control as, there’s no history tracked. So yeah. Misnomer. I get it. Go pedant somewhere else!

This all began when I started home brewing a few months ago, I found the lack of an online community, other than HomeBrewTalk.com both annoying and suspicious. I couldn’t find a good, online application for home brewers to track their boils, fermentations and whatnot. And I learned extremely quickly that as much as I thought I’d remember the boil, I wouldn’t.

So, with almost no research into what was already out there, I started writing my own. And since I knew by the time I’d actually finish it, months would pass, I went with the future. The software stack consists of:

  • Visual Studio 11 Developer Preview (with some help from VS2010)
  • MVC 4 Developer Preview
  • Entity Framework 4.2 (June 2011 CTP)
  • SQL Server “Denali”
  • IIS 7.5

As much as I love this project, it’s by no means prime-time yet. I’m looking for a small army of home brewers and testers to jump in and see what they can break, and give me some good feature requests, feedback and bug reports. Thus, I warn anyone who does join me in my crusade for better, stronger, faster brewing software, to be wary, as everything is super unstable at this point.

Also, it’s by no means hosted reliably, and thus may simply reject your request at any given moment (I blame Apache modproxy, but…it’s not really it’s fault either). Until this gets off the ground, I’m reluctant to get it into real, actual hosting. I mean, hosting is expensive!

September 16, 2011

Preview Cubed

My home PC was in need of a format at the same time the Windows 8 Developer Preview was released. Wanting it all, wanting it now, I installed it. Then installed the Visual Studio 11 Developer Preview. And finally, because I love me some MVC, the ASP.NET MVC 4 Developer Preview.

Life on the edge.

The first thing I noticed was that I couldn’t run or debug up a newly created MVC 4 project:

Could not load file or assembly 'Microsoft.VisualStudio.Web.Runtime, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

Which I’m not surprised about, because I can’t find it either. Anywhere.

So, I simply removed it in the web.config:

<assemblies>
    <remove assembly="Microsoft.VisualStudio.Web.Runtime, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    ...
</assemblies>

I expect it’s floating around a machine.config or other inherited config file somewhere, but this’ll work till the bugs previews are sorted out.

EDIT: 1/16/2012

Thanks to MSDN Social who dug up the actual cause. Apparently it’s that the Page Inspector is only available in the .NET Framework 4.5. If you aren’t using that, the reference needs removal. At least until MVC 4 is officially available with Visual Studio 11.

September 9, 2011

The Rhythm Method

by Greg Leppert — Categories: UselessLeave a comment

Every so often I get inspired.

To be an idiot.

A group of us were discussing code contests and candidate evaluation techniques, and I came up with the idea to “Code the Song” — take a song, and write it as code.

Cee Lo Green’s “Fuck You” popped into my head that night, and I couldn’t help but write the first verse. In .NET 4, of course.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace LadyKiller
{
    static class Track3
    {
        private static readonly Person Me = new Person { ChangeInPocket = decimal.MinValue };
        private static readonly Person Girl = new Person { InLoveWith = true };

        static void Main(string[] args)
        {
            using (var you = DrivingRoundTown(Girl))
            {
                you.Fuck();

                if (!Me.ChangeInPocket.IsEnough())
                {
                    you.Fuck();
                    Girl.Fuck();
                }

                var that = string.Empty;
                try
                {
                    if (Me.IsRicher)
                    {
                        Me.With = Girl;
                    }
                }
                catch
                {
                    that = "Some shit";
                }

                Console.WriteLine(that);

                Me.AddPain("Chest");
            }
        }

        private static Person DrivingRoundTown(Person p)
        {
            if (p.InLoveWith)
            {
                return new Person();
            }

            throw new ArgumentException("Invalid person", "p");
        }
    }

    public class Person : IDisposable
    {
        public decimal ChangeInPocket { get; set; }
        public bool InLoveWith { get; set; }
        public Person With { get; set; }

        private readonly IList _painLocations = new List();

        public bool IsRicher
        {
            get { throw new NotImplementedException(); }
        }

        public void AddPain(string location)
        {
            _painLocations.Add(location);
        }

        public void Fuck()
        {
            Process.Start(new ProcessStartInfo(ToString(), "fuck"));
        }

        private void Wish(dynamic o)
        {
            if (!InLoveWith) return;

            Console.WriteLine(o.TheBest);
            Fuck();
        }

        public void Dispose()
        {
            Wish(new { TheBest = true });
        }

        public override string ToString()
        {
            return "cmd.exe";
        }
    }

    public static class ExtensionMethods
    {
        public static bool IsEnough(this decimal d)
        {
            return (d > decimal.Zero);
        }
    }
}

August 28, 2011

DRY Azure Table Storage

Ok, so it’s not really DRY, but you’ll see what I mean.

For some background, I’ve been trying to find a way to support saving data into Azure storage in a way that doesn’t influence the actual business objects used by the application. That is, I wanted to abstract away the storage medium from the objects in the simplest manner I could find. No one should have to think about what their PartitionKeys and RowKeys are if they’re going to backend an application with SQL rather than Azure.

At first I thought, I’ll just wrap the object in a generic class that supports the Azure storage pieces when it needs it. That way, I could pass the object to the Azure data context I was creating, and the context would know how to wrap it up and save it.

Sadly, Azure Table Storage doesn’t support generics. In fact, it doesn’t natively support most CLR types, only a tiny subset of objects.

There is a rather elaborate workaround that involves tying into the underlying XML generation that would allow you to deconstruct your object into the types that are supported by Azure, but it certainly isn’t easy or straightforward, and led into a whole lot picking apart objects one by one that I was trying to get away from in the first place.

Eventually I settled on an object translation model that simply copies the data from the business object into an Azure-compatible storage object, with matching fields. If I end up needing complex types in the business objects, I’ll let the Azure storage object contain the methods for breaking it up into bits Azure Table Storage is happy with. It’s not an ideal, but it seems to be working rather well.

As I work out the snags, I’ll post some examples of how this all fits together.

But that isn’t what this post is really about.

The real issue I ran into was that when I attempted to save data to one of the tables, I would receive a DataServiceRequestException, telling me that “an error occured while processing this request.” If you’ve worked with Azure Storage, you’re probably used to mining your way through the Exception Helper just to get a snippet of XML that hopefully tells you something informative. In this case, no, not so much:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <code>InternalError</code>
    <message xml:lang="en-US">Server encountered an internal error. Please try again after some time.</message>
</error>

Stack trace:

    at
System.Data.Services.Client.DataServiceContext.SaveResult.<HandleBatchResponse>d__1e.MoveNext()

So, great. I tried again after some time. Same result. I restarted the Storage Emulator. Same result.

Thankfully someone has run into errors this detailed before and knew how to enable diagnostic logging for the local storage emulator:

After switching it on and recreating the error, the logs told me the whole story:

8/28/2011 2:28:53 PM [Error] Caught exception during performing table operation and rethrow: System.Exception: c:\Users\WeaklyTyped\AppData\Local\Temp\ro9rbwlu.0.cs(245,23) : error CS0542: 'Content': member names cannot be the same as their enclosing type

   at Microsoft.Cis.Services.Nephos.Table.Service.Protocols.Rest.TableManager.EndPerformOperation(IAsyncResult ar)
   at Microsoft.Cis.Services.Nephos.Table.Service.Protocols.Rest.TableProtocolHead.<TableAndRowOperationImpl>d__3e.MoveNext()

Much like ScottR on the StackOverflow post from above, I had a table named the same as a field on the object I was storing. Azure Table Storage clearly does not like this. I wonder if that was ever mentioned anywhere in the documentation or training kits…

Anyway, moral of the story is don’t name any of your object properties the same as the Azure Table in which the object will be stored. It won’t work, and you won’t be told why.

August 18, 2011

June 6, 2011

The Odd Couple: Azure 1.4 and x86

Just don’t do it. Or, at least I never found a way.

Initially I was (unknowingly) using a 32-bit TFS build server to build and automatically publish an Azure project using DeployToAzure, a handy team build workflow that does the heavy lifting via the Azure Management API. Kudos to Viacheslav Koltovich for cobbling the bits together.

There were a few quirks.

For example, the Azure certificate you’re using needs to be in the Root store of whichever location, either CurrentUser (has to be installed as user the build server is running under) or LocalMachine. For some reason, the standard “My” store wasn’t working so well when it would try to locate the certificate.

Also, the Subscription Id must be lowercase. Azure seems to be case sensitive, and upon retrying, the WCF channel would not recreate properly, and fail out the build.

So everything seemed fine. But when the deployment occured, I’d get the following error: “Could not load file or assembly ‘msshrtmi’ or one of its dependencies. An attempt was made to load a program with an incorrect format.”

And I couldn’t figure out why. Sure, Azure was 64-bit, but what x64 Windows system couldn’t run x64 binaries? It’s even documented how to do so, available since 2009. What was I doing wrong? Colleagues informed me that this was not a problem in previous versions of Azure…

It turns out that when building a Windows Azure project on a 32-bit (x86) host, either by way of build server or development machine, the only binaries output are 32-bit for the associated roles.

Don’t believe me?

Check out the bin/[build configuration]/CloudProject.csx/roles/[role]/base folder. I bet you’ll only find a x86 folder full of x86 binaries that won’t load in the 64-bit environment of Azure. Both are included when building on an x64 system.

I even tried to force compilation in x64, but the deployment would never finish; only abort the deployment and continually retry in the developer portal.

When working with Azure, do everything 64-bit. It’ll save your weekend.

December 20, 2010

Super Delegate!

by Greg Leppert — Categories: SharePoint, Software Development, TechnologyLeave a comment

As will soon be obvious, I’m a technical SharePoint virgin. It’s been in my life before, but I’ve never really developed with it.

I’ll let you decide what that means exactly.

Throughout the week I’ve been working with SharePoint 2010 to create a content managed, mobile-optimized website for a product line. I’ve actually been impressed with the scads of improvements over 2007, particularly in the IDE. No more WSPBuilder! A Right-click “deploy” and “retract” commands that actually work! And best of all, GUI-based feature, package, and web part management.

Even the site collections finally look nice-ish.

What is disconcerting, though, is the heavy integration of the Microsoft AJAX Toolkit (formerly ATLAS, now System.Web.Extensions). It is true that it’s an incredibly full-featured Javascript framework a-la jQuery or Prototype (in fact, more so in some of it’s abilities to mimic the ASP.NET lifecycles and event driven nature). For one, it’s gigantic. An out-of the box page weighs in at ~400 kilobytes when all is said and done.

If your page layouts contain the new Ribbon interface or do anything with authoring, this extra payload is simply required. But what if you didn’t need it? For example, on said mobile site.

Considering the scope of the site was small, the team decided it would be best to try to just hide the authoring pieces unless the user was logged in via Windows Auth. A different approach, but it seemed simpler (at the time) then extending the site and doing all kinds of extra page layouts. Acutally, it would be cool to hear how you other SharePoint masters would approach the issue.

Clearly, a 400 kilobyte page payload won’t cut it on mobile devices. And the vast majority of that (~350 kilobytes or so) was SharePoint’s authoring and Ajax Toolkit scripts. So we set out to strip it out.

To achieve this, we wrapped all the unnecessary (read: authoring) junk in the SPSecurityTrimmedControl, a slick shortcut which flips the “Visible” property to False on any controls contained within it’s tags. The ribbon, the ScriptLink, et cetera.

And such began the Javascript errors.

The first peculiar error consisted of the following:

_spBodyOnLoadFunctionNames is not defined : _spBodyOnLoadFunctionNames.push('loadMDN1');

A lot of Googling later, I was able to determine that this is related to the TreeViewAndDataSource functionality of the Ribbon. Which matched up nicely to the DelegateControl with ControlId=”TreeViewAndDataSource”.

<SharePoint:DelegateControl ID="treeViewAndDataSourceDelegateControl" runat="server" ControlId="TreeViewAndDataSource"></SharePoint:DelegateControl>

But there’s no words there. What does it mean TreeViewAndDataSource? This thing sucks!

Well, if you pull the reference into code and debug it to check out it’s Controls collection, you’ll be able to see it’s actually a built-in SharePoint user control, the markup for which lives in the 14 hive:

control.Controls[0]
{ASP._controltemplates_metadatanavtree_ascx}
    [ASP._controltemplates_metadatanavtree_ascx]: {ASP._controltemplates_metadatanavtree_ascx}
    AppRelativeTemplateSourceDirectory: "~/_controltemplates/"
    BindingContainer: {ASP.PDHOMEPAGE_ASPX_73005077}
    ClientID: "ctl00_ctl16"
    Controls: {System.Web.UI.ControlCollection}
    EnableTheming: true
    EnableViewState: true
    ID: "ctl16"
    NamingContainer: {ASP.PURDUE_MASTER_1189259114}
    Page: {ASP.PDHOMEPAGE_ASPX_73005077}
    Parent: {Microsoft.SharePoint.WebControls.DelegateControl}
    Site: null
    SkinID: ""
    TemplateControl: {ASP._controltemplates_metadatanavtree_ascx}
    TemplateSourceDirectory: "/_controltemplates"
    UniqueID: "ctl00$ctl16"
    Visible: false

Opening up the 14 hive, looking in the TEMPLATE\CONTROLTEMPLATES\MetadataNavTree.ascx markup shows us it inherits from the MetaDataNavTree class in Microsoft.Office.DocumentManagement assembly in the GAC:

<%@ Control Language="C#" Inherits="Microsoft.Office.Server.WebControls.MetaDataNavTree,Microsoft.Office.DocumentManagement,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"    compilationMode="Always" %>

Now we can call up our old friend .NET Reflector to figure out what exactly is happening here.

protected override void OnLoad(EventArgs e)
{
    //...SNIP...
    this.ResizeGrippyForKeyFilters();
    this.CallOnTreeViewLoad();
}

private void ResizeGrippyForKeyFilters()
{
    //...SNIP...
        string script = "<style>BODY #s4-leftpanel {width:230px;} BODY #MSO_ContentTable{margin-left:230px;} .res-nav-l{width:230px;}</style>";
        this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "overrideTreeWidthCSSForKeyFilters", script, false);
    //...SNIP...
}

protected void CallOnTreeViewLoad()
{
    //...SNIP...
        MetadataNavigationContext.Current.OnTreeViewLoad(this.WebTreeView);
    //...SNIP...
}

public void OnTreeViewLoad(SPTreeView spTreeView)
{
    //...SNIP...
                spTreeView.Page.ClientScript.RegisterStartupScript(typeof(MetadataNavigationContext), "prepareMDNScript", "function loadMDN2() { EnsureScript('MDN.js', typeof(loadFilterFn), null); }\r\nfunction loadMDN1() { ExecuteOrDelayUntilScriptLoaded(loadMDN2, 'sp.ribbon.js'); }\r\n_spBodyOnLoadFunctionNames.push('loadMDN1');", true);
    //...SNIP...
}

That’s a lot of script getting registered. What’s weirder is that even though the control is set to Visible to false, it still renders out the data. I ended up having to go into the page where the control lived, and manually remove it from the control collection:

protected void Page_Init(object sender, EventArgs e)
{
    if (HttpContext.Current.User.Identity.IsAuthenticated) return;

    var control = this.FindControl("metaDataNavTreeDelegateControl");
    this.Controls.Remove(control);
}

All well and good, right?

False.

Message: Object expected
Line: 117
Char: 77
Code: 0
URI: http://ebiz-comm-2010:1983/Pages/default.aspx

That line corresponds to:

document.onreadystatechange=fnRemoveAllStatus; function fnRemoveAllStatus(){removeAllStatus(true)};

And what’s worse, this error occurs ONLY in Internet Explorer. Firebug/Firefox, and even Chrome will ignore it (can someone try that out in Safari for me please?).

{ASP._controltemplates_publishingconsole_ascx}
    [ASP._controltemplates_publishingconsole_ascx]: {ASP._controltemplates_publishingconsole_ascx}
    AppRelativeTemplateSourceDirectory: "~/_controltemplates/"
    BindingContainer: {ASP.PDHOMEPAGE_ASPX_73005077}
    ClientID: "ctl00_SPNavigation_ctl00"
    Controls: {System.Web.UI.ControlCollection}
    EnableTheming: true
    EnableViewState: true
    ID: "ctl00"
    NamingContainer: {System.Web.UI.WebControls.ContentPlaceHolder}
    Page: {ASP.PDHOMEPAGE_ASPX_73005077}
    Parent: {Microsoft.SharePoint.WebControls.DelegateControl}
    Site: null
    SkinID: ""
    TemplateControl: {ASP._controltemplates_publishingconsole_ascx}
    TemplateSourceDirectory: "/_controltemplates"
    UniqueID: "ctl00$SPNavigation$ctl00"
    Visible: false

The PublishingConsole.ascx seems be an amalgamation of other Publishing Web Controls, but seems to interact with the Ribbon which, of course, was earlier removed.

<%@ Control Language="C#"   %>
<%@Assembly Name="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@Register TagPrefix="PublishingWebControls" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.Publishing.WebControls"%>
<%@Register TagPrefix="PublishingInt" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.Publishing.Internal.WebControls"%>
<%@Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="~/_controltemplates/Welcome.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="MUISelector" src="~/_controltemplates/MUISelector.ascx" %>

<SharePoint:UIVersionedContent id="publishingConsoleV4" UIVersion="4" runat="server"><ContentTemplate>
<PublishingInt:PublishingRibbon id="publishingRibbon" runat="server" />
</ContentTemplate></SharePoint:UIVersionedContent>
<SharePoint:UIVersionedContent id="publishingConsoleV3" UIVersion="3" runat="server"><ContentTemplate>
<!-- Console -->

<%--..SNIP... -- Check out the implementation in your 14 hive \TEMPLATE\CONTROLTEMPLATES\PublishingConsole.ascx file --%>

<!-- Console -->
</ContentTemplate></SharePoint:UIVersionedContent>

Somewhere there is something that I expect is trying to set the ribbon active or inactive with that Javascript snippet. With the ribbon being removed, clearly that won’t be happening.

Again, the solution seemed to be dropping the control out of the Controls collection:

protected void Page_Init(object sender, EventArgs e)
{
    if (HttpContext.Current.User.Identity.IsAuthenticated) return;

    var control = this.FindControl("publishingConsoleDelegateControl");
    this.Controls.Remove(control);
}

Thus it seems, the DelegateControl seems to be a hopped up ContentPlaceHolder for injecting functionality into the page or SharePoint Ribbon. Which I guess is kinda cool, but it seems to have weird side effects, like “ignoring” control visibility.

Or, I may just be using them wrong…

No doubt there’s more items like this to watch for, so hopefully this process will help others track down strange issues when attempting to hide the authoring bits.

November 17, 2010

Go With the Overflow

It’s a funny thing, COM+ interop.

Maybe funny is the wrong word. Either way, there’s obviously significant drawbacks to marshaling data back and forth between .NET and COM. In this case, decimal precision. And (I assume) by extension, data type overflow in general.

As you may already know, Commerce Server does all of it’s basket calculations in its proprietary pipeline system which exists in a COM+ context. Thus, any data manipulations done custom need to fit into a new custom pipeline component.

There is the distinct possibility that your pipeline component could create data that doesn’t fit properly in the allotted space for marshaling. Or, you may assign values from the front end that cause a repeating decimal to occur (although, I’ve only seen this with custom components, so I think the built-in components already take care of the issue).

In either case, you’ll see an error similar to the following:

An exception of type 'System.OverflowException' occurred and was caught.
------------------------------------------------------------------------
10/01/2010 10:31:06
Type : System.OverflowException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Failed to write the decimal property Property of class OrderForm to the commerce dictionary. The decimal value cannot be represented as a VARIANT of type VT_CY either because it is too large or because it has too many digits of precision.  The value being marshaled was: 20.2973892738947827832.
Source : Microsoft.CommerceServer.Runtime
Help link :
Data : System.Collections.ListDictionaryInternal
TargetSite : Void PopulateDictionary(Microsoft.CommerceServer.Runtime.IDictionary, System.Collections.Hashtable, Microsoft.CommerceServer.Internal.Orders.Storage.ClassInfo, Microsoft.CommerceServer.Internal.Orders.Storage.ClassInfo, System.Object, Microsoft.CommerceServer.Runtime.Orders.OrderForm, Microsoft.CommerceServer.Internal.Orders.Storage.MappingResults)
Stack Trace :    at Microsoft.CommerceServer.Runtime.Orders.PipelineAdapter.PopulateDictionary(IDictionary dict, Hashtable propertyMap, ClassInfo classInfo, ClassInfo orderGroupClassInfo, Object sourceObject, OrderForm orderForm, MappingResults results)
   at Microsoft.CommerceServer.Runtime.Orders.PipelineAdapter.WriteDictionary(OrderForm orderForm, IDictionary dict)
   at Microsoft.CommerceServer.Runtime.Orders.PipelineAdapter.WriteDictionary(OrderForm orderForm)
   at Microsoft.CommerceServer.Runtime.Orders.OrderGroup.RunPipeline(PipelineInfo pipelineInfo, PipelineBase pipeline, ITransaction trans)
   at Microsoft.Commerce.Providers.CSHelpers.OrderHelper.RunOrderPipeline(String siteName, String pipelineName, OrderGroup commerceBasket, PipelineInfo pipelineInfo)
   at Microsoft.Commerce.Providers.Components.OrderPipelinesProcessor.RunPipeline(CommerceOperation operation, String siteName, String pipelineName, OrderPipelineType pipelineType, OrderGroup commerceBasket, PipelineInfo pipelineInfo)
   at Microsoft.Commerce.Providers.Components.OrderPipelinesProcessor.ExecutePipelines(CommerceOperation operation, String siteName, OrderGroup commerceServerBasket, PipelineInfo pipelineInfo)
   at Microsoft.Commerce.Providers.Components.OrderPipelinesProcessor.ExecuteQuery(CommerceQueryOperation queryOperation, OperationCacheDictionary operationCache, CommerceQueryOperationResponse response)
   at Microsoft.Commerce.Providers.Components.OperationSequenceComponent.Execute(CommerceOperation operation, OperationCacheDictionary operationCache, CommerceOperationResponse response)
   at Microsoft.Commerce.Broker.OperationSequence.ExecuteComponentTree(List`1 executionTreeList, CommerceOperation operation, OperationCacheDictionary operationCache, CommerceOperationResponse response)
   at Microsoft.Commerce.Broker.OperationSequence.Execute(CommerceOperation operation)
   at Microsoft.Commerce.Broker.MessageHandler.ProcessMessage(String messageHandlerName, CommerceOperation operation)
   at Microsoft.Commerce.Broker.OperationService.InternalProcessRequest(CommerceRequest request)
   at Microsoft.Commerce.Broker.OperationService.ProcessRequest(CommerceRequest request)

Additional Info:

MachineName : DEVELOPER-PC
TimeStamp : 10/1/2010 2:31:06 PM
FullName : Microsoft.Commerce.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
AppDomainName : /LM/W3SVC/778411967/ROOT-1-129304167221807495
ThreadIdentity :
WindowsIdentity : DOMAIN\AppPoolUser

Here is where Math.Round() is your friend.

Rounding these values prior to inserting them back into the dictionary will help to avoid this situation. Please note however, that the actual basket calculations may do rounding differently, so you’ll want to take that into account, perhaps leaving extra decimal places and allowing Commerce Server to make those decisions when necessary.

November 10, 2010

Lost in Translation

For better or worse, I’ve never been able to predict during project design phases what fields will end up needing to be marked multilingual in the catalog. I get surprised every time.

The irritation of this short-sightedness is that setting metadata properties to become multilingual requires large-scale schema changes for catalog data, and is not as simple as the check box in Catalog Schema Manager implies.

I also ran into an issue in that both programmatically with the Commerce APIs as well as with Catalog Schema Manager one cannot delete a non-multilingual LongText type field and then recreate it multilingual with the same property name. A rather bizarre limitation if I do say so myself.

So, ingenious developer that I am, I found a workaround via the catalog import process. If one does a full backup of the catalog via an export, destroy all catalogs and schema, hack up the XML, and finally import the hacked XML, the multilingual flag can be flipped with little issue, and no data loss.

This inspired me to create a quick utility class that would perform the XML edits for me. And now others!

And so:

using System.Collections.Generic;
using System.Globalization;
using System.Xml;

public class PropertyRetyper
{
    public PropertyRetyper(string fileName, IEnumerable propertyNames)
    {
        XmlCatalogData.Load(string.Format("{0}", fileName));
        PropertyNames.AddRange(propertyNames);
    }

    public PropertyRetyper(XmlDocument export, IEnumerable propertyNames)
    {
        XmlCatalogData = export;
        PropertyNames.AddRange(propertyNames);
    }

    public PropertyRetyper(XmlDocument export, string propertyName) : this(export, new List().AddInPlace(propertyName)) { }

    private XmlDocument _xmlCatalogData;
    public XmlDocument XmlCatalogData
    {
        get { return _xmlCatalogData ?? (_xmlCatalogData = new XmlDocument()); }
        set { _xmlCatalogData = value; }
    }

    private List _propertyNames;
    public List PropertyNames
    {
        get
        {
            return _propertyNames ?? (_propertyNames = new List());
        }
    }

    private CultureInfo _cultureInfo;
    public CultureInfo Culture
    {
        get { return _cultureInfo ?? System.Threading.Thread.CurrentThread.CurrentCulture; }
        set { _cultureInfo = value; }
    }

    public void Retype()
    {
        PropertyNames.ForEach(UpdateProperty);
    }

    void UpdateProperty(string propertyName)
    {
        foreach (XmlElement property in XmlCatalogData.GetElementsByTagName("Property"))
        {
            if (!string.Equals(property.Attributes["name"].Value, propertyName)) continue;

            property.SetAttribute("Multilingual", "1");

            if (string.Equals(property.Attributes["dataType"].Value, "float"))
            {
                property.SetAttribute("dataType", "string");
                property.SetAttribute("MaxValue", "255");
            }

            if (property["PropertyValue"] == null) continue;

            foreach (XmlElement elementValue in property)
            {
                if (elementValue.Name != "PropertyValue") continue;

                elementValue.SetAttribute("language", Culture.Name);
            }
        }

        foreach (XmlElement product in XmlCatalogData.GetElementsByTagName("Product"))
        {
            DoUpdate(product, propertyName);
        }

        foreach (XmlElement product in XmlCatalogData.GetElementsByTagName("VirtualProduct"))
        {
            DoUpdate(product, propertyName);
        }

        foreach (XmlElement product in XmlCatalogData.GetElementsByTagName("Category"))
        {
            DoUpdate(product, propertyName);
        }

        foreach (XmlElement product in XmlCatalogData.GetElementsByTagName("VirtualCategory"))
        {
            DoUpdate(product, propertyName);
        }
    }

    void DoUpdate(XmlElement product, string propertyName)
    {
        if (product.Attributes[propertyName] == null) return;

        var element = XmlCatalogData.CreateElement(propertyName);
        element.SetAttribute("Value", product.Attributes[propertyName].InnerText);
        element.SetAttribute("language", Culture.Name);

        product.AppendChild(element);
    }
}

I also utilize this utility extension method in a lot of places. It does nothing but shortcut adding a single item to a newly instantiated list, so that I can utilize this:

var list = new List().AddInPlace("Hello world!");

And soon:

public static class ListExtensions
{
    public static List AddInPlace(this List list, T item)
    {
        list.Add(item);
        return list;
    }
}

This has been tested for properties which were once string type or float type, but no others. All properties become 255 character string fields. It will go in, edit the catalog schema definition, search through base catalogs and virtual catalogs, and then exchange the data for products and categories in those catalogs. So far, the process takes about 30 seconds on a 30 MB catalog export, which is significantly faster than anything with the Commerce Server API.

Usage:

  1. Export all desired catalog data
    • Include entire schema
  2. Delete all catalog data
  3. Delete all catalog schema
    • Category definitions
    • Product definitions
    • Property definitions
  4. Run the export through this class (see example following)
  5. Import the resulting XML to the catalog system
    • Full schema import

To perform the retype action, I simply wrote a quick console application to read in and save out the catalog XML:

using System;
using System.Xml;

class Program
{
    static XmlDocument _export;

    static void Main(string[] args)
    {
        Console.WriteLine("Enter XML file path and name:");
        var fileName = Console.ReadLine();

        _export = new XmlDocument();
        _export.Load(string.Format("{0}", fileName));

        var retyper = new PropertyRetyper(_export, "Features");
        retyper.Retype();
        _export = retyper.XmlCatalogData;

        _export.Save(string.Format("{0}_Retyped.xml", fileName.Replace(".xml", string.Empty)));
    }
}

June 30, 2010

Virtual Fail

I really, really didn’t want to re-type Commerce Server Catalog properties. Unfortunately, sometimes circumstances call for drastic measures.

Of course, the catalog system does not allow you to change the type of a property on the fly, for good reason. A string isn’t necessarily a decimal, nor an integer a filename, it goes on, and on, and on, and on.

But I digress.

The point is, I whipped up a quick application to change the type by reading in the current catalog data, caching it, deleting the property, re-create the property, add it back to the appropriate definitions, and finally repopulate it with the cached data.

WARNING: This was quickly cobbled code; it’s certainly not the best, nor may it even compile. It is purely for example.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using log4net;
using log4net.Config;
using Microsoft.CommerceServer;
using Microsoft.CommerceServer.Catalog;

[assembly: XmlConfigurator(Watch = true)]
namespace WeaklyTyped.Commerce.Utilities.Catalog.ImportTweaks
{
    class Program
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(Program));

        private const string _webServiceUrl = "http://weaklytyped.com/CatalogWebService/CatalogWebService.asmx";

        private static XmlDocument _xmlCatalogData = new XmlDocument();

        private static CatalogServiceAgent _agent;
        private static CatalogContext _context;
        private static BaseCatalog _baseCatalog;
        private static VirtualCatalog _virtualCatalog;

        static void Main(string[] args)
        {
            _agent = new CatalogServiceAgent(_webServiceUrl);
            _context = CatalogContext.Create(_agent);
            _baseCatalog = _context.GetCatalog("BaseCatalog") as BaseCatalog;
            _virtualCatalog = _context.GetCatalog("VirtualCatalog") as VirtualCatalog;

            Console.WriteLine("ImportTweaks");
            Console.WriteLine(_webServiceUrl);
            Console.WriteLine("Press a key to continue");
            Console.ReadKey();

            _xmlCatalogData = new XmlDocument();

            try
            {
                _xmlCatalogData.Load("C:\\Users\\gleppert\\Desktop\\OldCatalogData.xml");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Could not load catalog XML catalog data: please check the app.config. Exiting...");
                Console.ReadKey();
                return;
            }

            _context.CreateProperty("PropertyName", CatalogDataType.Double, 255);
            var definitionNames = new List<string>
                            {
                                "ProductType1",
                                "ProductType2"
                            };

            foreach (var name in definitionNames)
            {
                var definition = _context.GetDefinition(name);
                definition.AddProperty("PropertyName", DefinitionPropertyType.NormalProperty);
                definition.Save();
            }

            UpdateFromFeature("PropertyName", "PropertyNameOld");

            Console.WriteLine("Finished");
            Console.ReadKey();
        }

        private static void UpdateFromFeature(string commercePropertyName, params string[] featureNames)
        {
            foreach (XmlElement catalogFamily in _xmlCatalogData["c:Catalog"]["Families"])
            {
                if (catalogFamily["Products"] == null) continue;

                foreach (XmlElement catalogProduct in catalogFamily["Products"])
                {
                    if (catalogProduct["Features"] == null) continue;

                    // show me you're still running!
                    Console.Write(".");

                    foreach (XmlElement catalogFeature in catalogProduct["Features"])
                    {
                        foreach (var featureName in featureNames)
                        {
                            // per biz req's: concatinate all old values together
                            var concatinatedCatalogValue = new StringBuilder();

                            if (catalogFeature["Name"].InnerText == featureName)
                            {
                                foreach (var catalogValue in catalogFeature["Values"])
                                {
                                    concatinatedCatalogValue.Append(catalogValue);
                                }
                            }

                            try
                            {
                                var product = _baseCatalog.GetProduct(catalogProduct["ProductNumber"].InnerText);
                                product[commercePropertyName] = concatinatedCatalogValue.ToString();
                                product.Save();
                            }
                            catch (Exception ex)
                            {
                                Logger.Error(
                                    string.Format("Could not update product [{0}]",
                                                catalogProduct["ProductNumber"].InnerText), ex);
                            }
                        }
                    }
                }
            }
        }
    }
}

Bonus points if you can guess the old catalog system.

Sadly, when attempting to update one specific catalog property’s type (from string to decimal), upon attempting to add the property back to the definition it once belonged, I ran into the following error:

An exception occurred in the 'CatalogWebService' Web service.  Exception details follow: 

Microsoft.CommerceServer.Catalog.CatalogDatabaseException: Failed to update the view for the virtual catalog named 'VirtualCatalog' for the language 'LNG_NEUTRAL'. ---> System.Data.SqlClient.SqlException: Invalid column name 'PropertyName'.
Invalid column name 'PropertyName'.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.CommerceServer.Catalog.Internal.SqlHelper.ExecuteNonQuery(SqlTransaction transaction, CommandType commandType, String commandText, SqlParameter[] commandParameters)
   at Microsoft.CommerceServer.Catalog.Internal.CommonHelpers.UpdateVirtualCatalogLanguageView(ExecutionContext executionContext, String virtualCatalogName, String language, Boolean neutralLanguage, List`1 removedColumns, Boolean materialized)
   --- End of inner exception stack trace ---
   at Microsoft.CommerceServer.Catalog.Internal.CommonHelpers.UpdateVirtualCatalogLanguageView(ExecutionContext executionContext, String virtualCatalogName, String language, Boolean neutralLanguage, List`1 removedColumns, Boolean materialized)
   at Microsoft.CommerceServer.Catalog.Internal.CommonHelpers.UpdateAllVirtualCatalogViews(ExecutionContext executionContext, String virtualCatalogName, List`1 removedColumns, CatalogFlags flags)
   at Microsoft.CommerceServer.Catalog.Internal.CommonHelpers.UpdateAllVirtualCatalogViews(ExecutionContext executionContext, IEnumerable virtualCatalogNames, Boolean includeDependentCatalogs)
   at Microsoft.CommerceServer.Catalog.Internal.CatalogDefinitionProperties.SetDefinitionProperties(CatalogExecutionContext executionContext, CatalogDefinitionPropertiesDataSet definitionProperties, String definitionName)
   at Microsoft.CommerceServer.Catalog.Internal.CatalogDefinition.Save(CatalogExecutionContext catalogExecutionContext, CatalogDefinition updatedDefinition)
   at Microsoft.CommerceServer.Catalog.Internal.CatalogServerContextBase.SaveDefinition(CatalogDefinition updatedDefinition)

A ServerFaultException will occur within your code when you try to save the definition back, but this is the root cause. Also, any attempt to rebuild the virtual catalog, or export the virtual catalog will fail, miserably. The rebuild will fail with a:

Line 0: Failed to rebuild the virtual catalog 'VirtualCatalog'.

And the export will fail with a:

Line 0: Invalid column name 'PropertyName'.

Super detailed error message is super detailed.

What I can’t figure out is why it can’t update the view. The same code executed fine on a different property (from string to integer), and it seems the only issue is with the virtual catalog associated. And again, only with said property.

Weird.

Initially I thought it was database permissions, but I tried it with a “superuser” account and didn’t fare any better.

In order to get around it, I had to re-remove the property from each definition and delete the virtual catalog, and then add the property back, again, and then create a new virtual catalog with the previous rules.

Finished, right? Nope. Cause, there are edits in the virtual catalog that can’t be lost…

Thankfully, after clearing out the offending property and rebuilding the virtual, I was allowed to do an export of the virtual catalog, so data could be preserved. Of course, it did require manually adding the property back to the definitions it belonged in the exported XML to trick Catalog Manager into completing the import.

Although, I wouldn’t do a schema import…

© 2012 Weakly Typed All rights reserved - Wallow theme v0.46.5 by ([][]) TwoBeers - Powered by WordPress - Have fun!