Azure, SignalR, & Redis File Validation Site

Summary

This project was to develop a site to validate files. The user will drop a .csv file on the page (or click the Browse button to find a file). After the file is uploaded, it will be analyzed to determine if it matches the expected format.

This was an Azure project from the beginning, which made it fun. I was also able to use SignalR to provide immediate feedback to the user as the file was being analyzed. I use Redis on the server to store the results for the last 7 days. Purging old results is automatic, since Redis can expire a key after a set number of days.

The site uses FluentValidation for the many rules to check the file. This tool made some pretty complex validation much easier. It also helped eliminate what would have been a bunch of repetitious code.

Screens

Main Screen

When the user loads the site, this is the first screen they see. They can either drop a file on this page or click the browse button for a file picker dialog.
The User textbox and associated button was a temporary way of grouping SignalR messages. The name really should have been changed to Group instead of user. The idea is that everybody listening in Group “Fred” would receive the same messages. This is important because of the next screen.

150722_Clean

Receiving File Screen

As the file is being analyzed, results are being sent back to the browser. The top 5 errors encountered are entered in the green list (not sure why I made it green). The gauge animates to show the number of lines processed.
150722_ErrorsGreen

Upload Complete Screen

When the analysis is complete, the user will either see a green check mark indicating the file is good, or they will see the green check mark with the red “not” circle indicating there were problems. A DataTables grid appears at the bottom of the page where they can view all the errors (using paging).

150722_Failure

History List Screen

The user can view the history for the last 7 days. This screen had as much value to me as the developer as it did to the user. Before I added this screen, I would get an email saying some file didn’t validate. The email was not very descriptive as to why it didn’t validate and I was left searching log messages to figure out the problem. This screen allowed me to easily see why a file failed validation. I added a date picker to select the day and view the results.
150722_HistoryList

History Detail Screen

Once a file is clicked from the History List Screen, this screen displays the detailed error messages for that file.
150722_HistoryGrid

Code

Code Coverage

I know how rare it is to get 97% code coverage. I have been on projects where we were happy with 75-80% (and many where we had none). This was really helpful when we needed to do a version 1.1 of this project. I made the required changes, ran my tests. Some of them were broke, as they should have been, based on the new requirements. I fixed those tests and added a few more.
I highlighted the catch block above, since that is the code that is hardest for me to get code coverage on. I know many people would eliminate the try/catch and let it bubble up. That would “fix” the code coverage issue, but I am not really sure that is a fix. In my experience, I like to catch the error as close to where it happened as possible. Often, I can add parameters that were passed to the method in the error handler so I know what value it was working with when the error occurred.

CodeCover_141027

Code Execution Time

We all agree that our unit tests should be fast so they can be run often. However, this was another one of those rare cases that I don’t see on most projects. It ran all 130 tests in under 2 seconds.
TestsPassed_141002_1130

Technology / Tools

  • C#
  • SignalR
  • Redis
  • Azure
  • FluentValidation
  • AngularJS
  • DataTables
  • DropZone

NoSql, YesSql, MaybeSql – A hybrid approach

UPDATE: I don’t usually claim something as an original idea. Anything formed in my brain is just an amalgamation of the videos I have watched and the stuff I have read. For this idea of just creating a JSON field in each table, I don’t remember seeing it anywhere else before. That is why I thought it was neat when I was perusing the Octopus tables today, and I noticed they also include a JSON field in each table. They even went as far as to make theirs Not Null, which I didn’t do.
OctopusJson

Others can argue when to use Sql vs a NoSql solution. There is a time and place for each. I have went down both paths and there usually comes a time where the grass looks a little greener on the other side. Sql is great for creating related tables and helping to ensure referential integrity at the database level. However, it can be a little cumbersome to add one simple field to an existing table. Even if you are doing migrations from code, there is some work involved.

On the other hand, NoSql is great at handling this “jagged” data. You simple add new properties to your json object and store it. However, the problem I have had with NoSql in the past is that you have to put more thought into your data design up front to know how you will want to pull it out. Often, ad hoc queries require a full table scan. Some of these things may no longer be true, but who can keep up with how quickly things change around us?

Anyway, in some recent work, I stumbled across an approach that I think combines the benefits of both approaches. Others have probably been doing some version of this for years, but it just clicked for me the other day. The “trick” is to create a VARCHAR(max) field where we can store a json object. This gives me most of the relational goodness of Sql along with the ability to easily add “minor” fields, without touching the database. This will not work for “major” fields that are a key to another table or something that is going to be searched often. However, for something like adding a link to store the gravatar on a user record, this can be pretty sweet.

Finally, some code. Here is the script I use to create a table. The key is line 21, where I add my JsonData field. Any table that might need “extended” in the future gets this new field.

150721_SqlPerson

Here is the C# code for the Person object.

public class Person : EntityBase
{
    public long PersonId { get; set; }
    public long TenantId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person_NoSql NoSql { get; set; }

    public Person()
    {
        NoSql = new Person_NoSql();
    }

    public string JsonData
    {
        get { return JsonConvert.SerializeObject(NoSql); }
        set { NoSql = JsonConvert.DeserializeObject<Person_NoSql>(value); }
    }

    public class Person_NoSql
    {
        public string UrlAvatar { get; set; }
        public string AnyOtherField { get; set;}
    }
}

The code to use the new property is pretty simple.

Person person = mgr.GetPerson(personId);

if (!ReferenceEquals(null, person))
{                       
    FirstName = person.FirstName;
    LastName = person.LastName;

    UrlAvatar = person.NoSql.UrlAvatar;
    AnyOtherField = person.NoSql.AnyOtherField;
}

I am using Dapper for the data layer. When it sees a JsonData field returned from a query, it maps it to the JsonData string property on the object. As far as Dapper knows, this is just another string field.

That is pretty much it. I thought this would be a longer post, but in the words of the great “Forrest Gump”, That’s all I have to say about that

Sql Server Stored Procedure Template

Summary

Have you ever written that “perfect” piece of code to handle some situation? You spend hours reading blogs and other code, and you finally pull it all together in a version that works in your project. In your mind, you know that since you just spent a few hours on this, there is no way you are going to forget it. In a future project when you will need this same code, you will just “remember” it was in xyz project. Well, for me, I start out that way, but I soon realize that I can’t remember where I did it. To my credit, I usually at least remember that I did it, I just don’t know where. While I love BareGrep as much as anybody, it is a bit like Google. If you don’t enter the right search terms, you still don’t find it.

So this post (and future posts like it) are an attempt to put that code in “one place” where I hope to be able to find it down the road. If the chunk of code is not worth a blog post, then it must not have been that impressive after all. 🙂

This morning, I was trying to to implement error handling in my catch block. I found a chunk of code I had used in the past, and it was rolling back transactions, which reminded me that I had forgot to put that in the current sproc I was working on. At that point, I started this post. Below are some notes that should be helpful to me in the future. I hope they will benefit you as well.

Header Comment

Include some kind of comment. I include a PRINT statement, which helps in the SqlCmd (future post) script that calls this. This also includes the DROP/CREATE approach which I like to do for sprocs.

/*
  Revision History
    4Jul15  ScottB  Initial Version
*/
PRINT 'ProceduresCall_spt.sql'

SET ANSI_NULLS ON
GO

SET ANSI_NULLS ON
GO

-- http://technet.microsoft.com/en-us/library/ms190324(v=sql.100).aspx
IF (OBJECT_ID(N'call.Call_spt', 'P') IS NOT NULL ) BEGIN
   DROP PROCEDURE call.Call_spt
   PRINT '   Dropped';
END
GO

SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE call.Call_spt
	@xml XML
AS
...

top

Initial Variables

Declare a couple variables at the very beginning that will be needed later. The @tranCount is needed to be able to commit and rollback transactions. The @tenantId is needed in the Error handler, so we set it outside the BEGIN TRY (not sure if this is necessary in TSQL, but it would be in C#).

BEGIN
	SET NOCOUNT ON;

	DECLARE @tranCount INT = @@TRANCOUNT;
	-- Default to System, but it should get reset below.
    DECLARE @tenantId INT = 1; 

top

BEGIN TRANSACTION ?? / COMMIT

As close as possible to your actual Insert or Update, you need a block like this. It handles the situation where we were already in a transaction (possibly from a calling sproc).

IF (@tranCount = 0) BEGIN
   BEGIN TRANSACTION
END ELSE BEGIN
   SAVE TRANSACTION Call_spt
END

-- Insert or Update ...

IF (@tranCount = 0) BEGIN
   COMMIT TRANSACTION 
END

top

Catch Block

This next block is assuming that the prior code was in a TRY CATCH block.

BEGIN CATCH

    DECLARE @xstate INT = XACT_STATE();
    IF @xstate = -1 BEGIN
       ROLLBACK TRANSACTION
    END
    IF @xstate = 1 AND @tranCount = 0 BEGIN
       ROLLBACK TRANSACTION
    END
    IF @xstate = 1 AND @tranCount > 0 BEGIN
       ROLLBACK TRANSACTION Call_spt
    END

    PRINT 'Error ' + CONVERT(varchar(50), ERROR_NUMBER()) +
          ', Severity ' + CONVERT(varchar(5), ERROR_SEVERITY()) +
          ', State ' + CONVERT(varchar(5), ERROR_STATE()) + 
          ', Procedure ' + ISNULL(ERROR_PROCEDURE(), '-') + 
          ', Line ' + CONVERT(varchar(5), ERROR_LINE());
    PRINT ERROR_MESSAGE();        

    EXEC tenant.ReThrowError_spt @tenantId

    RETURN -1;
END CATCH	

top

Footer for all pages on site

Summary


During the testing portion of a recent server move, I wanted to be sure the user knew they were on the test server. It seemed simple enough, and the solution I ended up with was not difficult.

There was one “critical” piece that probably cost me an extra couple hours, and it turned out to be one of those “one liners”. It is the second line in the snippet below (urlCompression…)

Example


I realize the orange has a certain “Home Depot” look to it, but I wanted it to be clear for the user. I even got to use a marquee for the first time in more than 10 years (they were hot, back in the day).

This article is a step-by-step with screen shots, but I was getting an Error 500 when I enable the rule.

I added this to the bottom of the web.confg files.

    &lt;system.webServer&gt;
     &lt;urlCompression doDynamicCompression="false" /&gt;
        &lt;rewrite&gt;
            &lt;outboundRules&gt;
                &lt;rule name="AddPageFooter" preCondition="IsHtml" enabled="true"&gt;
                    &lt;match filterByTags="None" pattern="&amp;lt;/body&gt;" /&gt;
                    &lt;action type="Rewrite" value="&amp;lt;marquee loop=&amp;quot;1&amp;quot; behavior=&amp;quot;alternate&amp;quot; style='background-color: #ff5500; color: white; width=100% text-align: center; font-size: 14pt; padding: 5px;'&amp;gt;Test Site (not Home Depot)&amp;lt;/marquee&amp;gt;" /&gt;
                &lt;/rule&gt;
                &lt;preConditions&gt;
                    &lt;preCondition name="IsHtml"&gt;
                        &lt;add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" /&gt;
                    &lt;/preCondition&gt;
                &lt;/preConditions&gt;
                &lt;customTags&gt;
                    &lt;tags name="body" /&gt;
                &lt;/customTags&gt;
            &lt;/outboundRules&gt;
        &lt;/rewrite&gt;
    &lt;/system.webServer&gt;

Here is the html I used for the message before I encoded it (using the
string encoder at http://www.whatsmyip.org)
I believe the “text-align” was a holdover from where I started with a div. It should probably come out, but this is already deployed, so I will leave it.


&lt;marquee loop="1" behavior="alternate" style='background-color: #ff5500;
color: white; width=100% text-align: center; font-size: 14pt;
padding: 5px;'&gt;Test Site (not Home Depot)&lt;/marquee&gt;

Example
Test Site (not Home Depot)

Trello Interface for Daily Printed ToDo Page

Summary


This project had two major goals. First, I wanted to filter Trello cards based on tags.
If you are not familiar with Trello, it is described as

.. the fastest, easiest way to organize anything, from your day-to-day work, to a favorite side project, to your greatest life plans.

It is an easy way to organize information into lists, which is how my mind works. This video gives a nice introduction.

One current drawback with Trello is the lack of support for Tags. They do provide 6 different color coded labels. This is nice, but just not enough. Although I am not a perfect follower of the David Allen method of Getting Things Done, I do try to organize my tasks using his methods.

In addition to adding a “little GTD” to Trello, I also wanted to output a page that was formatted for a printer. The idea is that I would print the page each morning and carry it with me throughout the day. I would mark it up, add notes and new tasks. I would draw a line through completed tasks and even have a place to Doodle during meetings. Then, at the end of the day or the next morning before I print a new page, I would update Trello with the notes from the paper.

Description

One issue I am sure the Trello dev team has encountered with tags is the fact that cards are usually viewed by more than one user. Each user wants “their own” tags that makes sense to them. However, as a team, users may want a common set of tags.

Here is my original list of tags:

  • +call = Calls that need to be placed
  • +dead = Brain dead tasks for when we can’t focus.
  • +dash = Things that can be done in under 15 minutes
  • +email = Emails that need to be sent
  • +errand = Things we need to do/get while out
  • +focus = Requires dedicated time and concentration
  • +paper = Tasks that only require a pen & paper (Brainstorming)

The idea is that I would just enter “+call” anywhere in the description and then I could filter all the cards that contained that tag. This works for a single user, but one of the huge benefits of Trello is that other people are viewing and editing those same cards.

The Kludge I came up with to solve this issue was to “prefix” the tag with the user initials. Now, my tag list became:

  • +jsb-call = Calls that need to be placed
  • +jsb-email = Emails that need to be sent
  • +jsb-errand = Things we need to do/get while out
  • +jsb-focus = Requires dedicated time and concentration

(I dropped a few out that I was no longer using)
I add these tags in the card description. Now, I just needed an easy way to filter the cards. This is where the Trello REST API steps up.

Proof of Concept

For my POC, I created a Winforms applicaiton. Here is a screen capture:

Yes, I went a little crazy with the colors, but I never claimed to be a designer (I can’t even match a pair of socks). If it helps any, remember that the goal is for the page to be printed. That is the reason for the underscores before each task. The calendars don’t really do anything on the web page, but are just there as a reference on the printed page. I also included the tag list as a reminder on the page.
top

Page Template

I created an html template for the page. The body of the template is shown below. The CSS is also included in the page, but I left it out for this example.

&lt;body&gt;
    &lt;div&gt;
        &lt;div style="float: right;"&gt;
            &lt;div style="text-align: right;"&gt;
                &lt;table&gt;
                    &lt;tr valign="top"&gt;
                        &lt;td&gt;
                            &lt;span id="datePrior" style="font-size: 7px;"&gt;&lt;/span&gt;
                        &lt;/td&gt;
                        &lt;td&gt;
                            &lt;span id="dateNext" style="font-size: 7px;"&gt;&lt;/span&gt;
                        &lt;/td&gt;
                    &lt;/tr&gt;
                &lt;/table&gt;
                &lt;table&gt;
                    &lt;tr valign="top"&gt;
                        &lt;td&gt;
                            &lt;span id="dateCurrent" style="font-size: 10px;"&gt;&lt;/span&gt;
                        &lt;/td&gt;
                        &lt;td&gt;
                            &lt;div style="text-align: left;"&gt;
                                &lt;span class="TagCaption"&gt;Tags&lt;/span&gt;&lt;br /&gt;
                                &lt;span class="TagList"&gt;+{{TagPrefix}}-call&lt;/span&gt;&lt;br /&gt;
                                &lt;span class="TagList"&gt;+{{TagPrefix}}-email&lt;/span&gt;&lt;br /&gt;
                                &lt;span class="TagList"&gt;+{{TagPrefix}}-errand&lt;/span&gt;&lt;br /&gt;
                                &lt;span class="TagList"&gt;+{{TagPrefix}}-focus&lt;/span&gt;&lt;br /&gt;
                                &lt;span class="TagList"&gt;+{{TagPrefix}}-next&lt;/span&gt;&lt;br /&gt;
                                &lt;span class="TagList"&gt;+{{TagPrefix}}-waiting&lt;/span&gt;&lt;br /&gt;
                            &lt;/div&gt;
                        &lt;/td&gt;
                    &lt;/tr&gt;
                &lt;/table&gt;
            &lt;/div&gt;
            &lt;h2 class="CaptionCall"&gt;Appointments / Calls&lt;/h2&gt;
            {{call}}
            &lt;h2 class="CaptionAssigned"&gt;Assigned To Me&lt;/h2&gt;
            {{@me}}
        &lt;/div&gt;
        &lt;div style="float: left;"&gt;
            &lt;h1&gt;{{Date}}&lt;span class="Time"&gt;{{Time}}&lt;/span&gt;&lt;/h1&gt;
            &lt;h2 class="CaptionFocus"&gt;Focus&lt;/h2&gt;
            {{focus}}
            &lt;h2 class="CaptionNext"&gt;Next Actions&lt;/h2&gt;
            {{next}}
            &lt;h2 class="CaptionNote"&gt;Notes&lt;/h2&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;

top

View Objects

I created my own objects that contain only the few properties I care about for my view.


    public class TrelloBoard
    {
        public string Id { get; set; }
        public string Text { get; set; }
        public string OrgText { get; set; }
    }

    public class TrelloList
    {
        public string Id { get; set; }
        public string Text { get; set; }
    }

    public class TrelloCard
    {
        public string OrgText { get; set; }
        public string BoardText { get; set; }
        public string ListText { get; set; }
        public string Text { get; set; }
        public string Url { get; set; }
    }

CreatePage Method

Here is the method called to replace the tokens in the template. It looks for some specific tokens. If it finds a token that is not one of the pre-defined values, it calls the method to query Trello for that value.

private void CreatePage(ITrelloData data)
{
    try
    {
        string templateFile = Path.Combine(Application.StartupPath, "DailyActions_Template.html");
        string htmlFile = Path.Combine(Application.StartupPath, "DailyActions.html");

        string html = File.ReadAllText(templateFile);

        string regEx = "{{.*?}}";

        MatchCollection tokenList = Regex.Matches(html, regEx, RegexOptions.IgnoreCase);

        foreach (Match match in tokenList)
        {
            switch (match.Value.ToLower())
            {
                case "{{date}}":
                    {
                        string date = dateTimePicker1.Value.ToLongDateString();
                        html = html.Replace(match.Value, date);
                        break;
                    }
                case "{{time}}":
                    {
                        string time = dateTimePicker1.Value.ToLongTimeString();
                        html = html.Replace(match.Value, time);
                        break;
                    }
                case "{{tagprefix}}":
                    {
                        html = html.Replace(match.Value, txtTagPrefix.Text);
                        break;
                    }
                default:
                    {
                        List&lt;TrelloCard&gt; cards = data.GetCardsForQuery(match.Value);
                        html = html.Replace(match.Value, GetActionsHtml(cards));
                        break;
                    }
            }
        }

        File.WriteAllText(htmlFile, html);

        webBrowser1.Navigate(htmlFile);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception: " + ex.ToString());
    }
}

top

GetCardsForQuery Method

public List&lt;TrelloCard&gt; GetCardsForQuery(string token)
{
    // Returns an ordered list of Trello cards that have only the
    // properties from the the Board and List that are required
    // for the view.

    // Remove the token delimiters.
    string query = token.Replace("{", "").Replace("}", "");

    // Trello uses the "@" for specific queries.  If this is not one of
    // those queries, we need to add the tag prefix (user initials) and
    // surround the query with quotes.
    if (!query.Contains("@"))
    {
        query = """ + "+" + _tagPrefix + "-" + query + """;
    }

    List&lt;TrelloCard&gt; retVal = new List&lt;TrelloCard&gt;();
    List&lt;TrelloBoard&gt; boards = new List&lt;TrelloBoard&gt;();
    List&lt;TrelloList&gt; lists = new List&lt;TrelloList&gt;();

    IEnumerable&lt;Card&gt; cards = _trello.Cards.Search(query, 1000);

    foreach (var card in cards)
    {
        TrelloCard trelloCard = new TrelloCard
        {
            Url = card.Url,
            Text = card.Name
        };

        TrelloBoard trelloBoard = boards.Find(b =&gt; b.Id == card.IdBoard);
        if (trelloBoard == null)
        {
            trelloBoard = new TrelloBoard { Id = card.IdBoard };
            Board board = _trello.Boards.WithId(card.IdBoard);
            trelloBoard.Text = board.Name;
            trelloBoard.OrgText = _trello.Organizations.WithId(board.IdOrganization).DisplayName;
            boards.Add(trelloBoard);
        }
        trelloCard.BoardText = trelloBoard.Text;
        trelloCard.OrgText = trelloBoard.OrgText;

        TrelloList list = lists.Find(b =&gt; b.Id == card.IdList);
        if (list == null)
        {
            list = new TrelloList { Id = card.IdList };
            list.Text = _trello.Lists.WithId(card.IdList).Name;
            lists.Add(list);
        }
        trelloCard.ListText = list.Text;

        retVal.Add(trelloCard);
    }

    retVal = retVal.OrderBy(o =&gt; o.OrgText + "-" + o.BoardText + "-" + o.Text).ToList();

    return retVal;
}

top

GetActionsHtml Method

Returns an Html unordered list grouped by Organization and Board.

private string GetActionsHtml(List&lt;TrelloCard&gt; actionList)
{
    StringBuilder sb = new StringBuilder();

    string org = "";
    string board = "";
    foreach (TrelloCard nextAction in actionList)
    {
        if (nextAction.OrgText != org)
        {
            if (!string.IsNullOrEmpty(org))
            {
                sb.Append("&lt;/ul&gt;");
                board = ""; // Since the Org is changing, clear the board.
            }
            sb.AppendFormat("&lt;div class='Organization'&gt;{0}&lt;/div&gt;", nextAction.OrgText);
            sb.AppendLine();

            if (!string.IsNullOrEmpty(board))
            { sb.Append("&lt;/ul&gt;"); }
            sb.AppendFormat("&lt;div class='Board'&gt;{0}&lt;/div&gt;", nextAction.BoardText);

            sb.AppendLine();
            sb.Append("&lt;ul&gt;");

            org = nextAction.OrgText;
            board = nextAction.BoardText;
        }
        sb.AppendFormat("&lt;li&gt;_ &lt;a href='{0}' target='trello'&gt;{1}&lt;/a&gt;&lt;span class='List'&gt;{2}&lt;/span&gt;&lt;/li&gt;",
            nextAction.Url, nextAction.Text, nextAction.ListText);
        sb.AppendLine();
    }
    sb.Append("&lt;/ul&gt;");
    sb.AppendLine();

    return sb.ToString();
}

top

Future Plans

  • Convert to Asp.Net MVC site, allow others to use it.
  • Schedule page to be ran daily and emailed

Technology

Visual Basic 6 Programming

Visual Basic was fun. I considered using VB2 for one project, but without database support, it was not the right fit. When VB3 came along with database support, I was in. I lived through the VB4 16/32 bit issues and fought the DLL Hell that was just a part of life back then. When VB6 finally came along and was so solid that the programs actually ran and did what we expected, it was very refreshing. I programmed in Visual Basic for many years.

This is a small example of a function I wrote to be able to log messages that contained binary information. A big part of our programming was serial communications, and the low ASCII chars wreaked havoc on the log files.
I am not offering this as the perfect example of VB6 programming. However, it does show that we had a convention for naming variables and parameters. It also include error handling, which was automatically added and removed from a VB6 Addin I wrote (we called it CodeBuddy).


Public Function wb_ConvertFromBinary(pstrMsg As String) As String
   On Error GoTo ErrorHandler:

   Dim intLoop As Integer
   Dim intLen As Integer
   Dim strReturn As String
   Dim strChar As String
   Dim blnBinary As Boolean
   
   intLen = Len(pstrMsg)
   blnBinary = False
   
   For intLoop = 1 To intLen
   
      strChar = Mid(pstrMsg, intLoop, 1)
      If Asc(strChar) < 32 Then
         blnBinary = True
         strChar = "<" & Right("00" & Asc(strChar), 3) & ">"
      End If
      strReturn = strReturn & strChar
      DoEvents
   Next intLoop

   If blnBinary Then
      strReturn = "BIN:" & strReturn
   End If
   
   wb_ConvertFromBinary = strReturn
   
   Exit Function
ErrorHandler:
   wb_LogError "WBSCommon/wb_ConvertFromBinary", Err.Description, Err.Number, Erl

End Function

SSH Interface to Siemens HiPath 4000 Phone Switch

Summary

The purpose for this project was to change the class of service for a specific extension on a Siemens HiPath 4000 Phone Switch. It connects with the switch over the network using SSH.

Description

The heavy lifting on this project is provided by the Jscape SSH component. We had used their Telnet component on another project, and found it very reliable and pretty easy to implement.

TODO: Code Samples and Screen shots.

Technology

BooksForLuke – Web Site

BflLogo

Summary

This site is more of a landing page than an actual site. The purpose of this page was to give people an easy way to contact us to donate books or find out more about what we are doing.

Banner Design

Since the site is just one page, I figured if I got help with a decent banner, I could put together the rest of the site. I created a project on 48HoursLogos asking for help with designing the banner. Here is the initial page I created to help the designers see how simple this site really was. (The original name was TammyBook.com). I was only asking them for something to replace the TammyBook.com banner at the top.
TammyBook_com
Here are some of the submissions I received.
Img07
Img06
Img05
Img04
I like them all and they each have their strengths. However, when I saw the last one with the children, I was hooked.  I had thought about a building block to convey the idea that this site is about children.  I worked with the designer, and he came up with the next version.

Img03
This is closer, but the block didn’t look like the classic building block I was thinking of. I found a picture on Google and uploaded it for him to see what I was thinking.

This was the final contest winner. He added the “lines” in the block and produced this.
Good design to me is like obscenity. I may not be able to describe it, but I know it when I see it. This one just worked for me.
Img02

With his banner, the site currently looked like this. I thought it looked pretty good. The font used is Kristen ITC. It is intended to be a “child” font and considering the site, seemed right to me. Here is what the site looked like with the new banner.

BflWeb01

Since my designer had raised the bar of how the site should look, I asked if he would suggest any other changes on the site. He came up with this.

BooksForLuke_140219a

I was “nearly” convinced. I liked the the new site. My only concern was that it may look a little “too polished” and we might lose the personal connection with the visitors. I submitted the two versions in a FeedBack Army project to get some feedback from other people. To my surprise, some people actually preferred the first site, but the majority liked the new site.

Amazon Email

When a visitor fills out the form on the site, we receive an email Using the Amazon SES SMTP Interface.

Technology

  • Asp.Net MVC, C#, Razor
  • Amazon SES SMTP

Health Level Seven (HL7) Application

Summary

Health Level Seven (HL7) is the standard for exchanging information between medical applications. This application receives HL7 messages from Hospital Information Systems (HIS). These messages allow us to keep track of the location of each patient.

Description

If you have not seen HL7 before, it can look a little ugly the first time. Here is a message.

MSH|^~&|P2000|POH|TVIF||20100301185944|N|ADT^A01|01|T|2.3
EVN|A01|20100301185943|||scosby
PID|||399599|399599|FLINTSTONE^FRED^K||194807070000|M||W|2915 STONE LN^^WATERFORD^MI^48329||(248)111-2222|||||7000103191|777-88-9999
PV1||I|7E^7028^B||||01195^FLINTSTONE^PEBBLES^P|||REH||||||N|01195^FLINTSTONE^PEBBLES^P|3||T||||||||||||||||||||||||201003011857
GT1|||FLINTSTONE^FRED^K||2915 STONE LN^^WATERFORD^MI^48329^USA|(248)111-2222|||M
(For a detailed explanation, checkout this link)

You can see names, addresses, and phone numbers in the message. The “7E^7028^B” is the assigned patient location (PointOfCare/Room/Bed). HL7 is extremely detailed. For births, it even identifies the birth order of each child.
Our software receives thousands of these messages per day. I wrote the library code that breaks these messages into something our software can use.

TODO: Code Samples and Screen shots.

Technology

  • C#

Move mission critical services to Amazon Web Services

Summary

Summary here…

Description

Detailed Description…

Amazon EC2 Metadata

We use the following class to obtain Metadata from a running Amazon instance. This code also demonstrates a way set a timeout variable when using the WebClient object.

public class AwsHelper
{
private const string LOG_PREFIX = "AwsHelper/";
/* A class to return Metadata from a running Amazon instance.
Requires: using System.Net;
We call these methods in the startup routine of our windows service.
Here is an example:
// If called from a non AWS instance, each request would take 5 seconds (the timeout),
// so we wrap them to only be called when an AvailabilityZone is returned.
string avZone = AwsHelper.AvailabilityZone;
if (!string.IsNullOrEmpty(avZone))
{
Logging.LogInfo(logMsg + " Amazon Availability Zone: " + avZone);
Logging.LogInfo(logMsg + " AmiId: " + AwsHelper.AmiId);
Logging.LogInfo(logMsg + " PublicIpv4: " + AwsHelper.PublicIpv4);
Logging.LogInfo(logMsg + " InstanceId: " + AwsHelper.InstanceId);
Logging.LogInfo(logMsg + " InstanceType: " + AwsHelper.InstanceType);
Logging.LogInfo(logMsg + " SecurityGroups: " + AwsHelper.SecurityGroups);
}
Here is a list of other meta data that could be returned. This list is
obtained by calling: http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
hostname
instance-action
instance-id
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/
*/
public static string AvailabilityZone
{
// Returns Region + AvailabilityZone, Example: eu-west-1c
get { return GetMetaData("placement/availability-zone"); }
}
public static string PublicIpv4
{
get { return GetMetaData("public-ipv4"); }
}
public static string AmiId
{
get { return GetMetaData("ami-id"); }
}
public static string InstanceId
{
get { return GetMetaData("instance-id"); }
}
public static string InstanceType
{
get { return GetMetaData("instance-type"); }
}
public static string SecurityGroups
{
get { return GetMetaData("security-groups"); }
}
public static string GetMetaData(string urlSuffix)
{
string logMsg = LOG_PREFIX + "GetMetaData";
string retVal = "";
const string URL_PREFIX = "http://169.254.169.254/latest/meta-data/";
const int WAIT_TIMEOUT_MILLISECONDS = 5000;
try
{
Logging.LogTrace(logMsg + string.Format(", for urlSuffix: {0}", urlSuffix));
// Create the Full Url we will need to find the data.
string fullUrl = URL_PREFIX;
if (!urlSuffix.StartsWith("/"))
{ fullUrl += "/"; }
fullUrl += urlSuffix;
WebClientWithTimeout webClient = new WebClientWithTimeout(WAIT_TIMEOUT_MILLISECONDS);
retVal = webClient.DownloadString(fullUrl);
}
catch (WebException ex)
{
if (ex.Message.Contains("The operation has timed out"))
{
// This "EXCEPTION" will normally be thrown when we try to use this
// method from a computer that is not running on an Amazon server.
Logging.LogTrace(logMsg + ", Property called from a non Amazon server.");
}
else
{ Logging.LogError(logMsg + ", WEBEXCEPTION: " + ex.Message, ex); }
}
catch (Exception ex)
{ Logging.LogError(logMsg + ", EXCEPTION: " + ex.Message, ex); }
return retVal;
}
private class WebClientWithTimeout : WebClient
{
// The reason we are not using the default WebClient class is because we need to specify
// a timeout. The default Timeout for the WebClient is at least 20 seconds (Some people
// say 100 seconds). When we are running on an Amazon server, the request returns almost
// instantaneously, so the timeout doesn't matter.
// However, when we call this from a non Amazon computer, the code will just "hang" until
// the timeout has elapsed.
// http://stackoverflow.com/questions/1789627/how-to-change-the-timeout-on-a-net-webclient-object
private int _timeoutMilliSeconds = 5000;
public WebClientWithTimeout() { }
public WebClientWithTimeout(int timeoutMilliSeconds)
{
_timeoutMilliSeconds = timeoutMilliSeconds;
}
protected override WebRequest GetWebRequest(Uri uri)
{
WebRequest webRequest = base.GetWebRequest(uri);
webRequest.Timeout = _timeoutMilliSeconds;
return webRequest;
}
}
}
Click the “view raw” link to view the entire code listing.

Technology

  • C#
  • WebClient (with timeout)