Monday, June 1, 2015

Custom Media Formatters in Web API

If you read my post on HATEOAS you may be wondering how to build a custom media formatter in WebAPI.  Although this example is straight from a school assignment, it worked out pretty well and I'll definitely be using it as the basis of any future work.  Part of the assignment was to develop a Domain Access Protocol specific to the assignment.  In this case, the content type of the DAP was to be along the lines of "application/vnd.class-name-assignment+xml".  Of course, WebAPI doesn't know what that type is or how to format the result unless you tell it, which is where the idea of a custom media formatter comes into play.

For starters, any custom media formatter we create will need to inherit from a MediaTypeFormatter base class.  I chose to inherit from BufferedMediaTypeFormatter because that's what I found first when I searched for it:

   1:  public class CustomXmlFormatter: BufferedMediaTypeFormatter

Once you do that, you'll need to add the media type to the SupportedMediaTypes list in the constructor:

   1:  public CustomXmlFormatter()
   2:  {
   3:      SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.class-name-assignment+xml"));
   4:  }

OK, great.  But all that's really done is set up the media type.  It doesn't tell the server when to use that media type.  This next part is going to go in the WebApiConfig file, in the Register method:

   1:  config.Formatters.Add(new CustomXmlFormatter());

At this point we're all set up to send and receive content from a client using the aforementioned "application/vnd.class-name-assignment+xml" content type.  As long as the request comes from the client with that content type, our new custom media formatter will be used to process it.  Unfortunately, nothing will happen with it at this point (actually, the service won't even compile yet because we haven't overridden a couple of important methods).  We have to override CanReadType and CanWriteType in order to read and write (respectively) the data as it comes in and goes out:

   1:  public override bool CanReadType(Type type)
   2:  {
   3:      return true;
   4:  }
   5:   
   6:  public override bool CanWriteType(Type type)
   7:  {
   8:      return true;
   9:  }

An important note about that code: it just passes everything right on through.  You may want to set those methods up so that it can only read or write based on certain types.  I didn't want to do that (and the assignment was coming due and I was short on time) so I just put in return true.

This is all great, but we still aren't actually doing anything here.  We have to override a couple more methods in order to actually process the data that comes and goes through this formatter.  Since it's shorter, I'll show you ReadFromStream first:

   1:  public override object ReadFromStream(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
   2:  {
   3:      var serializer = new XmlSerializer(type);
   4:      var val = serializer.Deserialize(readStream);
   5:      return val;
   6:  }

What we've said there is that anything that comes in should be deserialized using the XmlSerializer.  We're trusting that the input is properly formatted XML.  If it isn't, we'll throw an error.  As for the data on the way out, well, hopefully I commented it well enough to make sense:


   1:  public override void WriteToStream(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content)
   2:  {
   3:      // create a stream to work with
   4:      using (var writer = new StreamWriter(writeStream))
   5:      {
   6:          // check whether the object being written out is null
   7:          if (value == null)
   8:          {
   9:              throw new Exception("Cannot serialize type");
  10:          }
  11:   
  12:          // if the object isn't null, build the output as an XML string
  13:          var output = BuildSingleItemOutputAsXml(type, value);
  14:   
  15:          // write the XML string into the stream
  16:          writer.Write(output);
  17:      }
  18:  }
  19:   
  20:  private string BuildSingleItemOutputAsXml<T>(Type type, T viewModel)
  21:  {
  22:      // get the basic XML rendering of the object
  23:      var output = AsXml(type, viewModel);
  24:   
  25:      // strip off and store the closing tag
  26:      var closingNodeTag = output.Substring(output.LastIndexOf("</", StringComparison.InvariantCulture));
  27:      output = output.Substring(0, output.LastIndexOf("</", StringComparison.InvariantCulture));
  28:   
  29:      // use reflection to get the properties of the object
  30:      var properties = type.GetProperties();
  31:   
  32:      // iterate the properties of the object until the Links are found (this is related to the HATEOAS requirement)
  33:      // create a custom node for each link found in the Links property
  34:      output = (from property in properties
  35:                where property.PropertyType == typeof(List<LinkViewModel>)
  36:                select (List<LinkViewModel>)property.GetValue(viewModel, null)
  37:                    into links
  38:                    where links != null
  39:                    from link in links
  40:                    select link).Aggregate(output,
  41:                                       (current, link) =>
  42:                                       current +
  43:                                       string.Format("<link rel=\"{0}\" href=\"{1}\" />", link.Rel, link.Href));
  44:   
  45:      // append the closing tag back on the output
  46:      output += closingNodeTag;
  47:   
  48:      return output;
  49:  }
  50:   
  51:  private string AsXml<T>(Type type, T viewModel)
  52:  {
  53:      // Build an XML string representation of our object
  54:      string xmlResult;
  55:   
  56:      var settings = new XmlWriterSettings
  57:          {
  58:              Encoding = new UnicodeEncoding(false, false),
  59:              Indent = true,
  60:              OmitXmlDeclaration = true
  61:          };
  62:   
  63:      var xmlSerializer = new XmlSerializer(type);
  64:      using (var stringWriter = new StringWriter())
  65:      {
  66:          using (var xmlWriter = XmlWriter.Create(stringWriter, settings))
  67:          {
  68:              xmlSerializer.Serialize(xmlWriter, viewModel);
  69:          }
  70:   
  71:          //Strip out namespace info
  72:          xmlResult =
  73:              stringWriter.ToString()
  74:                          .Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", "")
  75:                          .Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "");
  76:              //This is the output as a string
  77:      }
  78:   
  79:      //Load the XML doc
  80:      var xdoc = new XmlDocument();
  81:      xdoc.LoadXml(xmlResult.Replace("xsi:nil", "nullable"));
  82:      //Remove all NULL values 
  83:      var xmlNodeList = xdoc.SelectNodes("//*[@nullable]");
  84:      if (xmlNodeList != null)
  85:          foreach (XmlNode node in xmlNodeList)
  86:          {
  87:              if (node.ParentNode != null)
  88:              {
  89:                  node.ParentNode.RemoveChild(node);
  90:              }
  91:          }
  92:   
  93:      return xdoc.OuterXml;
  94:  }

Everything up there ends up with one thing: a single XML string written into the stream that is being sent back to the client.  There's only one part left now, which is to specify in our responses when to use this new custom media formatter.  I use the CreateResponse extension of the HttpRequestMessage object to build my responses so this was a simple matter of specifying the content of the response like this:

   1:  response.Content = new ObjectContent(typeof(T), content, new CustomXmlFormatter(), "application/vnd.class-name-assignment+xml");


And that's all there is to it!

HATEOAS

HATEOAS: the much ignored tenet of REST services.  What does it mean?  What does it do?  How do you do it?  Those are the three questions I can hopefully answer in this post.

What does it mean?  This one is easy: Hypertext As The Engine Of Application State.

Great, so what does it actually mean?  It means you use links (yes, the same kind you put on a webpage in an anchor tag) to tell the client what it can do next.  Ergo, you use Hypertext (links) as the Engine of Application State (to tell the client what it can do).

The basic idea behind HATEOAS is that the server should be able to tell the client(s) what comes next in the process, based on the current state of the resource being returned.  Allow me to use an analogy.  Big shocker here, but I'm going with the old building a house analogy.

So let's say you're building a house.  Right from the beginning you have a resource that has a state.  For the sake of argument (and because I've never actually built a house), we'll say the resource is House and at the beginning of the process the State of House is Not Started (you can envision an empty lot if that helps).  When House has a State of Not Started, the Construction Crew (i.e. the client) may only take a few actions.  They can Change Plans, Lay Foundation, or Scrap It.  There's a ton of stuff that will come later, but right away they can't do those things (like Frame).

Once the Construction Crew chooses an action from the three available, the State of House changes.  So let's say the Construction Crew chooses Lay Foundation.  The State of House is now Foundation Laid and there are new actions that can be taken: Change Plans, Frame, Landscape, or Scrap It.  You'll notice (hopefully) that they have two of the same options.  When House is in Foundation Laid or Not Started, the actions Change Plans and Scrap It are both available.

As you can see the State of House dictates what Construction Crew can do next.  If House is the resource returned from the server, it's pretty easy to see that the server gets to dictate what steps are available for the client to take.  If the server decides that new steps should be available at different steps the service can be modified to include those links in the response.  Going back to the House example, if the service decided that Install Basketball Hoop should be available when the State of House is Foundation Laid, they can do that.  The client (Construction Crew) can then choose to do that or not.  See, the server isn't telling the client what to do next, only what they can do next.

So that should answer "What does it mean" and "What does it do", but the really important question is "How do you do it".  Again, there's a short answer and a long answer.  The short answer is that you return a links property in your response that contains the actions the client can take.  The longer answer involves code.  Keep in mind that this is just how I've done it one time so it's not The Way or anything, just a suggestion.

It's pretty easy to do the JSON version of this because serializing a class to a JSON object using JSONConvert is really simple. What I do is create a base class for every class that will be returned to a client, usually named something obvious like BaseViewModel:
   1:  public class BaseViewModel
   2:  {
   3:      [XmlIgnore]
   4:      public virtual List<LinkViewModel> Links
   5:      {
   6:          get { return new List<LinkViewModel>(); }
   7:      }
   8:  }

And the LinKViewModel is pretty basic as well:
   1:  [XmlRoot("link")]
   2:  public class LinkViewModel
   3:  {
   4:      [XmlAttribute("rel")]
   5:      public string Rel { get; set; }
   6:   
   7:      [XmlAttribute("href")]
   8:      public string Href { get; set; }
   9:  }

Once we have that, it's a single line to render the output into a usable JSON result:
   1:  JsonConvert.SerializeObject(value);

All of that leads to a result that looks like this:
{"Comments":"Happy","Id":6,"Links":[{"Rel":"view","Href":"/api/grade/get/6"},{"Rel":"update","Href":"/api/grade/update"},{"Rel":"appeal","Href":"/api/appeal/add"}],"State":0,"StudentId":123,"Value":0.0}
With that result I can use a little client-side code (in this case it's pure JavaScript) to get the links from the response and see what I can do:
   1:  function getLink(object, name) {
   2:      var links = getLinks(object);
   3:      for (var i = links.length; --i >= 0;) {
   4:          if (links[i].rel != null && links[i].rel == name) {
   5:              return links[i].href;
   6:          } else if (links[i].Rel != null && links[i].Rel == name) {
   7:              return links[i].Href;
   8:          }
   9:      }
  10:   
  11:      return null;
  12:  }
  13:          
  14:  function getLinks(object) {
  15:      var links = [];
  16:      if (object != null && object.link != null && object.link.constructor === Array) {
  17:          for (var i = object.link.length; --i >= 0;) {
  18:              links.push(object.link[i]);
  19:          }
  20:      } else if (object != null && object.link != null) {
  21:          links.push(object.link);
  22:      } else if (object != null && object.Links != null && object.Links.constructor === Array) {
  23:          for (var j = object.Links.length; --j >= 0;) {
  24:              links.push(object.Links[j]);
  25:          }
  26:      }
  27:      else if (object != null && object.Links != null) {
  28:          links.push(object.Links);
  29:      }
  30:   
  31:      return links;
  32:  }

So there you go. HATEOAS and how to do it.

Wednesday, May 27, 2015

Azure Makes Me Feel Blue

A few months ago I took a one day crash course on Microsoft Azure from Microsoft.  This particular course was tailored to the small and medium business implementations of Azure.  After an entire day playing in Azure and learning about everything it can do I still don't see why most places would invest in it, specifically small and medium businesses.

There are certainly situations in which Azure would be a great tool to use, but I don't see those being applicable to small and medium businesses.  For example, one of the really cool features of Azure is the ability to remotely host Active Directory.  That's really neat if you have multiple sites and/or remote employees.  But... if you have one office and everyone is on site (which, and I'm just guessing here, is probably most small and medium businesses) you won't really gain anything from that.

I couldn't help scratching my head as I left the Microsoft office and wondering what makes Azure so much better than any other co-located solution.  Maybe I'm missing something, but from where I'm sitting I just don't see it.

SQL Server Isolation Level

I haven't figured out why this happened yet (and I may not ever), but I wanted to put it up here so that I remember that it did happen.

We had a stored procedure that was used to populate a report.  Our report server was sporadically throwing out a timeout error when the report was retrieved so one of the SQL Server developers modified the isolation level to read uncommitted data, like this:
   1:  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

A few weeks later we encountered a problem which, after a bunch of digging and trial and error, turned out to be caused by that change.  Apparently, reading uncommitted data caused the stored procedure to also ignore the clustered index on the table from which it was reading.

I'm guessing this is documented somewhere, but since I spend most of my time on the front-end and middle-tier I'm not sure I'll ever devote the time to figuring this one out.  To fix our problem we just added another field to the order by clause to get the data we wanted.

Tuesday, May 26, 2015

Featured Directive

I'm trying to learn more about using Bootstrap within a .NET MVC application that uses Angular JS.  I figured I'd start easy on myself and try to just change the default everything to use Angular instead of Razor.  That's going pretty well, but one of the issues I came up against was the sections feature of the Razor syntax.  Basically, creating a section in a layout allows you to implement that section within a view that uses the layout and whatever you implement in that section in the "child" view will render wherever that section is rendered in the layout.  Here's the default layout and a default view:

   1:  <!DOCTYPE html>
   2:  <html lang="en">
   3:      <head>
   4:          <meta charset="utf-8" />
   5:          <title>@ViewBag.Title - My ASP.NET MVC Application</title>
   6:          <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
   7:          <meta name="viewport" content="width=device-width" />
   8:          @Styles.Render("~/Content/css")
   9:          @Scripts.Render("~/bundles/modernizr")
  10:      </head>
  11:      <body>
  12:          <header>
  13:              <div class="content-wrapper">
  14:                  <div class="float-left">
  15:                      <p class="site-title">@Html.ActionLink("your logo here", "Index", "Home")</p>
  16:                  </div>
  17:                  <div class="float-right">
  18:                      <section id="login">
  19:                          @Html.Partial("_LoginPartial")
  20:                      </section>
  21:                      <nav>
  22:                          <ul id="menu">
  23:                              <li>@Html.ActionLink("Home", "Index", "Home")</li>
  24:                              <li>@Html.ActionLink("About", "About", "Home")</li>
  25:                              <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
  26:                          </ul>
  27:                      </nav>
  28:                  </div>
  29:              </div>
  30:          </header>
  31:          <div id="body">
  32:              @RenderSection("featured", required: false)
  33:              <section class="content-wrapper main-content clear-fix">
  34:                  @RenderBody()
  35:              </section>
  36:          </div>
  37:          <footer>
  38:              <div class="content-wrapper">
  39:                  <div class="float-left">
  40:                      <p>&copy; @DateTime.Now.Year - My ASP.NET MVC Application</p>
  41:                  </div>
  42:              </div>
  43:          </footer>
  44:   
  45:          @Scripts.Render("~/bundles/jquery")
  46:          @RenderSection("scripts", required: false)
  47:      </body>
  48:  </html>


   1:  @{
   2:      ViewBag.Title = "Home Page";
   3:  }
   4:  @section featured {
   5:      <section class="featured">
   6:          <div class="content-wrapper">
   7:              <hgroup class="title">
   8:                  <h1>@ViewBag.Title.</h1>
   9:                  <h2>@ViewBag.Message</h2>
  10:              </hgroup>
  11:              <p>
  12:                  To learn more about ASP.NET MVC visit
  13:                  <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
  14:                  The page features <mark>videos, tutorials, and samples</mark> to help you get the most from ASP.NET MVC.
  15:                  If you have any questions about ASP.NET MVC visit
  16:                  <a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Forum">our forums</a>.
  17:              </p>
  18:          </div>
  19:      </section>
  20:  }
  21:  <h3>We suggest the following:</h3>
  22:  <ol class="round">
  23:      <li class="one">
  24:          <h5>Getting Started</h5>
  25:          ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that
  26:          enables a clean separation of concerns and that gives you full control over markup
  27:          for enjoyable, agile development. ASP.NET MVC includes many features that enable
  28:          fast, TDD-friendly development for creating sophisticated applications that use
  29:          the latest web standards.
  30:          <a href="http://go.microsoft.com/fwlink/?LinkId=245151">Learn more…</a>
  31:      </li>
  32:   
  33:      <li class="two">
  34:          <h5>Add NuGet packages and jump-start your coding</h5>
  35:          NuGet makes it easy to install and update free libraries and tools.
  36:          <a href="http://go.microsoft.com/fwlink/?LinkId=245153">Learn more…</a>
  37:      </li>
  38:   
  39:      <li class="three">
  40:          <h5>Find Web Hosting</h5>
  41:          You can easily find a web hosting company that offers the right mix of features
  42:          and price for your applications.
  43:          <a href="http://go.microsoft.com/fwlink/?LinkId=245157">Learn more…</a>
  44:      </li>
  45:  </ol>

What's going to happen is that when everything renders, the stuff between lines 4 and 20 in the second part of that code up there is going to render on line 32 of the first part of that code up there.  I wanted to replicate that in Angular so I wrote a directive for it.  There are probably lots of other (and possibly better) ways to do this, but this was my first shot and I didn't want to lose that progress.

The directive:
   1:  learnBootstrap.directive('featuredSection', ['$http', '$compile',
   2:      function ($http, $compile) {
   3:          'use strict';
   4:   
   5:          return {
   6:              restrict: 'E',
   7:              replace: true,
   8:              link: function (scope, el, attr, ctrl) {
   9:                  if (attr.templateurl != null) {
  10:                      $http({
  11:                          url: attr.templateurl,
  12:                          method: "GET",
  13:                          cache: true
  14:                      }).then(function (markup) {
  15:                          var compiledTemplate = angular.element($compile(markup.data)(scope));
  16:                          var placeholder = angular.element(document.querySelector("#body"));
  17:                          placeholder.prepend(compiledTemplate);
  18:                      });
  19:   
  20:                      scope.$on("$destroy", function () {
  21:                          var section = angular.element(document.querySelector(".featured"));
  22:                          section.remove();
  23:                      });
  24:                  }
  25:              }
  26:          };
  27:      }
  28:  ]);

The markup to implement that directive:
   1:  <featured-section templateUrl="/Features/Home/Featured.html"></featured-section>

The template the directive is going to retrieve on line 10:
   1:  <section class="featured">
   2:      <div class="content-wrapper">
   3:          <hgroup class="title">
   4:              <h1>{{Title}}</h1>
   5:              <h2>{{Message}}</h2>
   6:          </hgroup>
   7:          <p>
   8:              To learn more about ASP.NET MVC visit
   9:              <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
  10:              The page features <mark>videos, tutorials, and samples</mark> to help you get the most from ASP.NET MVC.
  11:              If you have any questions about ASP.NET MVC visit
  12:              <a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Forum">our forums</a>.
  13:          </p>
  14:      </div>
  15:  </section>

Friday, May 15, 2015

Sharing Session in IIS

Forget about why I had to do this and just accept that it was necessary.  I needed to share session between two completely separate applications in IIS.  In my particular setup, one application was a "child" of the other.

After quite a bit of searching and trial and error, I found a pretty simple solution that worked perfectly in my scenario.  This doesn't mean it'll work for everyone, but it definitely did for me.

We had previously configured session to use a SQL database, which I've found to be a pretty common scenario.  In each connection string, I added an ApplicationName piece and specified the same application name in both applications (e.g. ApplicationName = 'WhateverApp').

The next part made me feel a little icky, but it worked.  In the SQL Server ASPState database I modified the TempGetAppID stored procedure to
use [ASPState]
GO
SET ansi_nulls ON
GO
SET quoted_identifier OFF
GO

ALTER PROCEDURE [dbo].[TempGetAppID] 
(
     @appName tAppName
    ,@appId INT OUTPUT
)
AS
BEGIN

    -- Use the application name specified in the connection for the appname if specified
    -- This allows us to share session between sites just by making sure they have the
    -- the same application name in the connection string.

    DECLARE @connectionStringApplicationName NVARCHAR(50)
    SET @connectionStringApplicationName = App_name()

    IF @connectionStringApplicationName = 'WhateverApp'
    BEGIN
        SET @appName = @connectionStringApplicationName
    END

    SET @appName = Lower(@appName)
    SET @appId = NULL

    SELECT @appId = AppId
    FROM [ASPState].dbo.ASPStateTempApplications
    WHERE AppName = @appName

    IF @appId IS NULL
    BEGIN

        BEGIN TRAN

        SELECT @appId = AppId
        FROM [ASPState].dbo.ASPStateTempApplications WITH (tablockx)
        WHERE AppName = @appName

        IF @appId IS NULL
        BEGIN
            EXEC GetHashCode @appName@appId output

            INSERT [ASPState].dbo.ASPStateTempApplications
            VALUES (@appId,@appName)

            IF @@ERROR = 2627
            BEGIN
                DECLARE @dupApp tAppName

                SELECT @dupApp = Rtrim(AppName)
                FROM [ASPState].dbo.ASPStateTempApplications
                WHERE AppId = @appId

                RAISERROR( 'SQL session state fatal error: hash code collision between applications ''%s'' and ''%s''.
                    Please rename the 1st application to resolve the problem.'

                    ,18,1,@appName,@dupApp)
            END
        END

        COMMIT
    END

    RETURN 0
END 

This relatively small change allows us to detect whether an application name was passed in the connection string.  If it was (which it would be in both of our applications) then we set the @appName variable to be the name that was passed in the connection string instead of whatever it was going to use originally (for sub-applications, this can look like /lm/w3svc/1/root).  Since both connection strings pass the same application name, they're now sharing session.

Using MOQ to Test Whether a Method Was Called

I use MOQ for my server-side unit testing.  It's pretty easy to get started with and it's always done everything I needed it to do... until recently.

I needed to test whether a method was called with a specific set of parameters.  The result wasn't really important (because the method being called was mocked to do nothing anyway) and the method didn't have a return type so there was no way to validate the results of the method call.  I really just needed to know whether the method was called at all.  It turns out MOQ can do that, too.

The method that needs to be tested:
   1:  public void MethodUnderTest(CustomComplexObject customComplexObject)
   2:  {
   3:      var someLocalVariable = false;
   4:      
   5:      MethodThatShouldBeCalled(customComplexObject.Id, customComplexObject.UserId, someLocalVariable);
   6:      
   7:      /*snip*/
   8:  }

The method that should be called by the method that needs to be tested (this method must be virtual):
   1:  public virtual void MethodThatShouldBeCalled(int id, int userId, bool shouldDoSomethingSpecial)
   2:  {
   3:      if (shouldDoSomethingSpecial)
   4:      {
   5:          /*snip*/
   6:      }
   7:      else
   8:      {
   9:          /*snip*/
  10:      }
  11:  }

The test (an important note is the usage of "CallBase = true"; this won't work unless you include that):
   1:  [TestMethod]
   2:  public void MethodUnderTestShouldCallMethodThatShouldBeCalled()
   3:  {
   4:      // arrange
   5:      // create a new mocked BusinessManager
   6:      // note that this is mocking a concrete object and not an interface
   7:      var moqBusinessManager = new Mock<BusinessManager>{CallBase = true};
   8:      
   9:      // create a test object to pass to MethodUnderTest
  10:      var customComplexObject = new CustomComplexObject
  11:      {
  12:          Id = 123456789,
  13:          UserId = 987654321
  14:      };
  15:      
  16:      // setup the mocked BusinessManager so that MOQ knows what it should keep track of
  17:      moqBusinessManager.Setup(p => p.MethodThatShouldBeCalled(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<bool>()))
  18:          .Verifiable("MethodThatShouldBeCalled was not called");
  19:      
  20:      // act
  21:      // execute MethodUnderTest with the test object
  22:      // note the user of .Object
  23:      moqBusinessManager.Object.MethodUnderTest(customComplexObject);
  24:      
  25:      // assert
  26:      // use MOQ to verify that MethodThatShouldBeCalled was called with the expected parameters
  27:      moqBusinessManager.Verify(p => p.MethodThatShouldBeCalled(123456789, 987654321, false));
  28:  }