Thursday, July 12, 2018

Back in the Sitecore world again!!

I have recently been able to get back into the Sitecore world and I am excited! I have been living in the Tridion world for a few years and it has been painful. With that being said it is not like Sitecore does not have it's pain points as well. Here are some I have run into as I start playing with version 9.

Keep your license file name "license.xml"

Using SIF to install locally only works with a license file named "license.xml". I had a temp license for awhile so in the file name I put the expiration date. I updated the install powershell script for SIF with the new license file name (yes in both places) and run the install. The install was fine until it tried to start the indexer window service. That step failed as the service would not start. When I looked at the logs the service said it was still looking for a file name "license.xml" and it was missing. Sure enough the license file I referenced was not copied over. I have not found the root cause of this yet, I just know changing it back to "license.xml" resolved the issue.

The prefix must be unique for all installs

When you set up your install.ps1 script for SIF you have to set the prefix for the install. I have installed a couple different instances to play with different things. Some of these had the same "demo" prefix. This will cause somethings (mainly the installed Window Services) to overlap and screw things up. So always make sure you have a unique prefix for all your instances. 

Uninstalling, while manual, is not too painful

The above issue lead me into figuring the uninstall process out. While there is a nice script to install everything there is not a script to uninstall. There is a nice thread on StackExchange on doing this. How to Uninstall an instance created using the Sitecore Install Framework.

Access to the registry key 'Global' is denied.

This one was a bit hidden and you will not even know it is there unless you check your logs. Here is a nice blog with the fix. 

Saturday, January 7, 2017

Windows Workflow Unit Testing

I know people have very mixed opinions about Windows Workflow and, to be honest, so do I. Really I am not even sure if it has much of a future given the little attention Microsoft has given it. However, despite all that and rather your like it or not there are times when you may use it and want to unit test it. The question is how? Well there are not a lot of options but there is one, that for me, has proven valuable.

People tend to use Windows Workflow in a few different ways, so first let me explain how I have use it most. I have never really used it where I programmatically created and instantiate of my own workflow. For me it has pretty much all been using the Windows Workflow designer and using IIS as my workflow host. Then inside those XAML workflows I have custom activities I create and need to test. Do to this I have found one tool that does this pretty well and pretty easy.

Microsoft Activities Unit Testing

It is an old framework but it still gets the job done. There is not a real Dependency injection framework for Windows Workflow activities but with this you can basically use the service locator pattern to do unit testing. You could get fancy with and use the metadata model to store a DI container like Castle Windsor but so far, for me, I have found that activities normally have discreet enough unit of works that I don't need anymore. The CodePlex page has a good how to get setup page. It also has a good how to use the framework page.

What is nice about the framework is it comes with an easy way to get your activity hosted as a Windows Workflow activity with standard in and out arguments so it really behaves just like a Workflow Activity. There are a couple key aspects to getting this setup.

Getting the host setup is easy:
public void SumAddsTwoNumbers()
    // Arrange
    var activity = new Sum();
    var host = WorkflowInvokerTest.Create(activity);

    // InArguments is a dynamic object of type Microsoft.Activities.WorkflowArguments
    host.InArguments.Num1 = 1;
    host.InArguments.Num2 = 2;

        // Act

        // Assert
        // Note: The host automatically captures the out arguments
        host.AssertOutArgument.AreEqual("Result", 3);
        // Note: The host automatically captures tracking

Notice in the above code all you are doing is creating an instance of your normal activity, then using the framework to create a host for it. You can then just setup your InArgs (notice these are dynamic so don't expect intellisense here).

Ok that is great, but what about my dependencies? Here is the magic for that.

In your activity you need to setup a new method override.
protected override void CacheMetadata(CodeActivityMetadata metadata)
    metadata.AddDefaultExtensionProvider<YourInterface>(() => Your code to create the instance);

Adding the default extension provider allows your code later (by the below line of code) to get the instance it should use. If nothing else is provided it, well, uses the default that is setup.

this.unitOfWork = context.GetExtension<IUnitOfWork>();

For my code I am assigning whatever extension is setup to my unitOfWork field. This lets me use that object through the rest of my code.

Now you need to get it setup for your unit test.
this.unitOfWork = Mock.Of<IUnitOfWork>(m => m.MytRepository == Mock.Of<IMyRepository>());

this.workflowHost.Extensions.Add(() => this.unitOfWork);

All I need to do is create my mocked object (I used Moq). Then register that mock as a workflowhost extension. Now when I run this code as part of my unit test it will see my registered moq as the extension for that interface and use it instead of the default. But when the code runs in production it will not see any registered extension so it will use the registered default. That is it. I am sure there is even more you can do with this but that should get you up and running with Windows Workflow Activity unit testing.

Here is a look at how to do your assert on what is returned.
var outarg = this.workflowHost.OutArguments["Result"] as List<OrderFulfillment>;
outarg.Select(p => p).Count(p => p.CustomAttributes == null).Should().Be(0);

Here is another example of how someone used it.
Here is a Stackoverflow thread with some resources as well.

Saturday, December 3, 2016

Getting Started with .Net Core and Docker

I have been working to understand .Net Core and Docker as of late. It is cool technology but a bit of a paradigm shift. I have found a few good resources here and wanted to start recording them in case I needed to come back to them a long this journey.

First step to all of this is to install the .Net Core SDK.

Second step is to get Docker.

Once these are installed you have all the command line you need for awhile as you start moving down this road.

One of the first steps for me was getting my head around what .Net Core is and how it is different then the >net world I have been living in. Here is a great walk through that help me understand this.
First Steps Exploring NET Core and ASPNET Core

For me that finally connected the dots of what was happening now with the .net framework.

The next step was to understand what Docker is going to do for me. I had played and read about it but I needed to get hands-on. To do that I actually ran into another great article.

Deploy an ASP.NET Core Application on Linux with Docker

With those tutorials, I started to understand how to put .Net Core application together and how to take that application and put it into a Docker image and container.

Docker has some good docs to understand a little deeper. For me a little reading on containers and images went a long way. After that as I used the command line to look at images and containers I had locally for Docker as started wonder why and how to use repository names, image ids, and tags. Well a little reading through the Docker docs helped there too.

Friday, September 9, 2016

Powershell, XML and Visual Studio build event

Visual Studio provides a lot of capability, but sometimes you need a little Powershell. I needed to update an XML file in my solution based on the projects build configuration. If the configuration was "Release" setup a node in the XML to be value A. If the configuration was "Debug" setup a node in the XML to be value B. I actually did not realize how easy it is to work with XML within Powershell. I found a nice little start on StackOverflow.

My XML was more complex of course but it is still pretty easy to work with. Here is my post-build event.

"%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe" -file ..\..\..\..\Scripts\ToggleXMLValues.ps1 $(ConfigurationName) $(TargetDir)

Then in a Powershell file I wrote some pretty simple code.

param ([string]$configName, [string]$outputDir)

$doc = [xml](Get-Content "$($outputDir)\config\myfile.xml")

if ($configName -eq "Release")
 $doc.Configuration.Global.Storages.Storage[0].Url = "URL 1"
 $doc.Configuration.Global.Storages.Storage[0].Url = "Url 2"


In my case I had an XML document that looked like this.

      <Storage Url="My URL"/>

With that little bit of code I can now toggle this value whenever I build.

Friday, July 1, 2016

Uniting Testing Expression Predicate with Moq

I recently was setting up a repository in a project with an interface on all repositories that took a predicate. As part of this I needed to mock out this call so I could unit test my code. The vast majority of samples out there for mocking an expression predicate just is It.IsAny<> which is not very helpful as it does not test anything other then verify it got a predicate. What if you actually want to test that you got a certain predicate though? It is actually pretty easy to do but not very straight forward.

Here is what you do for the It.IsAny<> approach in case someone is looking for that.

this.bindingRepository.Setup(c => c.Get(It.IsAny<Expression<Func<UserBinding, bool>>>())) 
.Returns(new List<UserBinding>() { defaultBinding }.AsQueryable());

This example just says to always return a collection of UserBindings that contain “defaultBinding” (which is an object I setup previously).

Here is what it looks like when you want to pass in an expression predicate that actually gets executed and only returns data if it matches something.

For my example I have a predicate that executes against an object collection of type “UserBinding.” The part to understand here is you are not passing to the Moq setup an expression. You are telling Moq to compile whatever expression it is given and see if that expression finds a match against the given object your provided. So in the example, does the expression passed in find a hit when executed against my “defaultBinding” object. If so return whatever data you want to return (in my case I am returning a collection of UserBindings as a queryable).

var defaultBinding = new UserBinding { Binding = new Binding { Name = "Default binding" }, Domain = "" };
this.bindingRepository.Setup(c => c.Get(It.Is<Expression<Func<UserBinding, bool>>>(y => y.Compile()(defaultBinding))))
.Returns(new List<UserBinding>() { defaultBinding }.AsQueryable());

That is it. Now you can test expression predicates and return different out put based on the expression you are expecting to be passed in.

Thursday, August 27, 2015

QuickBooks Web Connector with WCF services

I have been working a project to try and get some backend data plugged into QuickBooks so we can push that data into QuickBooks. We are using desktop QuickBooks hosted by another provided so our integration options are limited. We ended up using the QuickBooks Web Connector. First let me say that documentation on this front is limited and what you can find is normally old. Here is some of the reference we were able to find:
Web connector overview
QuickBooks error codes
Helpful UI for how to construct QuickBooks API calls
Web connector SDK
QBWC Dev Guide
You will need to create a QWC file and here is a QWC Example.
One of the biggest issues we ran into was all the documents and examples we could find were based on the older ASMX services. Because of this we struggle for a bit on understanding how the service needed to be attributed. So here is what our WCF service contract ended need to be set as.

   [GeneratedCode("wsdl", "")]

   [WebServiceBinding(Name = "QuickBookConnectorService", Namespace = "")]

   public class QuickBookConnectorService : QBWebConnectorSvc, IQuickBookConnector


       /// <summary>Provides a way for web-service to notify QBWC of it's version. This version string shows up in 

       /// the More Information pop-up dialog in QBWC.</summary>

       /// <returns>The <see cref="string"/>.</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string serverVersion()




       /// <summary>Optional callback allows the web service to evaluate the current web connector version and react to it.</summary>

       /// <param name="strVersion">The version of the QB web connector supplied in the web connector's call to clientVersion</param>

       /// <returns>The <see cref="string"/>See QB Dev guide for details on return values</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string clientVersion(string strVersion)




       /// <summary>The authenticate.</summary>

       /// <param name="strUserName">The username the connector should be running as to authenticate.</param>

       /// <param name="strPassword">The password the connector should be running as to authenticate.</param>

       /// <returns>The <see cref="string[]"/>Must contain 2 elements. 1) NONE, NVU, BUSY, string.empty or Company file name. 

       /// 2) Value noted how long to postpone the update 

       /// 3) Sets lower limit on how often QBWC can call 

       /// 4) Sets the min run seconds</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string[] authenticate(string strUserName, string strPassword)




       /// <summary>Tells your web service about an error the web connector encountered in its attempt to connect to QuickBooks or QuickBooks POS.</summary>

       /// <param name="ticket">The ticket from the web connector. This is the session token your web service returned to the web connector's

       ///  authenticate call, as the first element of the returned string array</param>

       /// <param name="hresult">The HRESULT (in HEX) from the exception thrown by the request processor</param>

       /// <param name="message">The error message that accompanies the HRESULT from the request processor.</param>

       /// <returns>The <see cref="string"/>.</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string connectionError(string ticket, string hresult, string message)




       /// <summary>The web connector's invitation to the web service to send a request.</summary>

       /// <param name="ticket">The ticket from the web connector. This is the session token your web service returned to the web connector's

       ///  authenticate call, as the first element of the returned string array.</param>

       /// <param name="strHCPResponse">Only for the first sendRequestXML call in a data exchange session will this parameter contains response 

       /// data from a HostQuery, a CompanyQuery, and a PreferencesQuery request. This data is provided at the outset of a data exchange because

       /// it is normally useful for a web service to have this data.In the ensuing data exchange session, subsequent sendRequestXML calls from 

       /// the web processor do not contain this data, (only an empty string is supplied) as it is assumed your web service already has 

       /// it for the session. </param>

       /// <param name="strCompanyFileName">The company file being used in the current data exchange.</param>

       /// <param name="qbXMLCountry">The country version of QuickBooks or QuickBooks POS product being used to access the company. .</param>

       /// <param name="qbXMLMajorVers">The qb xml major vers.</param>

       /// <param name="qbXMLMinorVers">The qb xml minor vers.</param>

       /// <returns>The <see cref="string"/>Either string of "NoOp" to tell the QBWC to pause or a valid qbXML string</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string sendRequestXML(

           string ticket,

           string strHCPResponse,

           string strCompanyFileName,

           string qbXMLCountry,

           int qbXMLMajorVers,

           int qbXMLMinorVers)




       /// <summary>Returns the data request response from QuickBooks or QuickBooks POS.</summary>

       /// <param name="ticket">The ticket from the web connector. This is the session token your web service returned to the web connector's

       ///  authenticate call, as the first element of the returned string array.</param>

       /// <param name="response">Contains the qbXML response from QuickBooks or qbposXML response from QuickBooks POS.</param>

       /// <param name="hresult">The hresult and message could be returned as a result of certain errors that could occur when 

       /// QuickBooks or QuickBooks POS sends requests is to the QuickBooks/QuickBooks POS request processor

       /// via the ProcessRequest call.If this call to the request processor resulted in an error (exception) instead of a 

       /// response, then the web connector will return the corresponding HRESULT and its text message in the hresult and message 

       /// parameters.If no such error occurred, hresult and message will be empty strings.</param>

       /// <param name="message">See above under hresult.</param>

       /// <returns>The <see cref="int"/>A positive integer less than 100 represents the percentage of work completed.</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override int receiveResponseXML(string ticket, string response, string hresult, string message)




       /// <summary>Allows your web service to return the last web service error, normally for display to the user, before 

       /// causing the update action to stop.</summary>

       /// <param name="ticket">The ticket from the web connector. This is the session token your web service returned to the web connector's 

       /// authenticate call, as the first element of the returned string array.</param>

       /// <returns>The message <see cref="string"/> describing the problem and any other information that you want your user to see</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string getLastError(string ticket)




       /// <summary>Tells your web service that the web connector is finished with the update session</summary>

       /// <param name="ticket">The ticket from the web connector. This is the session token your web service returned to the web connector's 

       /// authenticate call, as the first element of the returned string array</param>

       /// <returns>The <see cref="string"/>Specify a string that you want the web connector to display to the user showing the status of

       /// the web service action on behalf of your user.This string will be displayed in the web connector UI in the status column.</returns>

       [OperationContract(Action = "")]

       [XmlSerializerFormat(Style = OperationFormatStyle.Document, Use = OperationFormatUse.Literal)]

       public override string closeConnection(string ticket)


Consolibyte has some good information on his wiki as well.

Tuesday, August 25, 2015

Installing Tridion 2013 SP 1 on Hyper-V

I recently needed to install Tridion on Hyper-V. To do so I found a great series of blog posts on the Tridion website. The series starts with setting up a VM image instead of a Hyper-V. Really there is no difference (you install the OS on the image). One issue I did run into with Hyper-V though was my local network started to run a bit slower. I did some digging and found out it was because when the network bridge was setup for the Hyper-V image the priority order of the adapters put the Hyper-V image first. IT Writing has a great blog post on how to fix this.

Once you have Hyper-V ready you can follow the post step for step to get everything installed. I needed once change in that I already had a Content Management database setup. I wanted to move my database from production down into this Hyper-V instance so I did not need to setup the database from scratch.Here is what I did and a few of the tweaks I needed to make.

After installing I would try and open up the Tridion CM website. However, my login always failed. Looking into the event viewer’s security log I found the following:

An account failed to log on.

    Security ID:        NULL SID
    Account Name:        -
    Account Domain:        -
    Logon ID:        0x0

Logon Type:            3

Account For Which Logon Failed:
    Security ID:        NULL SID
    Account Name:        MTSUser
    Account Domain:        WIN-M3213T5Q594

Failure Information:
    Failure Reason:        An Error occured during Logon.
    Status:            0xC000006D
    Sub Status:        0x0

Since the Hyper-V image was running outside of a domain controller I did some digging and found a Microsoft KB article that fixed the issues. My resolution was method 1.

The other thing I had to do was update the Trustees table in the Content Manager database. Keep in mind that I am restoring an existing database. That existing one is on a domain controller so the MTSUser account that was created was linked to a different domain. So in the Trustees table find your MTSUser and in the name column update the domain part of the domain to whatever your Hyper-V’s machine name is (<machine name>\MTSUser). This assumes that during your install you said your system account was MTSUser.

After these steps I was able to login and all was good.