Friday, March 27, 2015

Angular Scopes and Factories

If you checked out my last post you know I needed to open a jQuery UI dialog from Angular.  This ultimately led me down a long and winding path to Angular factories and creating my own scope.

First the problem, in full detail.  I needed a link that would open a jQuery UI dialog to a form that needed rich controls (show/hide based on data and whatnot).  We'll call that dialog Details.  The Details dialog has a button that opens a History Summary dialog, which also needed rich controls.  One of the rich controls was a link that would open a History Details dialog.  The History Details dialog would look and behave exactly the same as the Details dialog except that the History Details dialog would not have the button that opens a History Summary.  So.  Link A opens Dialog 1, which opens Dialog 2, which has Link B, which opens Dialog 3, which is exactly like Dialog 1.

All of that convolutedness (yes, it's a word, or it is now anyway) had me really scratching my head over how to deal with all the scopes I was creating, not to mention all the new DOM elements I was creating.  I ended up writing a Directive (for the link), a Controller (for the rich controls on the Details and History Summary dialogs), a Service (to retrieve the markup to render, and the data to populate), and a Factory (to handle the scopes and actually opening the dialogs).  Yup, I'm pretty awesome.

First, the Directive:
myApp.directive('dialogLink', ['dialogFactory',
        function (dialogFactory) {
            return {
                // restrict this directive to be used as an element only
                restrict: 'E',
                require: '?ngModel',
                scope: {
                    "ngModel": "="
                },
                link: function (scope, element, attr) {
                    // watch the attribute so that once it populates, we render the value to the screen
                    scope.$watch(function () { return attr.displayValue; }, function (value) {
                        // only render the value if it's an actual value
                        if (value != undefined) {
                            scope.DisplayValue = value;
                        }
                    });
                    // bind the click event to open the dialog
                    element.bind("click", function (e) {
                        dialogFactory.open(attr);
                        
                        e.preventDefault();
                        e.stopPropagation();
                    });
                },
                template: "<a href='#'>{{DisplayValue}}</a>"
            };
        }
  ]
);

Use it like this:
<dialog-link display-value="{{SomeProperty}}"></dialog-link>

Now the Controller:
myApp.controller('dialogController', ['$scope', '$compile', 'dialogService', 'dialogFactory',
        function ($scope, $compile, dialogService, dialogFactory) {
            var historySummaryDialog;

            $scope.CloseHistorySummaryDialog = function() {
                historySummaryDialog.dialog('close');
            };

            $scope.CloseDialog = function () {
                dialogFactory.close($scope);
            };
            
            $scope.Initialize = function () {
                // get the detail data
                dialogService.getDetailData().then(function (data) {
                    $scope.Data = data;
                });
            };
            
            $scope.ShowHistorySummary = function () {
                var historyTitle = "History Summary Dialog";                
                dialogService.getHistorySummaryMarkup().then(function (markup) {
                    // compiles the current scope into the history summary dialog so the current scope ($scope) is used for the history summary dialog as well as the current dialog
                    historySummaryDialog = $compile(markup)($scope);
                    historySummaryDialog.dialog({
                        open: function() {
                            $(this).css({ 'max-height': dialogService.MaxDialogHeight, 'overflow-y': 'auto' });
                        },
                        width: dialogService.DialogWidth,
                        maxWidth: 1000,
                        height: 'auto',
                        fluid: true,
                        title: historyTitle,
                        modal: true,
                        closeOnEscape: true
                    });
                });
            };

            if (!$scope.PreventInit) {
                $scope.Initialize();
            }
        }
    ]
);

And the Service:
myApp.service('dialogService', ['$http', '$q', '$rootScope',
        function ($http, $q, $rootScope) {
            this.DialogWidth = $(window).width() * .8;
            this.MaxDialogHeight = $(window).height() * .8;
            
            this.getDetailData = function () {
                var deferred = $q.defer();

                $http({
                    url: "/GetDetailData",
                    method: "GET"
                })
                    .success(deferred.resolve)
                    .error(deferred.reject);

                return deferred.promise;
            };
            
            this.getDetailMarkup = function () {
                var deferred = $q.defer();

                $http({
                    url: "/Pages/Dialog/Detail.html",
                    method: "GET",
                    cache: true
                })
                    .success(deferred.resolve)
                    .error(deferred.reject);

                return deferred.promise;
            };
            
            this.getHistoricDetailData = function () {
                var deferred = $q.defer();

                $http({
                    url: "/GetHistoricDetailsData",
                    method: "GET"
                })
                    .success(deferred.resolve)
                    .error(deferred.reject);

                return deferred.promise;
            };
            
            this.getHistorySummaryMarkup = function () {
                var deferred = $q.defer();

                $http({
                    url: "/Pages/Dialog/HistorySummary.html",
                    method: "GET",
                    cache: true
                })
                    .success(deferred.resolve)
                    .error(deferred.reject);

                return deferred.promise;
            };
        }
    ]
);

And finally, the pièce de résistance, the Factory:
myApp.factory('dialogFactory', ['dialogService', '$compile', '$rootScope', '$controller', 
        function (dialogService, $compile, $rootScope, $controller) {
            return {
                open: function (attr) {
                    dialogService.getDetailMarkup().then(function (markup) {
                        var title;
                        // create a new scope manually (instead of allowing the ng-controller directive in the markup do it
                        var scope = $rootScope.$new();
                        // prevent the data from being retrieved twice
                        scope.PreventInit = true;

                        var dataPromise;
                        
                        if (attr.getHistory == undefined) {
                            dataPromise = dialogService.getDetailData();
                            title = "Details";
                        } else {
                            dataPromise = dialogService.getHistoricDetailData();
                            title = "History Details";
                        }
                        
                        dataPromise.then(function (data) {
                            scope.Data = data;

                            // instantiate a new instance of the controller
                            var controller = $controller('dialogController', { $scope: scope });
                            // bind the controller to the markup
                            $(markup).children().data('$ngControllerController', controller);
                            // compile the new scope against the markup
                            scope.Dialog = $compile(markup)(scope);
              
                            scope.Dialog.dialog({
                              width: dialogService.DialogWidth,
                              maxWidth: 1000,
                              height: 'auto',
                              fluid: true,
                              title: title,
                              modal: true,
                              closeOnEscape: true
                            });                            
                        });
                    });
                },
                close: function (scope) {
                    // destroy the jQuery UI dialog
                    scope.Dialog.dialog('destroy');
                    // remove the dynamically injected DOM element
                    $('[ng-controller="dialogController"]').each(function (index) {
                        var element = $('[ng-controller="dialogController"]')[index];
                        if (element != null && angular.element(element) != null && angular.element(element).scope().$id == scope.$id) {
                            element.remove();
                        }
                    });
                    // destroy the scope
                    scope.$destroy();
                }
            };
        }
    ]
);

There's so much fun going on here that I'm probably going to explain it all in another (or several) posts. I also forgot to mention earlier that the contents of the Details Dialog (aka Dialog 1) also have to be available outside of a dialog. That's where the Initialize function and PreventInit property come in to play. By specifying PreventInit when I create the scope myself in the factory, I can stop the data from being loaded during the Initialize function. But when the markup includes the ng-controller directive to create a new scope (for the standalone implementation of the Details Dialog) that property isn't set so the Initialize function gets called and data is retrieved. I'm very pleased with myself for figuring this one out. And it only took me a full day!

Debug Test

I upgraded to Visual Studio 2012 Ultimate a few weeks ago so I could generate a couple of UML diagrams from the code I had already written (yes, I know that's a backward process, leave me alone).  Anyway, after I upgraded I found that I couldn't build my solution anymore because of the test projects I had.  Everything worked fine before I upgrade, then didn't work fine after I upgraded.

It turns out the fix was pretty simple.  I found it here, and reproduced the answer in my blog in case for some reason Stack loses the answer.

"I was getting the same output after upgrading a test project from VS 2010 to VS 2012 Ultimate Update 3. The message was displayed in Test Output window after using MSTest command to Debug Selected Tests.
I tried to debug tests using Resharper 8 Unit Test Session window. The message in the result window was "Test wasn't run".
The solution that helped me was to modify the test project settings to enable native code debugging as instructed at this link: Uncaught exception thrown by method called through reflection
In case the link does not work:
  1. Go to the project right click and select properties.
  2. Select 'Debug' tab on the left.
  3. Go to ‘Enable Debuggers’ on the bottom
  4. Check ‘Enable Native code debugging’ (or 'Enable unmanaged code debugging', depends on version) check box
Thanks to GalDude33 for posting the solution."
And the answer I just copy/pasted was posted by Branko on October 3, 2013.

Tuesday, February 10, 2015

Angular Dialog

If you're using Angular you've probably come across a situation where you wanted to take some action on one scope from a different scope.  In the specific situation I was faced with today I had one scope (scope A) launching a jQuery UI dialog and the contents of the dialog contained a second scope (scope B).  My problem was that the dialog contents included a Close button that called a function that existed on scope B and I needed that event to also get called if the built-in close button (the X in the upper right-hand corner of the dialog) was used to close the dialog.  Eventually I realized that I also needed that event to get called if the user pressed the <Esc> key to close the dialog.

Although I'm sure there are lots of ways to do this, I ended up using jQuery to retrieve scope B and invoke the desired function.  Some sample code:

$scope.OpenDialogWithChildScope = function () {
    $("#dialogWithChildScope").dialog({
        width: 800,
        height: 750,
        title: "Child Scope Dialog",
        modal: true,
        closeOnEscape: true,
        open: function (event, ui) {
            // store the current object (before any changes) in a separate object
            repository.OriginalItem = angular.copy(repository.CurrentItem);
        },
        close: function () {
            // get the scope of the dialog
            var scope = angular.element("[ng-controller='childScopeController']").scope();
            // clear the errors from the dialog so it's clean the next time it opens
            scope.ClearDialogErrors();
            // check whether the contents of the dialog need to be refreshed
            // (because some change was made and that change should be reflected on scope A)
            if (repository.ShouldRefresh) {
                // refresh scope A
                repository.refresh().then(function(results) {
                    // set the item on scope A, reset the pre-changed object, reset the refresh variable
                    repository.CurrentItem = results;
                    repository.OriginalItem = null;
                    repository.ShouldRefresh = false;
                });
            } else {
                // if the contents don't need to be refreshed, replace the contents with the pre-changed object
                // in order to undo any changes that were made but not saved while the dialog was open
                repository.CurrentItem = angular.copy(repository.OriginalItem);
                repository.OriginalItem = null;
            }
        },
        position: {
            my: "center center",
            at: "center center",
            of: $("#body"),
            collision: "fit fit"
        }
    });
};

Thursday, February 5, 2015

Dating the XML Serializer

Something came up at work yesterday that proved to be a tough nut to crack: when you use the .NET XMLSerializer to serialize an object that contains a DateTime property, the serializer will convert it to an offset based on your time zone.  That means that DateTime.Now will end up getting serialized into something like 2015-02-05T08:23:31.0858835-07:00.  While that's not necessarily bad by itself, it may cause problems depending on why you're serializing the object in the first place.  We serialize our objects and pass the resulting XML as a parameter to a stored procedure in SQL Server.  Then we use SQL Server's built-in XQuery support to get the values out that we need.

The problem we ran into was that when SQL Server retrieves that DateTime value (which, remember, is now an offset), and you convert it to DATETIME in SQL it converts the value to UTC time, which probably isn't what you intended.  So the sample I used above would get converted to '2015-02-05 15:28:59.903'.  If you didn't intend to store a UTC date (which, if you did, you probably should have just set the UTC date in .NET) that value is going to be wrong.  There's a simple solution for this that took me a while to figure out.  Retrieve the value from the XML as a DATETIMEOFFSET, then convert the resulting value to a DATETIME.  Check out the sample code below.


   1:  public class SomeDateTimeContainingObject
   2:  {
   3:      public DateTime CreationDate { get; set; }
   4:   
   5:      public int Id { get; set; }
   6:   
   7:      public string AsXml(bool shouldRemoveNull = true)
   8:      {
   9:          var xmlResult = "";
  10:   
  11:          var settings = new XmlWriterSettings();
  12:          settings.Encoding = new UnicodeEncoding(false, false);
  13:          settings.Indent = true;
  14:          settings.OmitXmlDeclaration = true;
  15:          var xmlSerializer = new System.Xml.Serialization.XmlSerializer(this.GetType());
  16:          using (var stringWriter = new StringWriter())
  17:          {
  18:              using (var xmlWriter = XmlWriter.Create(stringWriter, settings))
  19:              {
  20:                  xmlSerializer.Serialize(xmlWriter, this);
  21:              }
  22:   
  23:              //Strip out namespace info
  24:              xmlResult = stringWriter.ToString().Replace("'", "''").Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", "").Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", ""); //This is the output as a string
  25:          }
  26:   
  27:          //Load the XML doc
  28:          var xdoc = new XmlDocument();
  29:          xdoc.LoadXml(xmlResult.Replace("xsi:nil", "nullable"));
  30:   
  31:          if (shouldRemoveNull)
  32:          {
  33:              //Remove all NULL values 
  34:              foreach (XmlNode node in xdoc.SelectNodes("//*[@nullable]"))
  35:              {
  36:                  node.ParentNode.RemoveChild(node);
  37:              }
  38:          }
  39:   
  40:          return xdoc.OuterXml;
  41:      }
  42:  }


var someObject = new SomeDateTimeContainingObject
{
    CreationDate = DateTime.Now,
    Id = 123456
};
 
var xml = someObject.AsXml();


DECLARE @XML XML =
'<SomeDateTimeContainingObject><CreationDate>2015-02-05T08:23:31.0858835-07:00</CreationDate><Id>123456</Id></SomeDateTimeContainingObject>'

-- Wrong result
SELECT id.node.value('(CreationDate/text())[1]', 'DATETIME')
FROM   @XML.nodes('SomeDateTimeContainingObject') id(node)

-- Right result
SELECT CONVERT(DATETIME, id.node.value('(CreationDate/text())[1]',
                         'DATETIMEOFFSET'))
FROM   @XML.nodes('SomeDateTimeContainingObject') id(node) 

Saturday, January 31, 2015

Visual Studio Add-Ins

On my current project we have an n-tier solution that combines Angular JS, MVC for ASP.NET, and Web API.  When you want to create a "full stack" (all the way from the Angular files to the data access layer) you have to create 10 separate files.  Because of the naming conventions we use, we end up naming the files very similarly (e.g. SomeController, SomeBusiness, SomeDataAcess).  My boss tasked me with coming up with a way for the development team to create a full stack with as few steps as possible.  I ended up creating an add-in for Visual Studio that accepts a namespace and a root name ("Some" in the previous example) along with a few boolean options.  When you press the button, 10 custom templates are imported and modified to match the naming conventions.

This process will significantly decrease development time and coding errors.  Since we use Unity for IoC and DI, the add-in also registers each interface created with its respective concrete class.  We've effectively been able to eliminate the "class does not have a default constructor" error, which was a huge bonus.

It turns out creating an add-in is pretty easy, but getting it to do anything complex is a bit trickier.  Below are some pointers on creating an add-in so hopefully I don't make the same mistakes next time.


   1:  (Solution2)_applicationObject.Solution 

That little guy right there will get you the Solution (which seems a bit obvious now).


   1:  private IEnumerable<Project> GetProjects(List<string> projectNames)
   2:          {
   3:              var projects = new List<Project>();
   4:   
   5:              // get the projects from the root (solution) object
   6:              var solutionProjects = _applicationObject.Solution.Projects;
   7:   
   8:              // get the enumerator from the projects from the root (solution) object
   9:              var enumerator = solutionProjects.GetEnumerator();
  10:              // iterate the projects
  11:              while (enumerator.MoveNext())
  12:              {
  13:                  var project = (Project)enumerator.Current;
  14:   
  15:                  if (project == null)
  16:                  {
  17:                      continue;
  18:                  }
  19:   
  20:                  // check whether the project is a solution folder
  21:                  if (project.Kind == ProjectKinds.vsProjectKindSolutionFolder)
  22:                  {
  23:                      // if the project is a solution folder, get the projects from the solution folder
  24:                      projects.AddRange(GetSolutionFolderProjects(project, projectNames));
  25:                  }
  26:                  else if (projectNames.Contains(project.Name))
  27:                  {
  28:                      // if the project isn't a solution folder, add it to the results
  29:                      projects.Add(project);
  30:                  }
  31:              }
  32:   
  33:              return projects;
  34:          }

That beauty will get the projects at the root of the solution.  You'll notice on line 24 I'm calling another method named GetSolutionFolderProjects.  That's used in case your solution uses solution folder for easier visible navigation (like ours does).  Here's that method:


   1:  private IEnumerable<Project> GetSolutionFolderProjects(Project solutionFolder, List<string> projectNames)
   2:          {
   3:              var list = new List<Project>();
   4:   
   5:              // iterate the project items in the solution folder (each project item should either be another solution folder or a project
   6:              for (var i = 1; i <= solutionFolder.ProjectItems.Count; i++)
   7:              {
   8:                  var subProject = solutionFolder.ProjectItems.Item(i).SubProject;
   9:                  if (subProject == null)
  10:                  {
  11:                      continue;
  12:                  }
  13:   
  14:                  // check whether the project is a solution folder
  15:                  if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
  16:                  {
  17:                      // if the project is a solution folder, get the projects from the solution folder
  18:                      list.AddRange(GetSolutionFolderProjects(subProject, projectNames));
  19:                  }
  20:                  else if (projectNames.Contains(subProject.Name))
  21:                  {
  22:                      // if the project isn't a solution folder, add it to the results
  23:                      list.Add(subProject);
  24:                  }
  25:              }
  26:   
  27:              return list;
  28:          }

You'll notice on line 18 I'm calling another method named GetSolutionFolderProjects... that's a little recursion joke.  If you didn't get it, remember this: in order to understand recursion, you must first understand recursion.

I'm going to post more about this later, but I've been meaning to write this post for over a month and just haven't made the time.  This is a start.

Friday, January 30, 2015

C# Reference Value

I often run into situations in C# where my variables don't behave as I expect them to.  For example, I pass an instance of a custom object into another method and the attributes of that instance are changed even though I didn't specify the ref keyword.  That's because C# has value and reference types and which one you get depends on what you're declaring.  This also affects where the memory is stored (the two concepts are tied together to a degree).

In short, when you're using an instance of a custom object, your resulting variable will be a reference type.  Every time.  I know this article is a bit outdated, but it explained it very clearly to me (for the first time) so I'm passing it along (and remember, this blog is all about keeping track of my own answers).

Tuesday, January 13, 2015

RDP With Multiple Monitors

I love working from home, but I hate that I lose my dual monitors when I have to remotely access my work computer.  Apparently, you can totally do that with Windows 7 and I just gave up after being unable to do it with Windows XP.  If you need to remotely access another computer and you want that RDP session to span multiple monitors (I only have two, so I'm not sure whether this works with more than two) you can launch the RDP application with the /span switch, like this: mstsc /span.