Google Analytics

Tuesday, August 27, 2013

Fusion Log Viewer

Back to basics, indeed. The Fusion log viewer is an arrow that should be in the quiver of every Windows application developer. It wasn't in mine, however, until this morning. In our morning Scrum I complained that I was having trouble tracking down an issue with Infragistics Reporting. The report viewer was trying to load some libraries at runtime and was giving me the classic non-descript error: 

"System.NullReferenceException: Object reference not set to an instance of an object." 

The full error is:

Infragistics.Controls.Reports.ReportViewerException: An unknown error occurred while processing the report. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Infragistics.Reports.Engine.ServiceModelMefDiscoverer.Initialize(IServiceModelManager serviceModel)
   at Infragistics.Reports.Engine.ServiceModelBuilder.Setup(IServiceModelConfiguration configuration)
   at Infragistics.Controls.Reports.ReportProcessorLocalAdapter..ctor(ReportSource reportSource, Assembly reportAssembly, IEnumerable`1 externalDataSources)
   at Infragistics.Controls.Reports.RenderingEngineFactory.CreateLocalEngine(ReportSource reportSource, Assembly reportAssembly, IEnumerable`1 externalDataSources)
   at Infragistics.Controls.Reports.ProcessingSessionStateMachine.CreateProcessor(IRenderingEngineFactory renderingEngineFactory, IEnumerable`1 externalDataSources, Report reportDefinition, Assembly reportAssembly)
   --- End of inner exception stack trace ---
   at Infragistics.Controls.Reports.ProcessingSessionStateMachine.CreateProcessor(IRenderingEngineFactory renderingEngineFactory, IEnumerable`1 externalDataSources, Report reportDefinition, Assembly reportAssembly)

   at Infragistics.Controls.Reports.ViewerStateMachine.RenderHelper(RenderParameters renderParameters, Report reportDefinition, Assembly reportAssembly)

We narrowed it down to an issue loading libraries, since the viewer was working on my machine where I had the Infragistics tools installed.

My colleague said, "No problem. We'll just look at it in the Fusion log and see what the issue is." I gave him a blank stare. "Find me in a bit, and I'll show you." Not to throw said colleague under a bus, but then he spent the next 90 minutes on the phone. Important stuff, but I have these nice reports that I want to show off to the team.

Then I found this blog from Scott Hanselman (who has saved me on a number of such occasions): http://www.hanselman.com/blog/BackToBasicsUsingFusionLogViewerToDebugObscureLoaderErrors.aspx. It is very simple to fire up and use, and I am kicking myself for the times that I had similar issues that took me hours if not days to track down.

After firing up the Fusion Log Viewer on a machine where I did not have Infragistics installed, clearing the logs, and then running the troubled application, I found that the app was trying to load up two libraries (InfragisticsWPF4.Reports.Controls.Charts.XamDataChart.v13.1.dll and InfragisticsWPF4.Reports.Controls.DataVisualization.v13.1.dll for those of you who may run into the same issue) that I had added the references to but had forgotten to include in the Wix installer. With the libraries added I can finally show off what has been almost 3 weeks of work on the reporting functionality of this application.

Tuesday, June 11, 2013

I've been having these Inception-like dreams where I dream within my dreams. About a week ago I "awoke" from a dream where I had mistaken the sounds of kids playing in my dreams for the real sounds of a group of teenagers breaking into our house. One came into our bedroom and rushed us as we sat up in bed. And that's when I woke with a start out of that dream.

Last night I had a dream in which I had two other dreams. In each of the two inner dreams there was an owl present, which I found to be rather remarkable. When I awoke I told Jen about each dream and how strange that both had a different type of owl. Why owls, I thought to myself. It wasn't until just now that I realized I couldn't have had a conversation with Jen about my dream owls. She was fast asleep when I left the house this morning. I dreamt the conversation.

How far will this progress? Can I nest three of them together? I'm scared to even think about that. Time does seem to go slower in the inner dreams. Will I be lost for days or months inside some dream? I need to get myself a token.

Friday, March 1, 2013

More Custom Actions with WiX


The tutorial gave a relatively simple action to check a product key on install. For my own project I would like to ask the user to specify a remote service uri, check that the uri format is okay, warn the user if it is not, and finally write the value to my application’s configuration file.

First of all, my installer had no user interface at all, so I added the WixUI_FeatureTree as I described in a previous post. As in my tutorial, I am going to add the new custom action code by right-clicking the solution and doing Add > New Project and selecting Windows Installer XML > C# Custom Action Project and I will name it CustomInstallActions.

 The custom action will read in what the user input as SERVICEURI and will output a URIACCEPTED value to the install session.  For right now I am simply testing whether the string that is passed in can be used to form a URI.  The CustomAction attribute decorating the class indicates the method is an entry point for the installer custom action.

public class CheckUriAction
    {
        /// 
        ///     Checks that user input is a well-formed URI.
        /// 
        /// 
        ///     The installation session.
        /// 
        /// 
        ///     The . This will always be success so
        ///     long as the evaluation can be made. It does not indicate
        ///     whether the URI is well-formed or not.
        /// 
        [CustomAction]
        public static ActionResult CheckUri(Session session)
        {
            // log that custom action is being called.
            session.Log("Checking user input URI");
            
            // get the user string input from the install session
            var uri = session["SERVICEURI"];
            
            // check that the string passed in can be used to construct a valid URI
            var isURI = Uri.IsWellFormedUriString(uri, UriKind.Absolute);
            
            // write the evaluation back to the installation session
            session["URIACCEPTED"] = isURI ? "1" : "0";
            
            // return succes. This allows the installation to continue and the
            // user will be able to change the input if necessary. A different
            // ActionResult would cause the installation to end.
            return ActionResult.Success;
        }

To my Product.wxs WiX file I add a Fragment to reference this custom action and the dll that contains it. As I explained in a previous post, the C# custom action gets wrapped by [someEXE] into a managed custom action. This is handled automatically if you use the WiX C# CustomAction project, so the actual dll we reference is the wrapped one that has the .CA.dll extension.

<Fragment>
    <CustomAction Id='CheckingUri' BinaryKey='CheckUri.CA' DllEntry='CheckUri' />
    <Binary Id='CheckUri.CA' SourceFile='..\..\..\CheckURIAction\bin\Debug\CustomInstallActions.CA.dll' />
</Fragment>

The DllEntry attribute of the CustomAction is the name of the method that we designated as the entry point in our class. The Id of the CustomAction element is what we will use to reference this in our installation UI.

This UI is a new dialog I add to the WiX project that will ask for the user’s input.  By right-clicking on the WiX project and selecting Add > New Item > WiX > WiX File I’m given the skeleton of a WiX fragment. To this I add a UI component and within that a Dialog I will call ServiceDlg. If you followed the WiX tutorial you’ve done this. The key controls to look at are the “NameEdit” Edit control and its property called SERVICEURL and the “Next” PushButton control. When the user pushes the Next button it will “Publish” the CheckingUri action we specified in the fragment above. The value of SERVICEURL will be passed through the session to the action. In turn the action adds the URIACCEPTED property to the session, which we evealute to determine which dialog to show to the user. In the case there the URI is not accepted, we show the InvalidURIDlg that we will add below. If everything is ok we allow the user to move on to the CustomizeDlg dialog which is already built in to the WixUI_FeatureTree set.

<Dialog Id="ServiceDlg" Width="370" Height="270" Title="[ProductName] Setup" NoMinimize="yes">
        <Control Id="ServiceLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="&EED Service Address:" />
        <Control Id="NameEdit" Type="Edit" X="45" Y="85" Width="220" Height="18" Property="SERVICEURI" Text="{80}" />
        <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back">
          <Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
        </Control>
        <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
          <Publish Event="DoAction" Value="CheckingUri">1</Publish>
          <Publish Event="SpawnDialog" Value="InvalidURIDlg">URIACCEPTED = "0"</Publish>
          <Publish Event="NewDialog" Value="CustomizeDlg">URIACCEPTED = "1"</Publish>
        </Control>
        <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
          <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
        </Control>
        <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
        <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>[ProductName] needs access to the DVS Core Service</Text>
        </Control>
        <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
        <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>{\WixUI_Font_Title}Core Service Configuration</Text>
        </Control>
        <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
      </Dialog>

I add the InvalidURIDlg to this same UI element in my ServiceDlg.wxs since they go hand-in-hand. This dialog is created from the SpawnDialog event in the ServiceDlg dialog, so it has that dialog as a parent. The dialog displays a message to the user and then the user’s one option is to click a button to make the dialog go away (and return back to the ServiceDlg).

<Dialog Id="InvalidURIDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes">
  <Control Id="Return" Type="PushButton" X="100" Y="57" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Return">
    <Publish Event="EndDialog" Value="Return">1</Publish>
  </Control>
  <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30" TabSkip="no">
    <Text>The service location provided is not a valid URI.</Text>
  </Control>
</Dialog>
 
The final thing I need to do show this dialog is fit this new dialog into the install sequence.  I add the dialog reference to the same Fragment where I reference the FeatureTree set. Then I add the events to insert the ServiceDlg between the LicenseAgreementDlg and CustomizeDlg of the FeatureTree set.

 <Fragment>
    <UI Id="MyWixUI_FeatureTree">
      <UIRef Id="WixUI_FeatureTree" />
      <UIRef Id="WixUI_ErrorProgressText" />
 
      <DialogRef Id="ServiceDlg" />
 
      <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="ServiceDlg" Order="2">LicenseAccepted = "1"</Publish>
      <Publish Dialog="CustomizeDlg" Control="Back" Event="NewDialog" Value="ServiceDlg" Order="2">1</Publish>
    </UI>
  </Fragment>

You can run the install as it is and see the custom action “in action” but at this point it isn’t particularly useful unless we do something with the value that the user input. My goal is to write this value to the App.config of my application as the address for a service endpoint. Initially I just want to try to write the name to the appSettings element of the App.config. This is really quite simple with WiX using the XmlFile utility extension.

First we need a reference from the WiX project to the WixUtilExtension library. With that reference in place WiX will handle the correct commands to compile the library in with our installer. So to our main Wix root element we need to add the XML namespace for util.

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
 
Then to the same component that contains the configuration file that we want to change we add a series of XmlFile elements. The first will create a new node “add” to the appSettings node in our config file. Note that the configuration must have an appSettings node to begin with, AND even if the node is empty it must have separate opening and close tags. will not work; you need to have under the configuration element. The second element will add a “key” attribute to the “add” element and give it a value of “ServiceUri”. The final element will give a “value” attribute to our element with the value of the SERVICEURI that was passed in from the user. The INSTALLFOLDER in the file references the directory I gave the Id of INSTALLFOLDER which is the main install folder for the application.

         <File Id="File.ElectionDataService.Config" Name="ElectionService.exe.config" Source="$(var.ServicePath)\ElectionService.exe.config"/>
        <util:XmlFile Id='Settings.ElectionService.Config1' File='[INSTALLFOLDER]ElectionService.exe.config'                      
         Action='createElement' Name='add' ElementPath='//configuration/appSettings' Sequence='1' />
        <util:XmlFile Id='Settings.ElectionService.Config2' File='[INSTALLFOLDER]ElectionService.exe.config'
         Action='setValue' Name='key' Value='ServiceUri' ElementPath='//configuration/appSettings/add' Sequence='2' />
        <util:XmlFile Id='Settings.ElectionService.Config3' File='[INSTALLFOLDER]ElectionService.exe.config'
         Action='setValue' Name='value' Value='[SERVICEURI]' ElementPath='//configuration/appSettings/add' Sequence='3' />
 
Running the installation now results in the following appSettings block in the configuration file:
   

Simply writing to the appSettings is okay, but what I really want to do is save the user input in the configuration as the endpoint for a service that the application needs. To do this I need to refine where the XmlFile elements write to. My config file already has the endpoint element that I need; I just want to fill in the address attribute. Before installation it looks like this:
<endpoint binding="wsHttpBinding" bindingConfiguration="wsHttpBindingNoSec"
    contract="ICoordinateInterface" name="WSHttpBinding_ICoordinateInterface">
 
I really only need a single XmlFile element in my Wix file that will add the address attribute to that endpoint node with a value of whatever the user entered. Another consideration is that I actually have multiple endpoints for different services in the configuration file, so I need to select the endpoint with the service interface that corresponds to the one I want to change. I replace the sequence of XmlFile elements above with a new one.

         <File Id="File.ElectionDataService.Config" Name="DVS.EMS.Services.ElectionData.ElectionDataService.exe.config" Source="$(var.ServicePath)\DVS.EMS.Services.ElectionData.ElectionDataService.exe.config"/>
        <util:XmlFile Id='Settings.ElectionDataService.Config' File='[INSTALLFOLDER]DVS.EMS.Services.ElectionData.ElectionDataService.exe.config' 
          Action='setValue' Name='address' Value='[SERVICEURI]' ElementPath='//configuration/system.serviceModel/client/endpoint[\[]@contract="ICoordinateInterface"[\]]' Sequence='1' />

The bracketed @contract=… attribute in the ElementPath picks out the individual endpoint element with the service interface that I want to change, but not the others. The brackets need to be escaped with the pattern shown.

Now running the install my configuration ends up with the following endpoint: