GoalSystem, Delves - Character Sheet

08.25.13

GoalSystem, Delves - Character Sheet, is a web application design to build and store characters for the table top game GoalSystem, Delves.

Requirements:

  • Registration should be painless.
  • Creating characters should be easy.
  • Site design should be clean and simple.
  • Site needs to be done in under 100 hours.

Implementation:

GoalsSheet.com was developed in less than a month by a single developer(me) and as a side project. The goal was to learn ASP.Net MVC 4, Twitter Bootstrap, and Entity Framework. I tried  make registration as painless as possible. I choose to use OAuth Web Security and used both Google and Facebook providers. To provide a clean and simple design I used Twitter Bootstrap’s scaffolding and forms templates. To speed up development I built a framework that used an adapter pattern over entity framework models. This allowed me to  create  basic CRUD views for any entity model in about 10 lines of code. I choose SQL Server 2005  for my backend because it was familiar and all ready setup on my web server.

Technology Overview:

GoalsSheet.com was build with ASP.Net MVC 4, jquery 1.8, Twiter Bootstrap 2.3.2, OAuth, MS SQL 2005 and Entity Framework 4.4

Screen Shot:

goalsSheet

Posting Xml and Json MVC 2 Controller Actions

01.19.12

Problem:

You need to submit Xml or Json via Post/Put to your ASP.net MVC 2 Action. The request body must also bind to your models and validate.

Solution:

Write a custom value provider, this is a factory that handles the mapping of the request to forms dictionary. You just inherit from ValueProviderFactory and handle the request if it is of type “application/json” or “application/xml.”

More Info:

Phil Haack

MSDN

StackOverflow.com

CODE:

Add your custom value providers in Global.asax.cs in the method OnApplicationStarted().

protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);

ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
ValueProviderFactories.Factories.Add(new XmlValueProviderFactory());
}

JsonValueProviderFactory.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Web.Mvc;
using System.Web.Script.Serialization;

public class JsonValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var deserializedJson = GetDeserializedJson(controllerContext);

if (deserializedJson == null) return null;

var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

AddToBackingStore(backingStore, string.Empty, deserializedJson);

return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}

private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
{
var dictionary = value as IDictionary<string, object>;
if (dictionary != null)
{
foreach (KeyValuePair<string, object> keyValuePair in dictionary)
AddToBackingStore(backingStore, MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value);
}
else
{
var list = value as IList;
if (list != null)
{
for (var index = 0; index < list.Count; ++index)
AddToBackingStore(backingStore, MakeArrayKey(prefix, index), list[index]);
}
else backingStore[prefix] = value;
}
}

private static object GetDeserializedJson(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/json", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;

var input = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();

if (string.IsNullOrEmpty(input)) return null;

return new JavaScriptSerializer().DeserializeObject(input);
}

private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}

private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}
}

XmlValueProviderFactory.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Xml;
using System.Xml.Linq;

public class XmlValueProviderFactory : ValueProviderFactory
{

public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var deserializedXml = GetDeserializedXml(controllerContext);

if (deserializedXml == null) return null;

var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

AddToBackingStore(backingStore, string.Empty, deserializedXml.Root);

return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);

}

private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, XElement xmlDoc)
{
// Check the keys to see if this is an array or an object
var uniqueElements = new List<String>();
var totalElments = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (!uniqueElements.Contains(element.Name.LocalName))
uniqueElements.Add(element.Name.LocalName);
totalElments++;
}

var isArray = (uniqueElements.Count == 1 && totalElments > 1);


// Add the elements to the backing store
var elementCount = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (element.HasElements)
{
if (isArray)
AddToBackingStore(backingStore, MakeArrayKey(prefix, elementCount), element);
else
AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
}
else
{
backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
}
elementCount++;
}
}


private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}

private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}

private XDocument GetDeserializedXml(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase))
return null;

XDocument xml;
try
{
var xmlReader = new XmlTextReader(controllerContext.HttpContext.Request.InputStream);
xml = XDocument.Load(xmlReader);
}
catch (Exception)
{
return null;
}

if (xml.FirstNode == null)//no xml.
return null;

return xml;
}
}

SQL Server 2008 and Non-ANSI Joins

04.15.11

Problem:

You are upgrading your old SQL server to be compliant with SQL Server 2008 but you are getting errors on incompatible non-ANSI outer join operators ("*=" or "=*").

Solution:

Replace these outer joins with the following syntax.

Left Join:

SELECT e.*, a.AttatchmentPath, a.FileSize
FROM InboxEmails e, EmailAttachments a
WHERE a.EmailID =* e.EmailID

-- The Fix
SELECT e.*, a.AttatchmentPath, a.FileSize
FROM InboxEmails AS e
LEFT JOIN EmailAttachments AS a
ON a.EmailID = e.EmailID

Right Join:

SELECT e.*, a.AttatchmentPath, a.FileSize
FROM InboxEmails e, EmailAttachments a
WHERE a.EmailID *= e.EmailID

-- The Fix
SELECT e.*, a.AttatchmentPath, a.FileSize
FROM InboxEmails AS e
RIGHT JOIN EmailAttachments AS a
ON a.EmailID = e.EmailID

Gotcha: The old way of doing the joins handles nulls differently. There is some pixy dust in how sql limts the amount of record returned. The new syntax removes rows much earlier in the process eliminating rows with nulls that may have showed up in the older style of joins.

Tournament Software–Maintenance and Development

10.03.10

Tournament Software is an application built to organize and manage the progression of a bowling tournament and deliver scoring statistics to fans and players. Originally the software was built with .net 1.1, winforms and .asmx web services and only supported one type of tournament. As technologies and the PBA business evolved so did the software. Changing tournament software was increasingly difficult and stability became an issue. To help solve this I implemented software best practices such as automated testing, continues integration and software engineering design patterns.

Requirements:

  • Introducing new functionality should not be overly complex.
  • Existing functionality needs to be easily and quickly verified when new functionality is introduced.
  • Deploying new functionality should be one click and not require manual steps.
  • Need to be able to persist and test different states of the software.

Implementation:

Automated tests where written with NUnit and automatically ran on each commit using CuiseControl. Automated build and deployment was handled using combination of NAnt, ClickOnce, and MSBuild. The strategy pattern was used combined with constructor injection to easily swap data sources allowing easy creation of test cases. Command pattern was used to provide a means extending tournament software functionality.

Technology Overview:

Tournament Software is a multi tiered C# .net 3.5 application. Data access layer is written with ADO.Net, Service layer uses asmx .net 2.0 web services and GUI client is written in Winforms.

MVC Model Binding Complex Collections Gotcha

09.15.10

Problem:

Despite using proper MVC binding HTML conventions values are not being bound correctly to your list. The list may have the correct number of objects but all the value are set to default.

Solution:

Use properties instead of public variables.

Correct Html Markup.

<div>Id</div>
<div>
    <input id="Products_0__Id"
           name="Products[0].Id"
           type="text"
           value="1" />
</div>
<div>Name</div>
<div>
    <input id="Products_0__Name"
           name="Products[0].Name"
           type="text"
           value="Product 1" />
</div>
<div>Quantity</div>
<div>
    <input id="Products_0__Quantity"
           name="Products[0].Quantity"
           type="text"
           value="2" />
</div>
<div>Unit Price</div>
<div>
    <input id="Products_0__UnitPrice"
           name="Products[0].UnitPrice"
           type="text"
           value="200.00" />
</div>

Incorrect View Model:

public class Basket
{
    public List<Product> Products { get; set; }
    public Totals Totals { get; set;}
}
public class Product
{
    public int Id;
    public string Name;
    public int Quantity;
    public decimal UnitPrice;
}

Correct View Model:

public class Basket
{
    public List<Product> Products { get; set; }
    public Totals Totals { get; set;}
}
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

Reading a String from a Stream

07.02.10

Problem: You need to read some text from a stream of some sort.

Solution: Use StreamReader class , set the postion to 0 and call ReadToEnd().

Code!

 

static string GetStringFromStream(Stream inputStream)
{
var pos = inputStream.Position;
var reader = new StreamReader(inputStream);
inputStream.Position = 0;
var text = reader.ReadToEnd();
inputStream.Position = pos;
return text;
}

My Azure Resources List

07.01.10

Just throwing out some resources I like to reference for windows azure.

Tools

Blob Storage Explorer – CloudXplorer

Web based Table Storage Explorer

Windows Azure Service Management CmdLets

Cloud Storage Studio

HedgehogDev Azure Cloud Application Monitor

Azure Tool kit for Facebook

Lokad Cloud – Cloud Platform Abstractions cool stuff like Auto Scaling.

Install Azure

Web Platform Installer

Educational

Steve Marx – Microsoft Azure Dude

Cloud Cover – Channel 9 Videos on Cloud Platform

Azure Training Kit

Azure Deployment for your Build Server

Returning Json in ASP.Net MVC

06.22.10

Problem: You want to return a Json object to your page using ASP.Net MVC.

Solution: Use JQuery.getJSON and JsonActionResult.

CODE:

View HTML

<ul id="jsonRequestFun">
</ul>
<a href="#" id="requestFun">Json is fun</a>

View Javascript

<script type="text/javascript">
$(function () {
$("#requestFun").click(function () {
$.getJSON("/home/jsonfun", null, function (data) {
$("<li>" + data.Message + "</li>").appendTo("#jsonRequestFun");
$("<li>" + data.Time + "</li>").appendTo("#jsonRequestFun");
});
});
});
</script>

Handy tool to view the request in IE is nikhilk.net Web development Helper

Once installed the tool is a bit hidden you can find it at Tools>Explorer Bars>Web Developer Helper

openDevHelper

Enable Logging to view the request.

webdevhelper

Model:

public class JsonData
{
public string Message { get; set; }

public string Time { get; set; }
}

 

Controller Action:

Beware of this little gotcha if you do not pass the AllowGet your requests will be ignored.

public ActionResult JsonFun()
{
var data = new JsonData
{
Message = "Hello World",
Time = DateTime.Now.ToShortTimeString()
};
return Json(data, JsonRequestBehavior.AllowGet);
}

MSpec + Resharper

06.08.10

1. Download MSpec

2. Download Resharper Template

3. Setup Reshaper style to work with MSpec.

resharperMspecSettings1

resharperMspecSettings2

Salesforce Destination

05.26.10

Problem: You need to submit salesforce sObjects including custom fields to many different organization’s salseforce instances.

Solution: Use the Partner WSDL, it's loosely typed and can be used with all organizations and  whatever customizations they have.

 

Code:

//Base class 
public class SalesforceBase
{
private readonly List<SalesforceCustomField> _customFields = new List<SalesforceCustomField>();
private readonly XmlDocument _document = new XmlDocument();
private readonly List<XmlElement> _elements = new List<XmlElement>();
private readonly string _type;

protected SalesforceBase(string type)
{
_type = type;
}

public string Type
{
get
{
return _type;
}
}

public IEnumerable<SalesforceCustomField> CustomFields
{
get
{
return _customFields;
}
}

public XmlElement[] Any
{
get
{
return _elements.ToArray();
}
}

public void AddCustomField(SalesforceCustomField customField)
{
UpdateElements(customField.FieldName, customField.Value);
_customFields.Add(customField);
}

protected void UpdateElements(string name, string value)
{
var element = _elements.Find(ele => ele.Name == name);
if (element != null)
{
element.InnerText = value;
}
else
{
element = _document.CreateElement(name);
element.InnerText = value;
_elements.Add(element);
}
}
}
//Implement Base
public class SalesforceContact : SalesforceBase
{
public SalesforceContact() : base("Contact") { }

private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (value != null)
{
UpdateElements("FirstName", value);
_firstName = value;
}
}
}
}
//Send It
SForceService.create( new sObject(){Any = contact.Any, type = contact.Type});