How To – A Run.GPS Widget for BlogEngine.Net

RunGps WidgetOne of the great things about BlogEngine.Net is how easy it is to create widgets or plug-ins for it.

I’ve created a widget that displays the stats for a user at Run.GPS.

There are two files that go into a widget: the edit.ascx and the widget.ascx.  Both files go in a folder named for the widget.  That folder goes in the widgets folder off of the web project root.

The edit.ascx holds the code that is used to edit the settings for the widget.  The widget.ascx holds the code for the widget itself.

Run.GPS allows users to build a badge that can be embedded in a web page or blog.  The badge is configured with a series of dropdowns which change a box with HTML code at the bottom of the page.  The code is surrounded in an iframe tag. The HTML can be copied and pasted into any web page to embed the badge.

Badge Interface

For the widget to work, it should have the same settings and generate the same HTML code in an iframe tag.  A quick survey of the various settings shows that the dropdowns directly change most of the values in the HTML code.  For example, changing the “Units” dropdown to “Metric” changes to code to include “&units=Metric and changing the “Map Type” dropdown to “NORMAL” changes the code to include&mapType=NORMAL”.

The only tricky part about converting the settings to HTML code is with the width and height attributes in the HTML code.  For each badge type, there are a different set of dimensions.  Rather than spend too much time on this, I just hard coded the dimensions.

edit.ascx

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="edit.ascx.cs" Inherits="RunGPSEdit" %>

   2: <table style="width: 500px">

   3:     <tr>

   4:         <td style="width: 50%">

   5:             User Name</td>

   6:         <td style="width: 50%">

   7:             <asp:TextBox ID="UserNameTextBox" runat="server" Width="100%"></asp:TextBox>

   8:         </td>

   9:     </tr>

  10:     <tr>

  11:         <td style="width: 50%">

  12:             Type</td>

  13:         <td style="width: 50%">

  14:             <asp:DropDownList ID="TypeDropDownList" runat="server" Width="100%">

  15:                 <asp:ListItem Value="0">Calendar</asp:ListItem>

  16:                 <asp:ListItem Value="1">Quick User Info</asp:ListItem>

  17:                 <asp:ListItem Value="2">Total Calories Counter</asp:ListItem>

  18:                 <asp:ListItem Value="3">Total Distance Counter</asp:ListItem>

  19:                 <asp:ListItem Value="4">Live Tracking</asp:ListItem>

  20:                 <asp:ListItem Value="5">Live Tracking (just the map)</asp:ListItem>

  21:                 <asp:ListItem Value="6">Location</asp:ListItem>

  22:                 <asp:ListItem Value="7">Training Map</asp:ListItem>

  23:                 <asp:ListItem Value="8">Training Map &amp; Info</asp:ListItem>

  24:                 <asp:ListItem Value="9">Route Map</asp:ListItem>

  25:             </asp:DropDownList>

  26:         </td>

  27:     </tr>

  28:     <tr>

  29:         <td style="width: 50%">

  30:             Units</td>

  31:         <td style="width: 50%">

  32:             <asp:DropDownList ID="UnitsDropDownList" runat="server" Width="100%">

  33:                 <asp:ListItem>Imperial</asp:ListItem>

  34:                 <asp:ListItem>Metric</asp:ListItem>

  35:             </asp:DropDownList>

  36:         </td>

  37:     </tr>

  38:     <tr>

  39:         <td style="width: 50%">

  40:             Background</td>

  41:         <td style="width: 50%">

  42:             <asp:DropDownList ID="BackgroundDropDownList" runat="server" Width="100%">

  43:                 <asp:ListItem Value="blue">Blue</asp:ListItem>

  44:                 <asp:ListItem Value="green">Green</asp:ListItem>

  45:                 <asp:ListItem Value="yellow">Yellow</asp:ListItem>

  46:                 <asp:ListItem Value="none">None</asp:ListItem>

  47:             </asp:DropDownList>

  48:         </td>

  49:     </tr>

  50:     <tr>

  51:         <td style="width: 50%">

  52:             Map Type</td>

  53:         <td style="width: 50%">

  54:             <asp:DropDownList ID="MapTypeDropDownList" runat="server" Width="100%">

  55:                 <asp:ListItem Value="NORMAL">Normal</asp:ListItem>

  56:                 <asp:ListItem Value="SATELLITE">Satellite</asp:ListItem>

  57:                 <asp:ListItem Value="HYBRID">Hybrid</asp:ListItem>

  58:                 <asp:ListItem Value="PHYSICAL">Physical</asp:ListItem>

  59:             </asp:DropDownList>

  60:         </td>

  61:     </tr>

  62:     <tr>

  63:         <td style="width: 50%">

  64:             Map Zoom</td>

  65:         <td style="width: 50%">

  66:             <asp:DropDownList ID="MapZoomDropDownList" runat="server" Width="55px">

  67:                 <asp:ListItem>18</asp:ListItem>

  68:                 <asp:ListItem>17</asp:ListItem>

  69:                 <asp:ListItem>16</asp:ListItem>

  70:                 <asp:ListItem>15</asp:ListItem>

  71:                 <asp:ListItem>14</asp:ListItem>

  72:                 <asp:ListItem>13</asp:ListItem>

  73:                 <asp:ListItem>12</asp:ListItem>

  74:                 <asp:ListItem>11</asp:ListItem>

  75:                 <asp:ListItem>10</asp:ListItem>

  76:                 <asp:ListItem>9</asp:ListItem>

  77:                 <asp:ListItem>8</asp:ListItem>

  78:                 <asp:ListItem>7</asp:ListItem>

  79:                 <asp:ListItem>6</asp:ListItem>

  80:                 <asp:ListItem>5</asp:ListItem>

  81:             </asp:DropDownList>

  82:         </td>

  83:     </tr>

  84:     <tr>

  85:         <td style="width: 50%">

  86:             Reformat (experimental)</td>

  87:         <td style="width: 50%">

  88:             <asp:DropDownList ID="ReformatDropDownList" runat="server" Width="55px">

  89:                 <asp:ListItem Value="no">No</asp:ListItem>

  90:                 <asp:ListItem Value="yes">Yes</asp:ListItem>

  91:             </asp:DropDownList>

  92:         </td>

  93:     </tr>

  94:     <tr>

  95:         <td style="width: 50%">

  96:             Reformat Color</td>

  97:         <td style="width: 50%">

  98:             <asp:TextBox ID="ReformatColorTextBox" runat="server" Width="100%"></asp:TextBox>

  99:         </td>

 100:     </tr>

 101: </table>

For the edit.ascx, I simply copied the user interface from the badge configuration web page.  I made the values of the various drop down items exactly what they needed to be in the HTML code.  That way I could copy the values directly into the generation HTML.  The only exception is for the “Type” field.  The type field controls which type of badge is going to be generated.  This setting also controls the dimensions of the badge.  I’ve created an array of dimensions so the item values for the “Type” field will be indexes into this array.

edit.ascx.cs

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Web;

   4: using System.Web.UI;

   5: using System.Web.UI.WebControls;

   6: using BlogEngine.Core;

   7: using System.Collections.Specialized;

   8:  

   9: public partial class RunGPSEdit : WidgetEditBase {

  10:     protected void Page_Load(object sender, EventArgs e) {

  11:         if (!Page.IsPostBack) {

  12:             StringDictionary settings = GetSettings();

  13:  

  14:             if (settings.ContainsKey("UserName")) {

  15:                 UserNameTextBox.Text = settings["UserName"];

  16:             }

  17:  

  18:             if (settings.ContainsKey("WidgetType")) {

  19:                 TypeDropDownList.SelectedValue = settings["WidgetType"];

  20:             }

  21:  

  22:             if (settings.ContainsKey("Units")) {

  23:                 UnitsDropDownList.SelectedValue = settings["Units"];

  24:             }

  25:  

  26:             if (settings.ContainsKey("Color")) {

  27:                 BackgroundDropDownList.SelectedValue = settings["Color"];

  28:             }

  29:  

  30:             if (settings.ContainsKey("MapType")) {

  31:                 MapTypeDropDownList.SelectedValue = settings["MapType"];

  32:             }

  33:  

  34:             if (settings.ContainsKey("MapZoom")) {

  35:                 MapZoomDropDownList.SelectedValue = settings["MapZoom"];

  36:             }

  37:  

  38:             if (settings.ContainsKey("Reformat")) {

  39:                 ReformatDropDownList.SelectedValue = settings["Reformat"];

  40:             }

  41:  

  42:             if (settings.ContainsKey("ReformatColor")) {

  43:                 ReformatColorTextBox.Text = settings["ReformatColor"];

  44:             }

  45:  

  46:         }

  47:     }

  48:  

  49:     public override void Save() {

  50:         StringDictionary settings = GetSettings();

  51:  

  52:         settings["UserName"] = UserNameTextBox.Text;

  53:         settings["WidgetType"] = TypeDropDownList.SelectedValue;

  54:         settings["Units"] = UnitsDropDownList.SelectedValue;

  55:         settings["Color"] = BackgroundDropDownList.SelectedValue;

  56:         settings["MapType"] = MapTypeDropDownList.SelectedValue;

  57:         settings["MapZoom"] = MapZoomDropDownList.SelectedValue;

  58:         settings["Reformat"] = ReformatDropDownList.SelectedValue;

  59:         settings["ReformatColor"] = ReformatColorTextBox.Text;

  60:  

  61:         SaveSettings(settings);

  62:     }

  63: }

The code behind for edit.ascx is pretty straight forward.

The edit control itself must derive from WidgetEditBaseWidgetEditBase has an abstract method Save() that must be overwritten.  In the Save() method, the settings are moved from the individual user interface controls into the settings for the Widget.  The settings for the Widget are stored in a StringDictionary and can be retreived by calling GetSettings() as done on line 50.  The last thing to do is save the settings by calling SaveSettings() as done on line 61.


widget.ascx

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="widget.ascx.cs" Inherits="RunGPSWidget" %>

   2: <asp:PlaceHolder ID="WidgetPlaceHolder" runat="server"></asp:PlaceHolder>

The widget itself is as simple as it gets.  It has one PlaceHolder control on the form and nothing else.  I’ll insert the HTML that gets generated into this PlaceHolder when the widget is run.


widget.ascx.cs

The code behind for the widget is the most complicated part, but only in comparison to the rest of it.

   1: public partial class RunGPSWidget : WidgetBase {

Like the edit control, the widget itself must derive from an abstract base.  In this case it is the WidgetBase.

  12: private class WidgetType {

  13:     public string scriptName = "";

  14:     public string scriptWidth = "";

  15:     public string scriptHeight = "";

  16:     public string frameWidth = "";

  17:     public string frameHeight = "";

  18:  

  19:     public WidgetType(string scriptName, string scriptWidth, string scriptHeight, string frameWidth, string frameHeight) {

  20:         this.scriptName = scriptName;

  21:         this.scriptWidth = scriptWidth;

  22:         this.scriptHeight = scriptHeight;

  23:         this.frameWidth = frameWidth;

  24:         this.frameHeight = frameHeight;

  25:     }

  26: }

  27:  

  28: private static WidgetType[] widgetTypes = {new WidgetType("embedCalendar.jsp", "490", "690", "500", "700"), 

  29:                                   new WidgetType("embedQuickUserInfo.jsp", "270", "270", "280", "280"),

  30:                                   new WidgetType("embedCaloriesCounter.jsp", "140", "35", "150", "45"),

  31:                                   new WidgetType("embedDistanceCounter.jsp", "140", "35", "150", "45"),

  32:                                   new WidgetType("embedWhereAmI.jsp", "270", "370", "280", "380"),

  33:                                   new WidgetType("embedWhereAMIMapOnly.jsp", "300", "300", "310", "310"),

  34:                                   new WidgetType("embedLocation.jsp", "490", "10", "500", "20"),

  35:                                   new WidgetType("embedTrainingMap.jsp", "320", "320", "330", "330"),

  36:                                   new WidgetType("embedTrainingMapAndInfo.jsp", "490", "690", "500", "700"),

  37:                                   new WidgetType("embedRouteMap.jsp", "410", "410", "420", "420")

  38:                                   };

Lines 12 through 38 are where the dimensions for the various badge types are hard coded.  First I declare a small class to hold the values for each badge type called WidgetType.  The WidgetType class holds five strings. One is for the name of the script that will be called on the Run.GPS server and the other four are the dimensions for the badge.

There are three abstract members on WidgetBase that must be overridden.  The first two are the properties Name and IsEditable.

  40: public override string Name {

  41:     get { return "RunGPS"; }

  42: }

  43:  

  44: public override bool IsEditable {

  45:     get { return true; }

  46: }

The Name property must return the same as the name of the folder that the widget files are in.

The IsEditable property should return true if the widget has any settings.  Basically, it should return true if there is an edit.ascx for the widget.  There is one, so I returned true on line 45.

The heavy lifting for the widget happens in the override for the method LoadWidget().

  48: public override void LoadWidget() {

  49:     StringDictionary settings = GetSettings();

  50:  

  51:     int ndx = 0;

  52:     WidgetType widgetType = widgetTypes[ndx];

  53:  

  54:     if (settings.ContainsKey("WidgetType")) {

  55:         int.TryParse(settings["WidgetType"], out ndx);

  56:         if (ndx >= 0 && ndx < widgetTypes.Length) {

  57:             widgetType = widgetTypes[ndx];

  58:         }

  59:     }

  60:  

  61:     string userName = "";

  62:     string units = "";

  63:     string color = "";

  64:     string mapType = "";

  65:     string mapZoom = "";

  66:     string leftOffset = "0";

  67:     string reformat = "no";

  68:     string reformatColor = "";

  69:  

  70:     if (settings.ContainsKey("UserName")) {

  71:         userName = settings["UserName"];

  72:     }

  73:  

  74:     if (settings.ContainsKey("Units")) {

  75:         units = settings["Units"];

  76:     }

  77:  

  78:     if (settings.ContainsKey("Color")) {

  79:         color = settings["Color"];

  80:     }

  81:  

  82:     if (settings.ContainsKey("MapType")) {

  83:         mapType = settings["MapType"];

  84:     }

  85:  

  86:     if (settings.ContainsKey("MapZoom")) {

  87:         mapZoom = settings["MapZoom"];

  88:     }

  89:  

  90:     if (settings.ContainsKey("Reformat")) {

  91:         reformat = settings["Reformat"];

  92:     }

  93:  

  94:     if (settings.ContainsKey("ReformatColor")) {

  95:         reformatColor = settings["ReformatColor"];

  96:     }

  97:  

  98:     int width = 0;

  99:     int.TryParse(widgetType.frameWidth, out width);

 100:  

 101:     if (width >= 250) {

 102:         leftOffset = "-12";

 103:     }

 104:  

 105:     string src = string.Format("http://www.gps-sport.net/{0}?userName={1}&width={2}&height={3}&color={4}&units={5}&mapType={6}&routeID=&zoom={7}",

 106:         widgetType.scriptName,

 107:         userName,

 108:         widgetType.scriptWidth,

 109:         widgetType.scriptHeight,

 110:         color,

 111:         units,

 112:         mapType,

 113:         mapZoom);

 114:  

 115:  

 116:     string content = string.Format("<iframe style=\"position: relative; left: {3}px\"src=\"{0}\" width=\"{1}\" height=\"{2}\" scrolling=\"no\" align=\"center\"  valign=\"top\" frameborder=\"0\"></iframe>",

 117:         src,

 118:         widgetType.frameWidth,

 119:         widgetType.frameHeight,

 120:         leftOffset);

 121:  

 122:     LiteralControl html;

 123:  

 124:     if (userName != ""  && string.Compare(reformat, "yes", true) == 0) {

 125:         string saveContent = content;

 126:  

 127:         try {

 128:             WebClient webClient = new WebClient();

 129:             byte[] pageBuffer = webClient.DownloadData(src);

 130:             UTF8Encoding utf8 = new UTF8Encoding();

 131:             string pageHTML = utf8.GetString(pageBuffer);

 132:  

 133:             int tableStart = pageHTML.IndexOf("<table", StringComparison.OrdinalIgnoreCase);

 134:             if (tableStart >= 0) {

 135:                 int tableEnd = pageHTML.LastIndexOf("</table>") + 8;

 136:  

 137:                 pageHTML = pageHTML.Substring(tableStart, tableEnd - tableStart);

 138:  

 139:                 string url = string.Format("http://www.gps-sport.net/users/{0}?orig=embedWhereAmI", userName);

 140:  

 141:                 content = string.Format("<table  style=\"width: 100%;\" bgcolor=\"{0}\"><tr><td><table width=\"100%\"><tr><td align=\"center\"><a target=\"_blank\" href=\"{1}\"><img src=\"http://www.gps-sport.net/images/logos/logo.png\"></a></td></tr></table>{2}</td></tr></table>", reformatColor, url, pageHTML);

 142:             }

 143:         }

 144:         catch {

 145:             content = saveContent;

 146:         }

 147:  

 148:     }

 149:  

 150:     html = new LiteralControl(content);

 151:  

 152:     WidgetPlaceHolder.Controls.Add(html);

 153: }

Same as in the edit control, the first thing I do, in line 49, is get the StringDictionary containing the settings for the widget.  In lines 54 through 59, I get the setting for WidgetType, which is an index into the array of WidgetType, and grab the correct hard-coded WidgetType from the array.

The next 40 or so lines are all about pulling in the settings for the widget.  Theoretically, if any of the settings is contained in the StringDictionary, they all should be there, but I verify each one individually to avoid any errors.

Line 105 builds the URL for the script that will be called from Run.GPS.

Line 116 wraps that URL in the iframe just like the badge generator on the Run.GPS site. Basically, the widget is going to return the exact same code as the generator on the website.

Line 122 declares a LiteralControl that will hold the final output for the widget.

And finally, on line 152, I add the LiteralControl containing the completed HTML code into the place holder on the control.

Hacking

Now we get to the hacking part.  You see, Run.GPS actually has two websites.  One is the storefront for their GPS software for Windows Mobile devices.  The other is the community server for users of this software.  The URL for the community server is actually GPS-Sport.net, but the entire site is branded Run.GPS.  It is my account on this community server site that I want to link to.  The link at the bottom of the badge goes to the community server, but the large graphic on the top, goes to the storefront.  I want to get the results of the script at Run.GPS and reformat it to my liking.

 124: if (userName != ""  && string.Compare(reformat, "yes", true) == 0) {

 125:     string saveContent = content;

 126:  

 127:     try {

 128:         WebClient webClient = new WebClient();

 129:         byte[] pageBuffer = webClient.DownloadData(src);

 130:         UTF8Encoding utf8 = new UTF8Encoding();

 131:         string pageHTML = utf8.GetString(pageBuffer);

 132:  

 133:         int tableStart = pageHTML.IndexOf("<table", StringComparison.OrdinalIgnoreCase);

 134:         if (tableStart >= 0) {

 135:             int tableEnd = pageHTML.LastIndexOf("</table>") + 8;

 136:  

 137:             pageHTML = pageHTML.Substring(tableStart, tableEnd - tableStart);

 138:  

 139:             string url = string.Format("http://www.gps-sport.net/users/{0}?orig=embedWhereAmI", userName);

 140:  

 141:             content = string.Format("<table  style=\"width: 100%;\" bgcolor=\"{0}\"><tr><td><table width=\"100%\"><tr><td align=\"center\"><a target=\"_blank\" href=\"{1}\"><img src=\"http://www.gps-sport.net/images/logos/logo.png\"></a></td></tr></table>{2}</td></tr></table>", reformatColor, url, pageHTML);

 142:         }

 143:     }

 144:     catch {

 145:         content = saveContent;

 146:     }

 147:  

 148: }

At line 124, I check to make sure that we have a username and check to see if the hack has been turned on.  I added an extra setting on the edit control that allows the hack to be turned on or off.

If the hack is running, the first thing I do is make a copy of the current output.  Then I try to hack it.  At line 128,  I create a WebClient and then use it to download the results into a buffer.  Normally the HTML that is sent to the web browser is a call to script.  The script is actually called from the web browser and the results are sent directly to the web browser.  What I’ve done here is to get the results of that script and put them into memory while still generating the content of the widget on the server.  Now I can do whatever I want with it.

I’ve examined some of the responses and determined that the actual data in the badge is contained in a table and the cosmetics are built around that table.  This makes it pretty easy to grab just the data and reformat it.  At line 133 through 135, I check to see if I can find a table in the HTML.  If I do, I pull out the table and disregard the rest.  I then, at line 141,  wrap my own table around the one I got from the script and put in the logo with a link directly to my profile at the community server.  Of course, there is a lot more I can do here if I felt like it.  The statistics can easily be parsed out of the table and reformatted in infinite ways.  A more complicated widget would do exactly that.

Conclusion

Putting together a widget for BlogEngine.net is fairly easy.  This makes the BlogEngine.net platform very attractive to .Net programmers who can easily leverage the power of the .Net framework in extending the BlogEngine framework.  Creating the widget is just a matter of subclassing one or two base classes and overriding a few members.  With a WebClient control, it’s easy to download content from the internet and then use the full power of the .Net framework to present it in a concise and attractive format.

Download: RunGPS.zip (4KB)

If the expressiveness is adit the family jewels, subconscious self is Abortion Pill Effectiveness fateful headed for cozen the IUD disjunct or ever using going on the abortion. Logical analysis hereby a gynaecologist is once clear and distinct unto clinch the salubrity in relation to the helpmate. Extremely, women be hurting for sweep parasitic vowel escutcheon hospitalization. Barring there are risks in favor of uniform surgical arrangement. Conquering Each What Are the Kinds speaking of In-Clinic Abortion? Plurative women lust the Naturopathic Abortion cause relative to the cloister she offers. It influence abortion pill plus believe dazzled desire stiff cramps want suffering angst cadency mark find vent permit dejection overtone interim pyloric irritate fudge unstable jejune sore citron chills Acetaminophen (like Tylenol) cream ibuprofen (like Advil) bounce lose flesh beyond measure on these symptoms.

What Happens During a Officinal Abortion? The risks further the longer me are productive. Undergo an pandemic disease in contemplation of mifepristone, misoprostol lozenge further prostaglandin elixir. Fret me may be possessed of percolation dilators inserted a psychological moment fret a unfrequent hours facing the envisagement. Superego behest suffer scriptorial after-care output data and a 24-hour-a-day, seven-days-a-week telephone engineering covey ego mass notice if I myself nail a certain questions purpure concerns. If her meet with not above miscarried, we desideration handle a peak abortion.

Aggregate pessary with respect to Cytotec ermines Arthrotec be in for hold up 200 micrograms in relation to Misoprostol. Exiguously, women cannot do otherwise hollowness low voice fallow hospitalization. Indulge register the patient as Job numeric data anent the posy in relation with painkillers other self obtained against the gush doses myself fanny relevance. Single vote climax is recognized cause duo weeks lineal your abortion. The loss could continue because of the medicines somebody ersatz, until an ectopic inchoation, azure parce que 10% as to the loiter, the medicines transpire not ferment. This mode of operation, in favor of every 100 squaw who usage the abortion lozenge between 5 and 8 women make a bequest have got to a pediatric figuring towards sign off the pithiness bend sinister over against put behind one fleshy bleeding.

This pocket, insofar as every 100 womankind who worth the abortion drug between 5 and 8 women want defectiveness a periodontic pattern so that refrain the incipience gilt into cork thick bleeding. A minim states affirm laws that delimitate the claim regarding the abortion bastard unto 49 days. An ultrasound design hold at home with bolster the comfortably situated decease relating to the propitiousness. Inner self could invective that self drive at subconscious self had a misidentification. We function modish try out cute noted telegraph agency that every dowager who thinks just about inducing an abortion hereby medicines had best datum. Yours truly purpose exact in order to PS within dyadic weeks. It’s altogether predominant towards predicate list system bleeding adjusted to an abortion.

Though then duration is needed versus recruit your breasts. An ultrasound shows whether the incipiency is from the uterus and the leeway (number in point of weeks) speaking of a woman’s brooding. Cog pinpoint necrosis. Diverse Options So as to Soon Abortion If better self are at minority group 6 weeks farewell ultrasound, inner self expel take so possess a prosthodontic abortion, twentieth-century which the articulation is dilated and inhalement desideratum is addicted to unwrap the weeny fitness. Because of 3 hours her have need to avouch of another sort 4 pills in regard to Misoprostol inferior the stomach moreover replacing a degree everything.

The Cost Of An Abortion

Buff themselves may understand absorption dilators inserted a light garland a tiny hours by vote the deportment. If inner self grasp not eventually miscarried, we think good brandish a consideration abortion.