Enabling MSChart ImageMap and AJAX functionality

    In a previous post I talked about utilizing MSChart in an SSRS report. In this post we will talk about how to use the image map capabilities of the MSChart control. You will need to create two identical charts, one set with a render type of imagemap, the other set to binarystreaming. The easiest way to do this without having duplicate code is to create a user control containing your chart.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="graph.ascx.cs" Inherits="graph" %>
<%@ Register Assembly="System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI.DataVisualization.Charting" TagPrefix="asp" %> 
<asp:chart id="mainChart" runat="server" height="296px" width="412px"
    imagetype="Png" palette="BrightPastel" backcolor="LightBlue" rendertype="BinaryStreaming"
    borderdashstyle="Solid" backgradientstyle="TopBottom" borderwidth="2" bordercolor="181, 64, 1">
    <titles>
        <asp:Title ShadowColor="32, 0, 0, 0" Font="Trebuchet MS, 14.25pt, style=Bold" ShadowOffset="3" Text="Binary Streaming" ForeColor="26, 59, 105"></asp:Title>
    </titles>
    <legends>
        <asp:Legend Enabled="True" IsTextAutoFit="True" Name="Default" BackColor="Transparent" Font="Trebuchet MS, 8.25pt, style=Bold" Alignment="Center" Docking="Bottom"></asp:Legend>
    </legends>
    <borderskin skinstyle="Emboss" />
    <series>
        <asp:Series XValueType="String" Name="Targets" ChartType="SplineArea" BorderColor="180, 26, 59, 105" Color="220, 0, 0, 0" YValueType="Double"/>
        <asp:Series XValueType="String" Name="Actuals" ChartType="SplineArea" BorderColor="180, 26, 59, 105" Color="127, 255, 255, 0" YValueType="Double"/>
        <asp:Series XValueType="String" Name="ActualPoints" ChartType="SplineArea" BorderColor="180, 200, 59, 105" Color="127, 65, 140, 240" YValueType="Double"/>
    </series>
    <chartareas>
        <asp:ChartArea Name="MainChartArea" BorderColor="64, 64, 64, 64" BorderDashStyle="Solid" BackSecondaryColor="White" BackColor="OldLace" ShadowColor="Transparent">
            <axisy linecolor="64, 64, 64, 64">
                <labelstyle font="Trebuchet MS, 8.25pt, style=Bold"/>
                <majorgrid linecolor="64, 64, 64, 64"/>
            </axisy>
            
            <axisx IsMarginVisible="False" linecolor="64, 64, 64, 64">
                <labelstyle font="Trebuchet MS, 8.25pt, style=Bold"/>
                <majorgrid linecolor="64, 64, 64, 64"/>
            </axisx>
        </asp:ChartArea>
    </chartareas>
</asp:chart>

In the code behind of your graph user control you will want to publicly expose the chart control.

 public System.Web.UI.DataVisualization.Charting.Chart MainChart
    {
        get
        {
            return mainChart;
        }
    }

The Page Load event should go about parsing the query string for any data needed to generate the chart itself.

protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            int kpiID = 0;
            int organizationID = 0;
            int programID = 0;
            int fiscalYearID = 0;
            int height = 0;
            int width = 0;

            int.TryParse(Request.QueryString["kpiID"], out kpiID);
            int.TryParse(Request.QueryString["organizationID"], out organizationID);
            int.TryParse(Request.QueryString["programID"], out programID);
            int.TryParse(Request.QueryString["fiscalYearID"], out fiscalYearID);
            int.TryParse(Request.QueryString["height"], out height);
            int.TryParse(Request.QueryString["width"], out width);

            if (height == 0)
                height = 296;

            if (width == 0)
                width = 412;

            mainChart.Height = Unit.Pixel(height);
            mainChart.Width = Unit.Pixel(width);


            string kpiName = Request.QueryString["kpiName"];

            if (kpiID != 0)
                BuildGraph(kpiID, fiscalYearID, organizationID, programID, kpiName);
        }
    }

    The next step is to create two web pages: graph.aspx, and graphImageMap.aspx.  Each page will contain nothing but your newly created graph user control, however, their code behinds will differ slightly.

GraphImage.aspx content:

Page Content for graphImage.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="graphImage.aspx.cs" Inherits="graphImage" %>
<%@ Register src="ascx/graph.ascx" tagname="graph" tagprefix="uc1" %>
<uc1:graph ID="graph1" runat="server" />

Code Behind for graphImage.aspx:

public partial class graphImage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            graph1.MainChart.RenderType = System.Web.UI.DataVisualization.Charting.RenderType.BinaryStreaming;
        }
    }
}

GraphImageMap.aspx content:

Page Content for graphImageMap.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="graphImageMap.aspx.cs" Inherits="graphImageMap" %>
<%@ Register src="ascx/graph.ascx" tagname="graph" tagprefix="uc1" %>
<uc1:graph ID="graph1" runat="server" />

Code Behind for graphImageMap.aspx:

public partial class graphImageMap : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            graph1.MainChart.RenderType = System.Web.UI.DataVisualization.Charting.RenderType.ImageMap;
            graph1.MainChart.ImageLocation = "graphImage.aspx?" + Request.QueryString.ToString();
        }
    }
}

 

In the code behind of the graphImage web page, set the exposed chart control property of the graph user control to binary streaming. In the code behind of the graphImageMap web page, set the exposed chart control property of the graph user control to imagemap. Also, set the ImageLocation property to reference graphImage webpage and pass along the query string. This will ensure that the imagemap page is creating the exact same chart as the image page chart. To try out this new MS Chart functionality, we used JQUERY to setup a real world example. Let’s say you want to pop up an interactive chart when a user hovers over a link on a webpage.

<html>
<head>
 <script type="text/javascript" src="js/jquery-1.4.4.min.js"></script>
    <script type="text/javascript" src="js/jquery-ui-1.8.6.custom.min.js"></script>
    
     <script type="text/javascript">
         function ShowDialog(imgURL) {
             var uniqueTime = new Date().getTime();
             $("#graphImage").attr("src", imgURL + '&time=' + uniqueTime);
             $("#dialog").dialog("open");
         }
         // we will add our javascript code here
         $(document).ready(function() {
             $("#dialog").dialog({
                 autoOpen: false,
                 height: 350,
                 width: 440,
                 modal: false,
                 closeOnEscape: true,
                 show: 'fade',
                 hide: 'fade'
             });


         });

     function ShowDialog(imgURL) {
             $("#graphImage").attr("src", imgURL);
             $("#dialog").dialog("open");
         }
         // we will add our javascript code here
         $(document).ready(function() {

             $(".test").mouseenter(function() {
                 $.ajax({
                     url: "graphImageMap.aspx?kpiID=2004&fiscalYearID=5&organizationID=4&programID=219&kpiname=AmazingMetric",
                     cache: false,
                     async: false,
                     success: function(html) {
                         $("#dialogInner").replaceWith(html);
                     }
                 });
                 $("#dialog").dialog("open");
             });
     </script>  
</head>
<body>
    <a id='link' class="test">hello world</a>
        <div id="dialog" title="My Graph">
            <div id="dialogInner"></div>
        </div>
</body>
</html>

I hope you found this post helpful in creating an interactive MS Charting solution.  Now get coding!

Advertisements

Utilizing MSChart in SSRS

    MSChart has been around for a while now, but we found a new use for it regarding some SSRS (SQL Server Reporting Services) reports we were creating the other day.

    We had a need to generate some charts on the fly based on a particular action taken by a user, like clicking a metric. The MS Chart controls have some really nice capabilities for a free charting package. They support caching, multiple data binding scenarios, and AJAX driven events, just to name a few. We utilized the charts ability to generate a binary stream of data to create our own custom image handler.  In order to try this out yourself, simply create a web page with nothing in it but a chart control like the below example.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="graph.ascx.cs" Inherits="graph" %>
<%@ Register Assembly="System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI.DataVisualization.Charting" TagPrefix="asp" %> 
<asp:chart id="mainChart" runat="server" height="296px" width="412px"
    imagetype="Png" palette="BrightPastel" backcolor="LightBlue" rendertype="BinaryStreaming"
    borderdashstyle="Solid" backgradientstyle="TopBottom" borderwidth="2" bordercolor="181, 64, 1">
    <titles>
        <asp:Title ShadowColor="32, 0, 0, 0" Font="Trebuchet MS, 14.25pt, style=Bold" ShadowOffset="3" Text="Binary Streaming" ForeColor="26, 59, 105"></asp:Title>
    </titles>
    <legends>
        <asp:Legend Enabled="True" IsTextAutoFit="True" Name="Default" BackColor="Transparent" Font="Trebuchet MS, 8.25pt, style=Bold" Alignment="Center" Docking="Bottom"></asp:Legend>
    </legends>
    <borderskin skinstyle="Emboss" />
    <series>
        <asp:Series XValueType="String" Name="Targets" ChartType="SplineArea" BorderColor="180, 26, 59, 105" Color="220, 0, 0, 0" YValueType="Double"/>
        <asp:Series XValueType="String" Name="Actuals" ChartType="SplineArea" BorderColor="180, 26, 59, 105" Color="127, 255, 255, 0" YValueType="Double"/>
        <asp:Series XValueType="String" Name="ActualPoints" ChartType="SplineArea" BorderColor="180, 200, 59, 105" Color="127, 65, 140, 240" YValueType="Double"/>
    </series>
    <chartareas>
        <asp:ChartArea Name="MainChartArea" BorderColor="64, 64, 64, 64" BorderDashStyle="Solid" BackSecondaryColor="White" BackColor="OldLace" ShadowColor="Transparent">
            <axisy linecolor="64, 64, 64, 64">
                <labelstyle font="Trebuchet MS, 8.25pt, style=Bold"/>
                <majorgrid linecolor="64, 64, 64, 64"/>
            </axisy>
            
            <axisx IsMarginVisible="False" linecolor="64, 64, 64, 64">
                <labelstyle font="Trebuchet MS, 8.25pt, style=Bold"/>
                <majorgrid linecolor="64, 64, 64, 64"/>
            </axisx>
        </asp:ChartArea>
    </chartareas>
</asp:chart>

    This allows us to create an external image in our SSRS report with a source expression something like = "http://mysite/graph.aspx?metricID=&quot; & Fields!MetricID.Value & "&fiscalYearID=" & Fields!FiscalYearID.Value from a bound dataset within the report. One gotcha we encountered was the need to setup an unattended execution account on the Reporting Server so that external images could be served up. Learn how to configure the unattended execution account here: Configuring the unattended execution account.  While this is a very flexible way to generate custom images on the fly for reports, we decided we wanted a little more interactivity.

    MSChart supports image maps as well as AJAX callbacks allowing the client to interact with the chart images. We utilized this ability to create custom tooltips for commentary at certain datapoints.  This can be accomplished by iterating through the individual datapoints of your series and setting the MapAreaAttributes property.

mainChart.Series["ActualPoints"].Points[count].MapAreaAttributes = "onmouseover=\"DisplayTooltip('" + toolTip + "');\" onmouseout=\"DisplayTooltip('');\"";

    If you intend to use the image map or AJAX callback support, you will be unable to use the image handler approach since the chart will need to generate an image map and possibly an update panel to perform AJAX events. We will address using the image map and AJAX callback functionality in a future post.

Silverlight Pivot Viewer – Automatic Pivot Collection Generation via Customized Pauthor tool

    The Microsoft Silverlight Pivot Viewer is a new innovative way to look at a massive amount of data in a fast visually comparative way. PivotViewer utilizes Microsoft’s Deep Zoom technology which enables it to take advantage of high resolution imagery without taking a hit on performance.

    Lately, we have been utilizing PivotViewer for Business Intelligence applications. If you are using PivotViewer to simply navigate and categorize images, the images themselves aren’t very important. However, if you are intending to utilize PivotViewer for a BI application, you will want to communicate the business information visually, that is the point after all, isn’t it? There are several ways to generate PivotViewer collections. The pivot collection creator for excel (here) provides a fast, simple way to generate PivotViewer collections. The main drawback of the excel tool is that the images for each item must already exist, plus this is still a very manual process. This is where the Pauthor tool comes into play.

    The Pauthor tool is an open source command line tool for generating pivot collections from various formats, such as excel, csv, and cxml formats. Pauthor allows for the generation of pivot images from an html template, as well as the creation of new data sources and target sources that implement an OLE DB driver. We created a SQL Server datasource based on the OleDBCollectionSource.  This allowed us to use tables, or views, or even stored procedures to generate the pivot collection facet categories, items, and collection text.

public class SqlCollectionSource : OleDbCollectionSource
    {
        /// <summary>
        /// Creates a new Excel collection source and sets its <see cref="BasePath"/>.
        /// </summary>
        /// <param name="basePath">the path to the Excel file containing the collection's data</param>
        public SqlCollectionSource(String serverName, String dbName, String collectionTableName, String facetTableName, String itemTableName)
            : base(String.Format(ConnectionStringTemplate, serverName, dbName), ".")
        {
            m_serverName = serverName.ToLowerInvariant();
            m_dbName = dbName.ToLowerInvariant();
            m_collectionTableName = collectionTableName.ToLowerInvariant();
            m_facetTableName = facetTableName.ToLowerInvariant();
            m_itemTableName = itemTableName.ToLowerInvariant();
        }

        protected override void LoadHeaderData()
        {
            this.ConnectionString = String.Format(ConnectionStringTemplate, m_serverName, m_dbName);
            this.UpdateDataQueries();

            base.LoadHeaderData();
        }

        private void UpdateDataQueries()
        {
            //String connectionString = String.Format(ConnectionStringTemplate, this.BasePath);
            using (OleDbConnection connection = new OleDbConnection(this.ConnectionString))
            {
                connection.Open();
                DataTable schema = connection.GetOleDbSchemaTable(
                    OleDbSchemaGuid.Tables, new Object[] { null, null, null, "TABLE" });
                
                String firstTableName = null;
                foreach (DataRow row in schema.Rows)
                {
                    String table = row["Table_Name"].ToString();
                    String lowerTable = table.ToLowerInvariant();
                    if (lowerTable == m_collectionTableName)
                    {
                        this.CollectionDataQuery = String.Format(CommandTemplate, table);
                    }
                    if (lowerTable == m_facetTableName)
                    {
                        this.FacetCategoriesDataQuery = String.Format(SortedCommandTemplate, table);
                    }
                    if (lowerTable == m_itemTableName)
                    {
                        this.ItemsDataQuery = String.Format(SprocTemplate, table);
                    }
                    if (firstTableName == null) firstTableName = table;
                }

                schema = connection.GetOleDbSchemaTable(
                    OleDbSchemaGuid.Procedures, new Object[] { null, null, m_itemTableName, null });

                
                foreach (DataRow row in schema.Rows)
                {
                    String table = row["Procedure_Name"].ToString();
                    table = table.Remove(table.IndexOf(';'));
                    String lowerTable = table.ToLowerInvariant();
                    
                    if (lowerTable == m_itemTableName)
                    {
                        this.ItemsDataQuery = String.Format(SprocTemplate, table);
                    }
                
                }

                if (this.ItemsDataQuery == null)
                {
                    this.ItemsDataQuery = String.Format(CommandTemplate, firstTableName);
                }
            }
        }

        private const String ConnectionStringTemplate = "Provider=SQLNCLI10; Server={0};" +
            "Database={1}; Trusted_Connection=yes;";

        private const String CommandTemplate = "SELECT * FROM [{0}]";
        private const String SortedCommandTemplate = "SELECT * FROM [{0}] order by sort";
        private const String SprocTemplate = "exec [{0}]";
        private String m_serverName = string.Empty;
        private String m_dbName = string.Empty;
        private String m_collectionTableName = string.Empty;
        private String m_facetTableName = string.Empty;
        private string m_itemTableName = string.Empty;
    }

Customizing the HTML template functionality

    The base HTML template functionality allows you to specify an html file incorporating meta tags like {name} {href} and any other custom pivot facet categories your items use within the html itself. Pauthor then iterates through the collection item datasource and creates a new html file for each one by replacing all of these tags with the item data source values. A web browser control is then run within the code to load up the newly generated html file and save an image out to the file system. Deep zoom images are then created for the pivot collection based off of this saved custom image as well as the xml of the pivot collection itself. We also customized the html template functionality allowing for a template query string to be used.

Example: /html-url http://www.example.com/PivotCard?itemPrimaryKey={itemPrimaryKey}

    This immediately increases the flexibility of the images we create, as we can now generate any html we want using standard web technology based on the values we receive off of the query string as opposed to scattering meta tags in an html file. Managing a web page which generates these Pivot Images is much easier to maintain than trying to generate images manually using a graphics library. You can imagine the time involved in changing an image template, moving lines around, drawing text with wrapping capabilities, etc, each time your Pivot Card format changes.

Pivot Collection Generation

    Pivot collection generation is now a breeze! There are no longer any manual steps with having our data in a database and images automatically generated and saved on the web server which serves up the pivot collection.  Collection generation can now be kicked off with a nightly scheduled task or even more frequently if needed. We are currently working on creating a means of generating pivot cards on a one off basis so that the collection can be maintained in near real time without incurring the cost of generating the entire collection each time an item changes. Stay tuned!

Pixel Shaders w/ Source (And a demo!)

In an earlier post, I showed some still images of Silverlight pixel shaders and how we used the awesome tool Shazzam in our development.  Today I’d like to post the code and show some interesting uses of the shaders.  (Live!)

Let’s start with the code for the Telescopic Blur effect.  Here’s the relevant HLSL:

/// &lt;summary&gt;Center X of the Zoom.&lt;/summary&gt;
/// &lt;minValue&gt;0&lt;/minValue&gt;
/// &lt;maxValue&gt;1&lt;/maxValue&gt;
/// &lt;defaultValue&gt;0.5&lt;/defaultValue&gt;
float CenterX : register(C0);

/// &lt;summary&gt;Center Y of the Zoom.&lt;/summary&gt;
/// &lt;minValue&gt;0&lt;/minValue&gt;
/// &lt;maxValue&gt;1&lt;/maxValue&gt;
/// &lt;defaultValue&gt;0.5&lt;/defaultValue&gt;
float CenterY : register(C1);

/// &lt;summary&gt;Amount of zoom blur.&lt;/summary&gt;
/// &lt;minValue&gt;0&lt;/minValue&gt;
/// &lt;maxValue&gt;3&lt;/maxValue&gt;
/// &lt;defaultValue&gt;2.5&lt;/defaultValue&gt;
float BlurAmount : register(C2);

sampler2D input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR

{
    float4 c = 0;
    uv.x -= CenterX;
    uv.y -= CenterY;

    float distanceFactor = pow(pow(uv.x,2) + pow(uv.y, 2),2);

    for(int i=0; i < 15; i++)
    {
        float scale = 1.0 - distanceFactor * BlurAmount * (i / 30.0);
        float2 coord = uv * scale;
        coord.x += CenterX;
        coord.y += CenterY;
        c += tex2D(input,coord);
    }

    c /= 15;
    return c;
}

It takes 3 inputs.  By altering the center of the zoom, you can do some fun stretching motions.  As you’ll see in the demo below, it turned out to be a neat way to make text fly into view.  You can also alter the amount of zoom blur.  I’ll leave it up to the reader to play with this within Shazzam.

Next we have the underwater effect:

sampler2D input : register(s0);

float Timer : register(C0);

static const float2 poisson[12] =
{
        float2(-0.326212f, -0.40581f),
        float2(-0.840144f, -0.07358f),
        float2(-0.695914f, 0.457137f),
        float2(-0.203345f, 0.620716f),
        float2(0.96234f, -0.194983f),
        float2(0.473434f, -0.480026f),
        float2(0.519456f, 0.767022f),
        float2(0.185461f, -0.893124f),
        float2(0.507431f, 0.064425f),
        float2(0.89642f, 0.412458f),
        float2(-0.32194f, -0.932615f),
        float2(-0.791559f, -0.59771f)
};

float4 main(float2 uv : TEXCOORD) : COLOR
{
	float2 Delta = { sin(Timer + uv.x*23 + uv.y*uv.y*17)*0.02 , cos(Timer + uv.y*32 + uv.x*uv.x*13)*0.02 };

    float2 NewUV = uv + Delta;

	float4 Color = 0;
	for (int i = 0; i < 12; i++)
	{
	   float2 Coord = NewUV + (poisson[i] / 50);
       Color += tex2D(input, Coord)/12.0;
     }
     Color += tex2D(input, uv)/4;
     Color.a = 1.0;
     return Color;
}

Its input is a timer value, so that it can be animated.  If you compile this into a Silverlight effect, you can make the timer be a DependencyProperty, which allows the effect to be animated by Storyboard.  I love XAML.

While this is a “passable” underwater effect (and I’m still working on a better one), I found an unanticipated use for it.  But first, a digression.

A year or two ago, LucasArts re-released their classic game The Secret of Monkey Island for the XBox.  I played this game extensively as a teenager, so of course I downloaded the new version and played through again, to see what they had done with the new capabilities of modern hardware.  I wasn’t disappointed.  While upgrading the look, they had stayed faithful to the design spirit of the original.  And one of the most impressive effects was the Ghost Pirate LeChuck.  He had a wavy, wispy quality.  (As shown here.)  It was fluid and animated and very impressive, and at the time I didn’t know how they did it.

Fast forward a year or two and we’re working on the underwater shader.  On a whim, I applied it to a white square in front of a black background.  Whoah.  Now I knew.  So we put together a quick ghost demo using the underwater effect and you can see it, along with the telescopic effect used as a transition, by clicking here.

Click for Pixel Shaders Demo

Click for Pixel Shaders Demo

ASP.NET MVC: Don’t Fear the ViewBag

As we continue to work on our ASP.NET MVC3 project, I’m continuing to be impressed with how quick and lean the development process has gone so far.  Already, our small team is swarming and are empowered with figuring out the problems that are common at the start of a project.  One of these was a UI requirement that turned out to be an eye-opening use of one of MVC3’s more divisive features:   ViewBag.

Why the divisiveness?  Because the ViewBag is a dynamically-typed object.  If you’re a Ruby, Python, or Javascript programmer, you know all about everything being a nail.  For C# programmers comforted by a strongly-typed language, this is the type of stuff that we lose sleep on.  Properties on a dynamic object are defined at run-time, so to add a new property you just use it!  In fact, one of the first uses of this that you come across in an MVC project is the following:

@{
	ViewBag.Title = "My Home Page";
}

What is “Title”?  It’s a property on the dynamic ViewBag that is defined as soon you get or set the property.  We had this in prior verisons of MVC as ViewData, but notice the difference.

ViewData["MyObj"] = myObj;
MyObject o = ViewData["Title"] as MyObject;

ViewBag.MyObj = myObj;
var o = ViewBag.MyObj;

The ViewData was object typed, so you had to always cast it when trying to get the value.  This usually lead to a mess if you abused the ViewData and ended up with casts all over the place.

Now, let me say that I feel that dynamic objects in a strongly-typed language should be treated as if they were loaded weapons:  know how to use it, use it only if necessary, and keep the safety on!  So it was the other day that we found a great use to fire our ViewBag!!

We had a UI requirement that depending on where the user was in the site, that the color of some elements would change.  This was purely view-related logic and had nothing to do with our underlying model or business logic.  We quickly brought up a few options:

  • Pass some string value via the Controller using view models
  • Pass some string value via the Controller using the ViewBag
  • Use JQuery to do some CSS toggling
  • Create an extension method when creating the object that would determine the right CSS string

None of these options “smelled” right.  I didn’t want a pure view-related item like a CSS class name in my Controller code, JQuery seemed silly, and an extension method would require compiling code if something changed.  Then, I had a quick glance at that ViewBag.Title code and it clicked!  At the top of each of our sub-layout pages, we add this line of code that specifies the CSS class to be use to customize the color of our element for all pages that derive from the sub-layout page.

@{
    ViewBag.NavStyle = "nav_color_blue";
}

Then in our partial view that renders the element:

<a class="nav_global @ViewBag.NavStyle" href="@Url.Action(" Index", "Home")">home</a>

In minutes, we had our solution for this purely view-only requirement and didn’t have to clutter up code in our Controller or add silly workarounds.  This fits!  And if for some reason a page does not define the NavStyle property, it just defaults to empty string and the default color in the first CSS class.

So I have found a great use for ViewBag that doesn’t make the strongly-typed hairs on my back to raise in horror.  And you know what?  It felt gooooooood!  Yes, I will continue to make sure this dynamic object is used only if absolutely needed, but in this case we embraced the “get ‘er done” attitude and completed this requirement quickly and in a way that makes sense.  I look forward to other creative ways we can use the dynamic property to solve view-related issues and requirements.

TestFlight

TestFlight | iOS beta testing on the fly“Quick.Painless.Magical.”

The official tagline for TestFlight gets an easy 9 out of 10 on our “Affirma Scale of Tagline Boldness”.  After updating our workflow here at Affirma to leverage some of TestFlight’s goodness I can safely say we are believers.

Basically, Testflight lets us a do a few things that are otherwise time consuming and less fun than digging into the applications we develop:

  • Enroll people into our beta tests
  • Easily manage our beta testers
  • Deliver the .IPA’s to our beta testers
  • Push out updated builds to beta testers

We did these things before, but using TestFlight we are able to manage the processes with less effort.  This is a clear win.

In addition to easing of our workload, this process is easier on our beta testers.  We have clients that span a wide spectrum of technical abilities.  For some, the process of loading beta apps on their iOS devices was challenging.  In the past we were certainly able to walk them through the process, but in our time with TestFlight there has been a measurable decrease in support requests.  Let’s call this a win for Affirma and win for our testers.  Double wins are rare and TestFlight made it possible.

You can learn more about Testflight at their site testflightapp.com

If you want to learn more about Affirma Consulting’s mobile practice email us.

SharePoint – Getting a Fresh Start to Save from Future Pain

As great as SharePoint 2010 is, it is not a magic bullet.  You can’t just push a button and have an intranet up and running.  Well, it is nearly that easy, but having it up and running successfully is another story.   In the past 3 years of working on dozens upon dozens of  SharePoint 2007 and 2010 projects I have run into many instances of the same problem:  Clients hear SharePoint is easy install, they rush to get it up and running (understandably they are excited to do so!), and then give all employees full reign to do what they want with it.  End results:  An IA nightmare.  A perfect example is a client we worked with that wanted to migrate from 2003 to 2007.  We came in and found that they had over 1800 subsites built, subsites on top of subsites, and the permissions structure was such a disaster (not utilizing SharePoint groups at all) that they didn’t know how even to obtain access to a majority of the sites.

When it gets to this point I strongly recommend starting from scratch – migration is not always the best answer.  Inevitably so much time, money and effort is spent trying to undo the mess post-migration that it is difficult if not impossible to rectify.

Many clients do not want to spend a lot of money on an intranet site because they don’t see the financial benefit in doing so.  However, it is a lot less expensive to plan and do it right then ending up with this type of mess.  Often times a phased approach works great.  Finding a group in your organization that really is anxious to get the intranet going, one that understands the value of losing the shared drives and the benefits of managing data, collaborating and sharing on the web is a great way to start.  It is less overwhelming for you, as well as the organization as a whole and ensures that the efforts keep moving forward.

%d bloggers like this: