Brothers In Code

...a serious misallocation of .net resources

WPF ToolBar Buttons Do Not Cause Loss Of Focus

Consider this little WPF Window:


    <DockPanel>
      <ToolBar DockPanel.Dock="Top" >
        <Button Content="Save" Name="BtnSave" Click="BtnSave_Click" />
      </ToolBar>
      <StackPanel>
        <TextBox Name="IptTextBox" Text="{Binding Content, ElementName=OptLabel}"  />
        <Label Name="OptLabel" Content="Hello" />
        <Button Content="Lose Focus" />
      </StackPanel>               
    </DockPanel>

If you type something into the text box and then click the "Lose Focus" button, the label is updated as expected (since the default UpdateSourceTrigger on the binding is on focus).  However, if you click on the save button in the toolbar, the label is not updated.

According to MSDN, ToolBars, in addition to a couple of other objects, have a different "Logical Focus Scope." To fix this problem, I added the following code to my save button:


      Control focusedElement = FocusManager.GetFocusedElement(this) as Control;
      if (focusedElement != null)
      {
        focusedElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        focusedElement.Focus();
      }

The "Could Not Load File Or Assembly" Error Strikes in a New Way

I got bit by this old error in a new way the other day so I thought I should make a quick checklist for myself since I made some mistakes in my troubleshooting process. 

First I would start by reading How the Runtime Locates Assemblies.  However, unless you are depending one of the methods that change the normal process, like including additional "codebases" or publisher policy redirection, then we can widdle this down to a few simple causes:

  • A Referenced Assembly is not in the Application's Path
  • A Referenced Assembly is not in the Global Assembly Cache
  • The Referenced Assembly is a different version than the installed assembly.
  • The Application is 32 bit and the dll is 64 bit

The Old Problems - Local Files and the GAC

Many if not most applications rely on a simple xcopy deployment, meaning that all of the application's files will be bundled together in the same directory.  If you're simply copying or app folder from one place to another and it's not working on the new machine than either you simply missed a dll or you were using a GAC'd assembly on your development machine and didn't realize it.  If you're using a third party component that was installed with a setup program, than there's a good chance that your referenced assembly is in the GAC.  In that case you need to decide if you you want to run the third party install program as part of your deployment or figure out which dlls that you need and copy them to the folder.

GAC or otherwise, your app isn't going to find anything if it references a different version than what you have installed.  There are two options if realigning the deployed version isn't an option.  Either change "Specific Version" to false in the reference properties in Visual Studio or do a policy redirection.  I actually recommend the latter since big companies will include policies for the GAC that point old references to newer versions.  On the flip side, changing specific version to false will let your app load any version including an ancient one which might give you all sorts of strange errors.

 The New Problems - 32 Bit, 64 Bit and the Platform Target Configuration

It's the last cause that is the inspiration for this post since it has now bit me multiple times.  In my case i was referencing Oracle.DataAccess.dll from Oracle's data provider for .net.  ODP.net throws in some additional variables like PATH and "ORACLE_HOME" environment variable dependencies that threw me off correctly debugging the problem.  After double checking that the dll was in the GAC, and then even copying the dll to the app folder in desperation, I still was getting the error in question.  The error also included the phrase "or one of its dependencies" which kept doubling me back to a PATH problem.  In retrospect I really don't ever remember a case where this error was caused by a dependent dll so I'm not sure why I put so much stock in that route.

Finally I got my wits back and loaded a consistent savior, Sysinternals Process Monitor.  I don't know why I'm so slow to use this tool some times.  Maybe it's the 5 minutes you have to spend getting the filters right, but 9 times out of 10 it more or less tells me exactly what is wrong.  Sure enough I saw the program trying to find my dll in GAC_32.  Why is it looking for a 32 bit dll when we are on a 64 bit machine with the 64 bit oracle provider....you dumb-ass I thought to myself, knowing that I had seen this before.  Sure enough the task manager showed the infamous *32 next to my process.  I went back to visual studio and looked that the build properties for my exe project - once again Platform Target was set for "x86".  I set it to "Any CPU", recompiled and the stupid world was right again.

ORA-01036: Illegal Variable Name/Number Using ODP.net

If you've reading this you've probably been to several sites already and are just about ready to kill something. Why Oracle couldn't print the name of the parameter that you attepted to bind is beyond me, but after all this is a company that was very recently recommending "locator varibles." I'll be honest and say there is no magic bullet here. Compared to Sql Server's Profiler, Oracle's statement tracing functionality is not developer friendly and the ODP client side tracing is pretty worthless. I can really only give some tips for things to look for and some brute force debugging tactics.

To be clear, this is with Oracle's data provider (Oracle.DataAccess.Client) and not Microsoft's Oracle provider (System.Data.OracleClient). The Microsoft provider is being deprecated so it is not recommended that you follow some of the older suggestions of simply switching providers.

In short, this error is a catch all for some sort of mismatch between the parameters referenced in the command text, and the parameters added to the command object. To start there's a couple of simple things to look for:

  • Missing Comma - This one annoys the crap out of me. Forget a comma between constant values in an insert statement, and you'll get a nice "missing comma" error. However, forget a comma between bind variables and you'll get the error in question. My guess is that some genius decided to look only for an operator to terminate a bind variable and didn't consider whitespace.
  • The number of parameters match - This one is an important one and an easy one. We just ran into a problem where a misnamed command variable was causing an extra parameter to be added that we werent seeing. I simple cmd.Parameters.Count in the watch window would have quickly told us there were more params in the collection than were in the statement.
  • The order of the parameters match - Despite the warm fuzzy you might get when your parameter names match what is in your statement, those names do nothing by default. The default functionality is to bind the parameters in the order they are added. You can change this by setting OracleCommand.BindByName = true;
  • Use OracleCommand.BindByName=true if you use the same parameter twice. This is a bit of assumption since I've not tested to confirm it, but I assume that because the default functionality is to bind by order, duplicated parameters would also need to be duplicated on the command object if BindByName is left as false.
  • The type of the parameters match - The type parameter needs to match the type in the database. There is a little bit of flexibility with this with types like numerics (excluding overflow errors, OracleDbType.Int32 can be used in place of OracleDbType.Int64), but if you got lazy and defined parameter as OracleDbType.Varchar2 when you've got a Date column you might get this error. Generally I just use the Parameters.Add overload that takes variable name and value and let oracle decide on the type. This however does not work on output parameters - I set both the type and the length (at least for varchars) for those.

If the above eye-ball debugging doesn't work, I suggest that you start trimming down your statement and params until you get a successful execution and then work backwards from there. I know that's not much to go with but if I find something else I'll be sure to post it.

WPF ComboBox's SelectedValue Property Doesn't Update Binding Source

A quick google search will find you a ton of information on this topic.  Unfortunately the majority of them are simple path issues with a binding expression.  I had a somewhat unique issue from a very simple mistake.

I had the following ComboBox:


          <ComboBox Grid.Row="7" Grid.Column="1" Name="IptGpSalesPersonSlprsnid2" SelectedValue="{Binding Data.SLPersonId, ValidatesOnDataErrors=True}" ItemsSource="{Binding SalesPeople}" DisplayMemberPath="DisplayName" SelectedValuePath="SalesPersonId" />

This mostly did what I expected.  It displayed "DisplayName" for the entire list of objects, it showed "SalesPersonId" as the SelectedValue, it would update another TextBox with the SelectedValue, and the selection could be changed by changing the SelectedValue in that TextBox.  But for the life of me, it would not update the SLPersonId property on the "Data" object it was bound too. 

"Data" is of type DynamicObject that I'm using as a binding proxy.  I really didn't expect anything wrong with it since a TextBox bound to the same property would update the property without a problem.  Still I trolled the overridable properties of DynamicObject and ended up adding the default overrides for the Equals and TryConvert methods.  Sure enough, upon selecting a new item from the ComboBox, the debugger broke on "Equals."  To my surprise, base.Equals was actually returning false.  Why?  This should be true since this was a reference type and I was returning the same object....damn....there's my problem...

In my view-model, my data property looked like this:


    public DynamicProxy Data
    {
      get { return new DynamicProxy(this.data); }
    }

I quickly added that code as a test and never made a variable to hold the new DynamicProxy instance.

Why ComboBox checks the variable instances to see if they are the same, i don't know, but since i look at what I did as bad code anyway, it doesn't really matter :P.

 

B

Moving a "Simplified Configuration" WCF Service From IIS Host to a Self Host

WCF configuration files can get pretty big pretty fast, so the new Simplified Configuration in .Net 4.0 definately interested me.   However there are a couple of errors that make perfect sense but might catch you off guard if you're not reading the fine print.

Error: Service has zero application (non-infrastructure) endpoints

The simple configuration is supposed to create one end point for each base address / contract combination.  So what happend here?  This can be explained by one simple line in the above linked document:

Services hosted under Internet Information Services (IIS) or Windows Process Activation Service (WAS) use the virtual directory as their base address.

Services NOT hosted in one of these environments assume nothing which is a good thing.  It wouldn't make a lot of sense if some console.exe program just randomly grabbed a port to use.  So you are required to specify a base address:


    <services>
      <service name="MyApp.MyService">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:51234"/>
          </baseAddresses>
        </host>
      </service>
    </services>

Error: The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address.

This is due to the following line:


  <serviceMetadata httpGetEnabled="true"/>

You're basically saying that you want the metadata to be exposed via http, but you've provided no http endpoint.  One fix is to set it to false.  Another is to add a second base address:


          <baseAddresses>
            <add baseAddress="net.tcp://localhost:51234"/>
            <add baseAddress="http://localhost:50080"/>
          </baseAddresses>


No rocket science here, but I wanted to post this because the answers I found weren't very specific.
 

AsyncPostBackTrigger On A Control External To An Update Panel Doesn't Display An Update Progress Control

Consider the following code:


    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>   
    <div>
      <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
      <asp:Button ID="Button2" runat="server" Text="Button" />
      <asp:Button ID="Button3" runat="server" Text="Button" />
      <asp:UpdatePanel ID="UpdatePanel1" runat="server">
   <Triggers>
     <asp:AsyncPostBackTrigger ControlID="Button2" />
   </Triggers>
      <ContentTemplate>
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
      </ContentTemplate>
      </asp:UpdatePanel>
     <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
      <ProgressTemplate>
       Please Wait
      </ProgressTemplate>
     </asp:UpdateProgress>
    </div>


//code behind:
  protected void Page_Load(object sender, EventArgs e)
  {
    Label1.Text = DateTime.Now.ToString();
    Label2.Text = DateTime.Now.ToString();

    if (Page.IsPostBack)
    {
      System.Threading.Thread.Sleep(2000);
    }
  }

In the above example, Button1 will do a partial page update for UpdatePanel1.  Button3 will cause a full postback.  Button two will also do a partial page update for UpdatePanel1 since it is listed as a trigger.  However, Button2 will not cause UpdateProgress1 to be displayed.

There is some help for this on the buttom of an article on codeproject.com, but I needed something for multiple controls.  My solution ended up being two functions:


  private void RegisterExternalTriggerFixForUpdateProgress()
  {
    String updateProgressWTriggerFix = @"
      var triggerMappings = new Array();

      function TriggerMapping(controlId, updatePanelId)
        {
          this.TriggerControlId = controlId;
          this.UpdatePanelId = updatePanelId;
        }
        function RegisterTriggerMapping(controlId, updatePanelId)
        {
          triggerMappings.push(new TriggerMapping(controlId, updatePanelId));
        }
        function GetTriggerMapping(control)
        {
          for(var i=0; i<triggerMappings.length; i++)
          {
            if(triggerMappings[i].TriggerControlId == control.id)
            {
              return triggerMappings[i];
            }
          }
          return null;
        }

        var prm = Sys.WebForms.PageRequestManager.getInstance();
        function CancelAsyncPostBack() {
            if (prm.get_isInAsyncPostBack()) {
              prm.abortPostBack();
            }
        }


        prm.add_initializeRequest(InitializeRequest);
        prm.add_endRequest(EndRequest);
        var postBackElement;
        function InitializeRequest(sender, args) {
            if (prm.get_isInAsyncPostBack()) {
                args.set_cancel(true);
            }
            postBackElement = args.get_postBackElement();
       
            var triggerMapping = GetTriggerMapping(postBackElement);
            if (triggerMapping != null) {
                $get(triggerMapping.UpdatePanelId).style.display = 'block';
            }
        }
        function EndRequest(sender, args) {
            var triggerMapping = GetTriggerMapping(postBackElement);
            if (triggerMapping != null) {
                $get(triggerMapping.UpdatePanelId).style.display = 'none';
            }
        }";

    this.Page.ClientScript.RegisterStartupScript(typeof(Page), "UpdateProgressWTriggerFix", updateProgressWTriggerFix, true);
  }
  protected void RegisterExternalAsyncTrigger(Control triggerControl)
  {
    this.Page.ClientScript.RegisterStartupScript(
      this.GetType(),
      triggerControl.ClientID,
      String.Format(@"RegisterTriggerMapping('{0}','{1}');",
        triggerControl.ClientID,
        UpdateProgress1.ClientID),
      true);
  }

Then to fix a particular AsyncPostBackTrigger control, you just need the following call in your Page_Load:


    //fix for async update from a control outside of the update panel
    RegisterExternalTriggerFixForUpdateProgress();
    RegisterExternalAsyncTrigger(Button2);

Events in Global.asax Do Not Fire - Did You Forget to Drop the File?

I was working in a web service project where by default, the codebehind is compiled into a single dll.  I didn't expect to need the Global.asax file that went with it's corresponding .cs file, but it turns out that the events won't fire without it.

Unwanted Assembly/EXE Sharing in Windows Mobile

I just got done merging two Windows Mobile applications into one and a user complained that the combined version was missing a feature of one of the original apps.  As I set out to prove him wrong I bumped into something very wierd.  I set a breakpoint in the event in the old app that I was expecting to check and nothing happened.  The form in the app did absolutely nothing.  However, I was pushing all http traffic from the mobile device thru fiddler and I could see that the form was hitting the webservice that the new combined app was supposed to be using.  The device also gave the beep code I had programmed in after a successful operation.  I put a breakpoint in a couple of other methods like Form.Load and Form.Close and even a couple of Button.Click events and those I was able to debug.  I wasn't loading any assemblies in the GAC but even if I was I couldn't explain this partial debugging ability.  The new app was on the device but it was in a separate folder so at first I disregarding the possibility that I was getting some sort of cross contamination.  But then I thought what if the other app is actually running?  Sure enough, after checking task manager, I could see the new app was running in the background and killing it let me debug the old app.  How did this happen?  The classes do have the same namespace and name but I've never heard of an app sharing another app's code in memory.

Wrong Tab Order in Windows Mobile

I had a form that didn't seem to want to follow the TabIndex that I had set on a number of controls.  The cursor jumped around passed controls with a lower index and then would come back to them later.  The difference between this form and others was that this form had a panel on it that hid a couple of controls until they were displayed upon the click of a radio button.  I did notice that if I deleted the controls and recreated them in the order I wanted them to tab, they would tab correctly.  However, starting over is a lousy fix so I dug into it a bit deeper.  After debugging I found that the TabIndex on the controls within the panel were being reset.  It also turns out that even though a Panel control has no mention of a TabIndex property in the Properties pane or with intellisense, it does in fact have that property.  Setting the Panel's TabIndex in the form load event fixed the issue for me.  To make it so I wouldn't get bit by this again,  I set the panel's TabIndex to the index of the first control in the panel.

B

Databound Winforms TextBox Reverts Back to Original Value

I had a TextBox that I had bound to a property on a class that implements INotifyProperyChanged.  No matter what I did, I just couldn't get the TextBox to update the bound property.  Each time the text box just reverted back to it's original value.  A simplified version of my class looked like this:

  public class KeyboardWedgeScanner : INotifyPropertyChanged
  {
    public TimeSpan ScanTimeout
    {
      get
      {
        return TimeSpan.FromMilliseconds(timeoutTimer.Interval);
      }
      set
      {
        timeoutTimer.Interval = value.TotalMilliseconds;
        OnPropertyChanged("ScanTimeout");
      }
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(String propertyName)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
    }

    #endregion
  }

In my form I bound the textbox with this in my constructor:

InScannerTimeout.DataBindings.Add("Text", scanner, "ScanTimeout");

This is exactly how a bound TextBoxes in another project and it worked fine there.  I messed around with the DataSourceUpdateMode parameter on an overload of Add but that didn't help either.  It wasn't until I set the formattingEnabled property to true did it start working:

InScannerTimeout.DataBindings.Add("Text", scanner, "ScanTimeout", true);

The only thing I can think of is that something about the TimeSpan class requires this to return the TextBox.Text's string value back into an instance of TimeSpan.