Category Archives: Projects

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)

Hospital Update Program

Summary

We install and maintain servers at customer locations around the country. Often, these servers are sitting in dark closets in basements without any human interaction. They are required to run 24×7 with a small window of time in the “wee hours” of the night for updates.
Our software runs as Windows Services and we have multiple services on each server. If our software is interfacing with a specific piece of hardware using a serial connection, that is one service. If we are listening for a call on a Dialogic Telephony card, that is a different service. Before this project was implemented, it was becoming very difficult to keep each location running the correct version of the software.

Description

This project consists of three major components. First, it has a Website component for administrators to view and edit information. Second, it has a console program that checks for and actually performs the updates. Finally, it has web services that expose functionality that both the Website and Console program need.

The original version was deployed on Microsoft Azure using Sql Azure as the database. It is currently being moved to Amazon servers using a SQL Server deployed on Amazon Web Services. The actual files were saved to Azure using a CloudStorageAccount, but are now being saved to Amazon S3 storage.

A WatchDog service at each location runs the Hup.exe console program hourly. Hup compares the current running version of each service to the required version for that location. When it finds a difference, it downloads the new version into a new folder. After it successfully downloads the files, it stops and unregisters the old version of the windows service. It then fires up the new version.

Here is an example of how a folder looks with multiple versions.
HupFolder

Website (Asp.Net MVC3, ASPX Views)

I am not a designer (seems like I say that in every post). I always liked the clean simple look of Google, and Gmail was no exception. Since this was an intranet site, I figured Google would not mind (or find out) that I “borrowed” their look.

This project started as an MVC2 project, so the Razor view engine was not a possibility. When I upgraded it to MVC3, it didn’t make sense to refactor the views. I have used Razor on subsequent projects, and I definitely prefer it to the ASPX view engine.

List Screen

Here is an example List screen. It includes paging information with the total record count and the current “page” being viewed. Clicking the “Show Filter” link brings up the next screen.
HupAppList
top

Filter Screen

The user can filter the results based on predetermined values. This specific screen only allows the user to filter based on the Application. They can also select the number of records they want to view per page. Finally, they can select from a predefined list of Sort fields to order the results.
HupAppFilter
top

Edit Screen

Here is an example Edit screen. The preferred action is to click the Save button, but if they want to cancel their changes, they can click the “Back to List” link to cancel their changes.
HupAppEdit
top

Set Current Version

This is one of the more critical screens in the application. Here, the user can chose what applications run for each specific location. Our server may be talking to one piece of hardware at one location, and completely different type of hardware at another location. In addition to the application, the user an also select what version needs to be ran at that location. Usually, we want to run the latest version, but not always. This screen gives us that flexibility.
HupAssign
top

Some services run multiple copies of the same service. For a Dialogic Telephony card listening on four lines, four separate services will run. The Service Count column allows the user to specify how many should run. In this example, the column is grayed out for the currently selected services, since each of them are only allowed to run one instance.

Version Report

This was a very helpful report/screen that allows the user to know what version is running at each location. This should be one of the first screens a tech should use when running down a problem at a location. It has been especially helpful when a location is have a problem that we “thought we already fixed”, only to find that a specific location is still running an older version of the code.
HupReport
top

WCF Services

Common Methods

I am sure I stole this idea from somebody, but it has been so helpful to always include a few common methods in all web services I write. It makes it nice to verify that the basics are working before I try to run down why some method that requires a complex object is not working.

  • Version – This is helpful for making sure the latest code actually got deployed.
  • ServerTime – Useful to make sure I am not fighting a cache issue and the result is changing each time I call the service.
  • Ping – Returns an Http 200 response status code, but no text. When using a Load Balancer, I have it call this method to make sure the server is ready for requests.
  • HupSnippet
    top

    Console Program

    Yes, an actual console program. This needed to be a simple .EXE that could be placed on a remote server. It only requires the .Net Framework and doesn’t depend on any custom .DLLs. When it is ran without any parameters, it shows the following help screen.
    HupConsole
    top

    It started with just a few simple switches, but grew to include other functions.

    Technology

    • Service-Oriented Architecture
    • Windows Communication Foundation (WCF)
    • Deployed on Microsoft Azure
    • Sql Azure Database
    • Asp.Net MVC3 (not Razor)
    • Windows Service, Console Program

    top

Using WordPress to display my Resume

I have used WordPress in the past for Luke’s website (GrowingUpLuke) and I found it very easy to get a decent looking site up quickly. It allowed me to focus on the content, while it handled all the plumbing of displaying it.

However, when I considered using WordPress for my resume, I could not find a way to create a custom page with only specific posts on that page.  I wanted to create an Experience page (like this) that showed only my job history, but not other posts.

I found a plugin that would allow me to do it, but when I looked for the Plugin menu item, I could not find it.  After a little searching I found this text, “…WordPress.com users won’t find a Plugins tab in the Dashboard” on the WordPress.com site.

I considered hosting the WordPress site on my existing server, but it seemed much easier (and fairly cheap) to sign up with Bluehost, since they were recommended from the WordPress.org site.

Here is the list of Plugins I have installed so far:

  • Add Posts to Pages – Makes it easy to create a page of specific posts.
  • Table of Contents Plus – Simple way to insert a table of contents into a post.
  • Simple Custom CSS – Allow custom CSS.
    (I needed to add some space after each list item on my about page. This Plugin allowed me to accomplish this
    by adding this CSS: “article ol li { padding-bottom: 15px; }”)

    Update: While creating this page, I discovered I could have just used a <div>. Oh well, I know for next time. 🙂
  • GistPress – A WordPress plugin to easily embed Gists via oEmbed or shortcode.
  • SyntaxHighlighter Evolved – easily post syntax-highlighted code to your site (brushes)
  • Print Friendly and PDF Button – Printer Friendly pages (Support)

Triage – Amazon Marketplace Web Services – Product Finder

Summary

The purpose of this project was to provide an easy way to determine if a book (or other media) is sellable on Amazon. It uses the MWS (Marketplace Web Services) API provided by Amazon that allows merchants to programmatically send and receive information.

A major consideration for this project was to not “waste” API calls. Amazon, like most APIs, limits the number of requests that can be submitted in a given amount of time (Throttling).

When searching for a product on Amazon, they often return multiple items for a given query. Most of them are not a match, and need to be eliminated from consideration. Once the user has determined which products are a match, they click the Get button. An Ajax call is made to our server, which makes the API call to Amazon. This is getting a bit wordy, but the screens below should help make sense of it.

Description

I named this project the Triage project since it reminds me of the television series M*A*S*H where the doctors look over the new arrivals and decide which patients need immediate attention. Some are prepped for surgery while others are moved directly to post-op.  That is similar to how we need to handle the batches of books.  For ones that appear battered or not in very good shape, they will go in one pile.  Ones that are missing a barcode will go in a different pile, and on and on.

When we have a good pile of products to investigate, we need so scan each one and determine if it is worth sending to Amazon.  That is the purpose of this program.

Design

I broke this project into the following layers.

Data Layer

There are two primary sources of data for this application.

MySql / SQLite

The project has a pretty simple database for the information about each book. Some basic information like the source of the book, weight, condition, price and a few other things are stored in the database. This information is used when the listing is submitted to Amazon. I am using NHibernate as the ORM for the data layer. This makes it very easy to work with SQLite for development, and then change to MySql for production with a simple web.config change.

I also used Fluent NHibernate to make configuration simple. I especially like the Schema generation feature where I can just add a property to an object during development, and have my database generated from the objects. This along with the SQLite makes the development side much more efficient.

Amazon Marketplace Web Services

The Amazon MWS API consists of multiple sections. Most of what is required for this app is in the Products section of the API.
Methods called include:

  • ListMatchingProducts
  • GetCompetitivePricingForASIN
  • GetLowestOfferListingsForASIN

Business Layer (with Entities)

The project uses a manager class that calls the MWS API and converts the objects into our entity objects.
For a bigger project, I would have added a Service Layer that would have controlled access to the Business layer and provided a REST interface to the client applications. However, for this project, the MVC Controllers call the Business layer directly.

Presentation Layer (Asp.net MVC)

We are not shooting for any awards here. This project has one of the most plain looks of anything I have done recently. My goal was to keep it as uncluttered and easy to use as possible.

Screens

In the Stephen Covey fashion of “Begin with the End in Mind”, here are some screenshots of what I was shooting for.

Input Form

Triage Input

This is a pretty simple screen. It allows the user to scan a barcode or enter text for a lookup. When scanning a barcode, the scanner adds an <ENTER> after the barcode. The following code was added so the lookup button will be clicked “automatically” after a scan.

$("#txtLookup").keyup(function (event) {
   // console.log("lookup keypress: %O", event);
   var KEYCODE_ENTER = 13;
   if (event.keyCode == KEYCODE_ENTER) {
      $("#btnLookup").click();
   }
});

List View

I make one API call to get the results for the input. Often, items will be returned which do not match the product we are looking for. This screen allows us to eliminate the ones we don’t care about.  The user will click the Hide button, and the item will be removed.
TriageList

Results View

Finally, we get the actual results we are looking for.
TriageResult

Considerations

Quick and easy (for the user)

I wanted it to be very easy for a new user to start being productive with this screen.  All they need is a browser and a pile of books and they can start scanning.

Go/NoGo

The original goal was to have a simple Go/NoGo (Red/Green) indicator on the screen to let the user know if we wanted to send the book to Amazon.  That will involve a good deal more analysis  of the many variables that play into that decision.   For a first pass, I may do a Red/Yellow/Green indicator.  I could inform the books we DO want to send (Green) and the ones we do NOT want to send (Red), and make everything else Yellow so they can determine if we should send it.

Throttling

Throttling must be considered when developing APIs. (That is a big part of the reason this project was an Asp.Net MVC application using Ajax). When we lookup a product on Amazon, we often get more than one result. Most of the results do not match the product we are searching, so we don’t want to “waste” our API calls on products we don’t care about.

Code Samples/Examples

Javascript ReplaceAll method

The Javascript Replace method only replaces the first occurrence of a string. This function will replaces all occurrences of a string.  It is just a one liner and I didn’t write it, but I wanted to include it for future reference.

function ReplaceAll(string, search, replacement) {
// http://stackoverflow.com/questions/1144783/replacing-all-occurrences-of-a-string-in-javascript
return string.split(search).join(replacement);
}
view raw ReplaceAll hosted with ❤ by GitHub

Click the “view raw” link to view the entire code listing.

top

UrlEncodeObj

Return object as URL encoded string. I didn’t write this function (I hate one character variable names). I think it was provided by a fellow we call “Little Ben Hooper”.

// return object as URL encoded string
// I did not write this code, it was pasted from somewhere.
UrlEncodeObj = function (o) {
var sdata = '';
for (var k in o) {
if (sdata) {
sdata += '&';
}
sdata += k + '=' + escape(o[k]);
}
return sdata;
};
view raw UrlEncodeObj hosted with ❤ by GitHub

Click the “view raw” link to view the entire code listing.

top

ViewModel

I like to use the view model for the page. This makes it much easier to pass all required objects to a view. It also makes it much easier to add future properties and objects.

public class ProductViewModel
{
   public string Filter { get; set; }
   public string Condition { get; set; }
   private List<SelectListItem> _conditionList;

   public ProductViewModel()
   {
      _conditionList = new List<SelectListItem>();
      _conditionList.Add(new SelectListItem { Text = "New", Value = "New" });
      _conditionList.Add(new SelectListItem { Text = "Used, Like New", Value = "UsedLikeNew" });
      _conditionList.Add(new SelectListItem { Text = "Used, Very Good", Value = "UsedVeryGood" });
      _conditionList.Add(new SelectListItem { Text = "Used, Good", Value = "UsedGood" });
      _conditionList.Add(new SelectListItem { Text = "Used, Acceptable", Value = "UsedAcceptable" });
   }

   public IEnumerable<SelectListItem> ConditionList
   {
     get { return _conditionList.AsEnumerable(); }
   }
}

Page Markup

This makes the actual page markup pretty simple.

@model Bfl.TriageWeb.ProductViewModel
@{
    ViewBag.Title = "Index";
}
<h3>MatchingProducts</h3>
<button id="btnClear" title="Clear Input field">X</button>
<input type="text" id="txtLookup" value="" autofocus />
<button id="btnLookup">Lookup</button>
<br />
    Last Input: <b><span id="lastInput"></span></b>

<div>
    Filter:
            @Html.RadioButtonFor(x => x.Filter, "", new { @class = "radioButton" })None
            @Html.RadioButtonFor(x => x.Filter, "VHS", new { @class = "radioButton" })VHS
            @Html.RadioButtonFor(x => x.Filter, "DVD", new { @class = "radioButton" })DVD
            @Html.RadioButtonFor(x => x.Filter, "Books", new { @class = "radioButton" })Books
</div>
<table id="tblProduct">
    <tbody valign="top">
    </tbody>
</table>
<hr />

Ajax Request

When the user clicks the Get button, the following method is called. It uses the index of the Get button clicked to change the caption and disable the button.

function ButtonGet(index, key) {
  // console.log("ButtonGet index: %O, key: %O", index, key);
  var data = {};
  data.index = index;
  data.asin = key;
            
  // Disable the button and change the caption.
  $('#btn' + index).html("Retrieving Item...");
  $('#btn' + index).attr("disabled", "disabled");

  $.ajax("/Product/GetLowestOffer/", {
    type: "POST",
    dataType: 'json',
    data: UrlEncodeObj(data),
    success: ShowOneItem
  });
}

The controller returns a ViewModel for this specific request.

public class OfferInfoViewModel
{
  public string Index { get; set; }
  public string Asin { get; set; }
  public List<LowestOfferDto> AmazonOffers { get; set; }
  public List<LowestOfferDto> MerchantOffers { get; set; }

  public CompetitivePricingDto CompetitivePricing { get; set; }

  public OfferInfoViewModel()
  {
    AmazonOffers = new List<LowestOfferDto>();
    MerchantOffers = new List<LowestOfferDto>();
  }
}

And here is the controller code for the Ajax call.

public string GetLowestOffer()
{
    string logMsg = LOG_PREFIX + "GetLowestOffer";

    OfferInfoViewModel retVal = new OfferInfoViewModel();
    try
    {
        if (Request.Form.Count > 0)
        {
            string asin = Request.Form["asin"];
            string index = Request.Form["index"];

            AmazonMwsProductManager mgr = new AmazonMwsProductManager();

            LowestOfferListingsListResult lowestList = mgr.GetLowestOfferListingsForAsin(asin);
            retVal.Asin = asin;
            retVal.Index = index;

            Logging.LogTrace("Lowest Offer Result for Asin: " + asin);
            StringBuilder sb = new StringBuilder();
            foreach (LowestOfferListingsResult lowestOffer in lowestList)
            {
                LowestOfferDto oneItem = new LowestOfferDto(lowestOffer);

                if (lowestOffer.FulfillmentChannel.ToLower().Contains("amazon"))
                { retVal.AmazonOffers.Add(oneItem); }
                else
                { retVal.MerchantOffers.Add(oneItem); }
            }

            CompetitivePricingResult competitivePricingResult =  mgr.GetCompetitivePricingForAsin(asin);

            retVal.CompetitivePricing = new CompetitivePricingDto(competitivePricingResult);

            ProductCategoryListResult categoryList = mgr.GetProductCategoriesForAsin(asin);
            retVal.CompetitivePricing.AddCategoryNameForSalesRank(categoryList);
        }
    }
    catch (Exception ex)
    { Logging.LogError(logMsg + ", EXCEPTION: " + ex.Message, ex); }

    return JsonConvert.SerializeObject(retVal);
}

Ajax Response

This code needs work.

  • The inline styles need to be removed and placed into the site.css.
  • It needs to be converted to use something like JsRender
  • Portions need to be extracted into separate functions

Even with those disclaimers, I was still hesitant to post this code. In my experience, writing code is a very iterative process. We don’t always get what we want the first time. Often, we “try” something (like an inline style) to see if we get what we are looking for. Good code is a process of continuous refactoring.
In some ways, this snippet is “mid-refactor”. I should clean it up before posting it, but I plan to make the JsView refactor a separate post later.

function ShowOneItem(data) {
    console.log("ShowOneItem, data: %O", data);

    $('#btn' + data.Index).html("Get");
    $('#btn' + data.Index).removeAttr("disabled");

    var rowFba = "";

    $.each(data.AmazonOffers, function (index, item) {
        rowFba += "" +
            "<tr width='400px' >" +
            "<td nowrap>" + item.LandedPrice +
            "<span style='font-size: small;'> (" + item.ListedPrice + " + " + item.Shipping + ") </span></td>" +
            "<td nowrap>" + item.Condition + "</td>" +
            "<td align='right'>" + item.NumberOfOfferListingsConsidered + "</td>" +
            "<td align='right' nowrap>" + item.SellerPositiveFeedbackRating + "</td>" +
            "<td align='right'>" + item.SellerFeedbackCount + "</td>" +
            "</tr>";
    });

    var rowMerchant = "";

    $.each(data.MerchantOffers, function (index, item) {
        rowMerchant += "" +
            "<tr>" +
            "<td nowrap>" + item.LandedPrice +
            "<span style='font-size: small;'> (" + item.ListedPrice + " + " + item.Shipping + ") </span></td>" +
            "<td nowrap>" + item.Condition + "</td>" +
            "<td align='right'>" + item.NumberOfOfferListingsConsidered + "</td>" +
            "<td align='right' nowrap>" + item.SellerPositiveFeedbackRating + "</td>" +
            "<td align='right'>" + item.SellerFeedbackCount + "</td>" +
            "</tr>";
    });

    var rowSalesRank = "";
    $.each(data.CompetitivePricing.SalesRankings, function (index, item) {
        rowSalesRank += "" +
            "<tr style='background-color: #A2D39C;'>" +
            "<td align='right'>" + item.Rank + "</td>" +
            "<td>" + item.CategoryName + "  (" + item.ProductCategoryId + ")</td>" +
            "</tr>";
    });

    // Build the Html for the item.
    var html = "" +
        "<table class='itemTable' >" +
        "<tr>" +
        "<th>&nbsp;</th>" +
        "<th>&nbsp;</th>" +
        "<th>&nbsp;</th>" +
        "<th colspan='2' nowrap >----Feedback----</th>" +
        "</tr>" +
        "<tr style='border-bottom: solid medium blue;'>" +
        "<th nowrap>Price <span style='font-size: 10px;' >(Listed + Shipping)</span></th>" +
        "<th>Condition</th>" +
        "<th align='right'>Offers</th>" +
        "<th align='right'>Rating</th>" +
        "<th align='right'>Count</th>" +
        "</tr>" +
        "<tbody>" +
        "<tr><td colspan='9'><h3>Lowest FBA Offers</h3></td></tr>" +
        rowFba +
        "<tr><td colspan='9'><h3>Lowest Merchant Offers</h3></td></tr>" +
        rowMerchant;

    html = html +
        "<tr><td colspan='9'><h3>Buy Box Price</h3></td></tr>";
    if (data.CompetitivePricing.NewPrice.Condition != null) {
        html = html + "" +
            "<tr style='background-color: #FDC68A;'>" +
            "<td nowrap>" + data.CompetitivePricing.NewPrice.LandedPrice +
            "<span style='font-size: small;'> (" + data.CompetitivePricing.NewPrice.ListedPrice + " + " +
            data.CompetitivePricing.NewPrice.Shipping + ") </span></td>" +
            "<td>" + data.CompetitivePricing.NewPrice.DisplayCondition + "</td>" +
            "<td>&nbsp;</td>" +
            "<td>&nbsp;</td>" +
            "</tr>";
    }
    if (data.CompetitivePricing.UsedPrice.Condition != null) {
        html = html + "" +
            "<tr>" +
            "<td nowrap>" + data.CompetitivePricing.UsedPrice.LandedPrice +
            "<span style='font-size: small;'> (" + data.CompetitivePricing.UsedPrice.ListedPrice + " + " +
            data.CompetitivePricing.UsedPrice.Shipping + ") </span></td>" +
            "<td>" + data.CompetitivePricing.UsedPrice.DisplayCondition + "</td>" +
            "<td>&nbsp;</td>" +
            "<td>&nbsp;</td>" +
            "</tr>";
    }

    html = html +
        "</tbody>" +
        "</table>";

    // Add the Sales Ranking
    html = html +
        "<table class='itemTable'>" +
        "<tr style='border-bottom: solid medium blue;'>" +
        "<th align='right'>Rank</th>" +
        "<th align='right'>Category</th>" +
        "</tr>" +
        "<tbody>" +
        rowSalesRank +
        "</tbody>" +
        "</table>";
    html = html +
        "<br /><div style='font-size: Large;'>" +
        "<b>Listings Available: </b>";

    if (data.CompetitivePricing.NumberOfListingsNew != "0") {
        html = html +
            "<a href='http://www.amazon.com/gp/offer-listing/" + data.Asin +
            "/ref=dp_olp_new?ie=UTF8&condition=new' " +
            " target='_blank' >" +
            data.CompetitivePricing.NumberOfListingsNew + " new" +
            "</a> ";
    }

    if (data.CompetitivePricing.NumberOfListingsUsed != "0") {
        html = html +
            "<a href='http://www.amazon.com/gp/offer-listing/" + data.Asin +
            "/ref=dp_olp_used?ie=UTF8&condition=used' " +
            " target='_blank' >" +
            data.CompetitivePricing.NumberOfListingsUsed + " used" +
            "</a> ";
    }

    if (data.CompetitivePricing.NumberOfListingsCollectible != "0") {
        html = html +
            "<a href='http://www.amazon.com/gp/offer-listing/" + data.Asin +
            "/ref=dp_olp_collectible?ie=UTF8&condition=collectible' " +
            " target='_blank' >" +
            data.CompetitivePricing.NumberOfListingsCollectible + " collectible" +
            "</a> ";
    }
    html = html + "</div>";

    $("#divLow" + data.Index).html(html);
}

Technology

  • Asp.Net MVC 4.5
  • C#
  • Amazon Marketplace Web Services
  • jQuery
  • Ajax