As I entered the pre-season draft this past weekend, I set forth my strategy, and knew I had some resources that would assist me in getting the best players available to me. I used the ample resources available to me, and built a simple single-page application to track the draft. With Telerik’s ASP.NET AJAX controls at my disposal, I knew I could quickly put together a very responsive and speedy application that I could control with just the touchscreen on my Lenovo Thinkpad X1 Carbon Touch. This is the brief story of that application, and I’ve shared the source code at the end of the article for you to download and use for your draft.
The first question about any fantasy application is always “where to get the data”? I located and downloaded a collection of tab-delimited cheat sheets from several locations and put them in the import folder of the solution as a set of position specific named files. The data is very simple, with name, NFL team, age, years of experience and a projected point value for each professional player listed.
For this application, I didn’t want to spend a lot of time thinking about database structure and management. I chose to use the NoSQL MongoDB database. This would allow me to store my data in the same model that I create it in, and not worry about relational translations. I really only needed to store one set of data: the list of professional players in the NFL. I defined my player class as a ProPlayer with the following syntax:
public
class
ProPlayer
{
[BsonId()]
public
ObjectId Id {
get
;
set
; }
[BsonElement(
"r"
)]
public
int
Rank {
get
;
set
; }
[BsonElement(
"p"
)]
public
string
Pos {
get
;
set
; }
public
string
Name {
get
;
set
; }
[BsonElement(
"nfl"
)]
public
string
NflTeam {
get
;
set
; }
[BsonElement(
"bye"
)]
public
int
ByeWeek {
get
;
set
; }
[BsonElement(
"i"
)]
public
string
Injury {
get
;
set
; }
public
int
Age {
get
;
set
; }
public
int
Exp {
get
;
set
; }
[BsonElement(
"pts"
)]
public
decimal
ProjectedPoints {
get
;
set
; }
[BsonElement(
"ffl"
)]
public
int
? FantasyTeamId {
get
;
set
; }
[BsonIgnore]
public
FantasyTeam FantasyTeam
{
get
{
if
(FantasyTeamId.HasValue)
{
return
FantasyTeam.Teams.First(t => t.Id == FantasyTeamId);
}
return
null
;
}
set
{
FantasyTeamId = value.Id;
}
}
[BsonIgnore]
public
string
FantasyOwner
{
get
{
return
this
.FantasyTeam ==
null
?
""
:
this
.FantasyTeam.Owner;
}
}
public
static
class
Position
{
public
const
string
Quarterback =
"QB"
;
public
const
string
Runningback =
"RB"
;
public
const
string
WideReceiver =
"WR"
;
public
const
string
TightEnd =
"TE"
;
public
const
string
Kicker =
"K"
;
public
const
string
Defense =
"DT"
;
}
}
Note the various Bson* attributes on the properties in this class. These attributes instruct the MongoDb serializer how to place these values into their JSON formatted fields in the Mongo database. Those properties with a value will use that field name, and those without a value will use a field name that matches the property name. I chose very short names for the fields (typically a standard abbreviation or just a first initial), as the fieldname is part of the space occupied by each record’s JSON document. Short names means that the document size is smaller, and the collection can be searched more quickly in memory.
The properties marked BsonIgnore are not to be included in serialization, and as you can see most of those fields are derived from other fields that are persisted to the data-store. Finally, the BsonId attribute shows which property is the primary key.
I decided against writing a list of fantasy teams to the database. This seemed like overkill, when I could just list them in a class to be presented on screen. The fantasy teams were structured like the following:
public
class
FantasyTeam
{
public
FantasyTeam(
int
? id,
string
o)
{
Owner = o;
Id = id;
}
public
string
Owner {
get
;
private
set
; }
public
int
? Id {
get
;
private
set
; }
public
static
FantasyTeam[] Teams =
new
[]
{
new
FantasyTeam(
null
,
"-- None --"
),
new
FantasyTeam(1,
"Team1"
),
new
FantasyTeam(2,
"Team2"
),
new
FantasyTeam(3,
"Team3"
),
new
FantasyTeam(4,
"Team4"
),
new
FantasyTeam(5,
"Team5"
),
new
FantasyTeam(6,
"Team6"
),
new
FantasyTeam(7,
"Team7"
),
new
FantasyTeam(8,
"Team8"
)
};
}
This is a very simple object, and I created an array of teams in the order that they would be drafting at our event. I have removed the names of the owners from my source code to “protect the innocent” and silly owners in my league who I believe will get trounced this year. J
With this simple data structure, I know which players are assigned if they have a FantasyTeamId property set. This would be key in my client-side filter that I would build.
I wrote a quick and dirty console application that would read each line from my player files and create players to write to the database. I used some test-driven techniques with the development of this in order to ensure I didn’t load bad data.
My PlayerRepository would contain an Add, Get, and Update method for the ProPlayer class. I extracted the Repository to an interface so that I could mock it with JustMock to verify that my repository was called with appropriate arguments. The IPlayerRepository interface would look like this:
public
interface
IPlayerRepository
{
void
Add(ProPlayer newPlayer);
}
I only needed to mock the one method, and as a believer in YAGNI I only extracted that method to the interface. With that structure in place, I could write unit tests like the following:
[TestFixture]
public
class
AddPlayer
{
[Test]
public
void
CleanPlayerShouldAdd()
{
// Arrange
var line =
"1 Drew Brees NO 7 34 12 436.7"
;
var repo = Mock.Create<IPlayerRepository>();
repo.Arrange(r => r.Add(Arg.Matches<ProPlayer>(p => p.Rank == 1 &&
p.Name ==
"Drew Brees"
&&
p.Pos ==
"QB"
&&
p.NflTeam ==
"NO"
&&
p.ByeWeek == 7 &&
p.Age == 34 &&
p.Exp == 12 &&
p.ProjectedPoints == 436.7M))).OccursOnce();
// Act
var sut =
new
ImportHandler(repo);
sut.AddPlayer(line,
"QB"
);
// Assert
repo.Assert();
}
}
This approach worked flawlessly, and showed me where I had issues in constructing my import mechanism. Within 30 minutes of iterating and coffee drinking, I had a simple file loader ready to go for my application. My unit tests ran quickly in JustCode, and I was happy with the results.
I wrote a helper method to make the database connection to Mongo and then constructed an Add method on the real PlayerRepository to get a MongoCollection and add the Player document to the collection. Two lines of code couldn’t be easier:
public
class
PlayerRepository : IPlayerRepository
{
public
void
Add(ProPlayer newPlayer)
{
var coll = GetCollection<ProPlayer>();
coll.Insert(newPlayer);
}
/// <summary>
/// Gets the collection.
/// </summary>
/// <returns></returns>
private
MongoCollection<T> GetCollection<T>()
{
var server = MongoServer.Create(
"mongodb://localhost:27017"
);
var db = server.GetDatabase(
"ffl"
);
return
db.GetCollection<T>(
typeof
(T).Name);
}
}
The final actions I would need on my PlayerRepository are those to Get players and Update them. With the MongoDb C# driver, the API was just the syntactic sugar I was looking for. I wrote a Get method that would take a Func parameter so that I could specify any query I needed outside of the repository and submit it to the Mongo database for execution. I constructed the Update method to do a simple save of the only value that would be submitted – a FantasyTeamId:
public
void
Update(ProPlayer player)
{
var coll = GetCollection<ProPlayer>();
var thisPlayer = coll.FindOneByIdAs<ProPlayer>(player.Id);
thisPlayer.FantasyTeamId = player.FantasyTeamId;
coll.Save(thisPlayer);
}
public
IEnumerable<ProPlayer> Get(Func<ProPlayer,
bool
> where)
{
var coll = GetCollection<ProPlayer>();
return
coll.FindAllAs<ProPlayer>().Where(where);
}
Next was the part of development that I was actually excited about: building the interface that would give me the information I needed to dominate my fantasy league. To get the enhanced touch capabilities for this application, I chose the standard MetroTouch theme in the AJAX suite. I didn’t want to get any fancier than that, so I wrote one line of CSS and dropped a RadSplitter on the design surface so that I could make the screen adapt to my needs mid-draft.
<
telerik:RadSplitter
runat
=
"server"
ID
=
"split"
Orientation
=
"Vertical"
Height
=
"100%"
Width
=
"100%"
>
<
telerik:RadPane
runat
=
"server"
Width
=
"400"
MinWidth
=
"400"
>
<!-- Cool left panel stuff here -->
</
telerik:RadPane
>
<
telerik:RadSplitBar
runat
=
"server"
></
telerik:RadSplitBar
>
<
telerik:RadPane
runat
=
"server"
>
<!-- Cool right panel stuff here -->
</
telerik:RadPane
>
</
telerik:RadSplitter
>
In the left panel, I wanted a combobox so that I could see each of the other owner’s teams. I connected a ComboBox to a small ASP.Net Grid that would show their players. With a little ASP.Net ModelBinding and some help from the ASP.Net wizards built into the AJAX controls, I had the following markup to monitor my opponents’ teams:
<
telerik:RadComboBox
runat
=
"server"
Width
=
"100%"
ID
=
"fflTeam"
ItemType
=
"Fritz.FFL.Data.FantasyTeam"
SelectMethod
=
"GetFantasyTeams"
DataValueField
=
"Id"
DataTextField
=
"Owner"
AutoPostBack
=
"true"
OnSelectedIndexChanged
=
"fflTeam_SelectedIndexChanged"
></
telerik:RadComboBox
>
<
telerik:RadGrid
runat
=
"server"
ID
=
"teamPlayers"
OnNeedDataSource
=
"teamPlayers_NeedDataSource"
ClientSettings-Scrolling-UseStaticHeaders
=
"true"
Height
=
"400"
ClientSettings-Scrolling-AllowScroll
=
"true"
>
<
MasterTableView
AutoGenerateColumns
=
"false"
CommandItemDisplay
=
"Bottom"
AllowSorting
=
"true"
>
<
Columns
>
<
telerik:GridBoundColumn
DataField
=
"Pos"
HeaderText
=
"Pos"
HeaderStyle-Width
=
"30"
ItemStyle-Width
=
"30"
></
telerik:GridBoundColumn
>
<
telerik:GridBoundColumn
DataField
=
"Name"
HeaderText
=
"Name"
></
telerik:GridBoundColumn
>
<
telerik:GridBoundColumn
DataField
=
"ByeWeek"
HeaderText
=
"Bye"
HeaderStyle-Width
=
"30"
ItemStyle-Width
=
"30"
></
telerik:GridBoundColumn
>
</
Columns
>
</
MasterTableView
>
</
telerik:RadGrid
>
Combined with the Metrotouch theme for enhanced touch capabilities, this gave me a great little UI that would look like
The final piece that I wanted to ensure that I could track how I was performing, and where I could take advantage of my fellow fantasy team owners was a simple average calculation. I would calculate on every change to players the average projected score that each team would score that week. This is a snap to write with the Get repository method and a little LINQ GroupBy magic:
public
void
GetAverageScores()
{
var repo =
new
PlayerRepository();
var takenPlayers = repo.Get(p => p.FantasyTeamId !=
null
);
var agg = takenPlayers.GroupBy(p => p.FantasyOwner).Select(p =>
new
GraphData(p.Key, p.Sum(d => d.ProjectedPoints) / 16M)).OrderByDescending(p => p.AvgPts).ToList();
avgChart.DataSource = agg;
avgChart.DataBind();
}
This is a little crude, but shows me some interesting information about each team. I calculated a straight average of each team’s scores. I could have improved this method to track the actual players by position who would (at the best) be ‘starting’ each week. But that was a more complex set of code, and I didn’t want to spend too much time writing it… I have a link to the GitHub repository at the conclusion of this piece, if you would like to send me a pull-request, I would gladly accept it.
This is a standard RadHtmlChart, that I bound to the collection of average values at the conclusion of the GetAverageScores method. I didn’t have to configure a lot of markup to get this chart, just a few lines to indicate that this is a bar chart and the names of the fields to make up the series and axis labels:
<
telerik:RadHtmlChart
runat
=
"server"
Width
=
"250px"
Height
=
"200px"
ID
=
"avgChart"
>
<
PlotArea
>
<
XAxis
DataLabelsField
=
"Name"
></
XAxis
>
<
Series
>
<
telerik:BarSeries
DataFieldY
=
"AvgPts"
></
telerik:BarSeries
>
</
Series
>
</
PlotArea
>
</
telerik:RadHtmlChart
>
The heart and soul of this page is the collection of available players and the ability to add one to a team. I made this functionality in just a few steps with a RadFilter, RadGrid, and another RadCombobox of Teams. Let’s break them down, starting with the grid.
The grid would contain a collection of players, and I wanted to be able to use BatchEditing to be able to select and assign players to teams if the draft picked up speed.
<
telerik:RadGrid
runat
=
"server"
ID
=
"playerGrid"
SelectMethod
=
"GetPlayers"
ItemType
=
"Fritz.FFL.Data.ProPlayer"
UpdateMethod
=
"SavePlayerChanges"
Width
=
"100%"
Height
=
"800"
CellSpacing
=
"0"
GridLines
=
"None"
AutoGenerateColumns
=
"False"
>
<
MasterTableView
PageSize
=
"10"
AllowPaging
=
"true"
AllowSorting
=
"true"
CommandItemDisplay
=
"Top"
DataKeyNames
=
"Id"
ClientDataKeyNames
=
"Id"
EditMode
=
"Batch"
>
<
BatchEditingSettings
EditType
=
"Cell"
OpenEditingEvent
=
"Click"
/>
<
CommandItemSettings
ShowExportToExcelButton
=
"true"
/>
</
telerik:RadGrid
>
By using the ModelBinding ItemType, SelectMethod, and UpdateMethod attributes in Code Listing 11, I was able to write simple methods in the code-behind that run against the PlayerRepository with the ProPlayer class that would be loaded and modified by the grid.
public
IQueryable<ProPlayer> GetPlayers()
{
var repo =
new
PlayerRepository();
return
repo.Get(x =>
true
).AsQueryable();
}
public
void
SavePlayerChanges(ProPlayer updated)
{
var repo =
new
PlayerRepository();
repo.Update(updated);
}
Note the Queryable collection returned by the GetPlayers method. This allows the filters, sorts, and other actions the grid will take to act on the collection in a way that sends database query commands all the way back to Mongo.
Ahh… filtering data, how am I going to search for just the right player? How can I get through this list of 500 players to find those diamonds in the rough? The RadFilter takes care of that job, and I wrote a pathetic 1 line of code to get an amazing amount of search functionality out of this control:<
telerik:RadFilter
runat
=
"server"
ID
=
"thisFilter"
AllowFilterOnBlur
=
"true"
ShowAddGroupExpressionButton
=
"true"
ShowApplyButton
=
"true"
FilterContainerID
=
"playerGrid"
></
telerik:RadFilter
>
Now I could easily search through the grid of players and return just those leaders in a specific position who had not been drafted yet with a user-interface that looked like the following:
<
telerik:GridTemplateColumn
DataField
=
"FantasyTeamId"
HeaderText
=
"FFL"
HeaderStyle-Width
=
"100"
ItemStyle-Width
=
"100"
>
<
ItemTemplate
><%#: Eval("FantasyOwner") %></
ItemTemplate
>
<
EditItemTemplate
>
<
telerik:RadComboBox
runat
=
"server"
ID
=
"cmbTeam"
ItemType
=
"Fritz.FFL.Data.FantasyTeam"
SelectMethod
=
"GetFantasyTeams"
DataValueField
=
"Id"
DataTextField
=
"Owner"
></
telerik:RadComboBox
>
</
EditItemTemplate
>
</
telerik:GridTemplateColumn
>
This markup should look very familiar, as I am using the same ModelBinding method for the ComboBox that I used in the left panel. The update action of the grid will take the DataValue from this Combobox and modify the FantasyTeamId specified in the DataField attribute of the GridTemplateColumn. After that, the Grid’s UpdateMethod would be called, passing in a ProPlayer with the FantasyTeam selected from the combobox. The ‘draft’ action to update the grid looks like this:
In a little less than 5 hours, I build a quick single-page application in ASP.NET with web forms and the Telerik AJAX controls to help me with my fantasy league draft. I was not the one running the draft, but with a single attribute setting change on the grid on my screen, I was able to generate an Excel sheet showing the entire results of the draft. Did I do well? My strategy paid off for me, as I am Team 7 in the charts above, a solid 9 point lead over my competition.
This app will not guarantee you a win, but just may give you the insight you want to help on draft day. Do you want to give it a try? I have uploaded the source code to GitHub for you to download and submit patches. Perhaps you’d like to modify the system to manage a Fantasy Premiere League or some other sport. Download the FFL Draft Manager from here as well. If you don't have a copy, download a trial copy of the AJAX controls and get started.
If you’re playing Fantasy NFL Football, you’ve got three weeks until the season starts, so hurry up and get started! Make some modifications to this app, and share them with us so that we can all win our fantasy leagues this year. Write me some comments in the area below to let me know how you do, or if you think I was a knucklehead for taking Drew Brees early in my draft. Good luck!
Jeffrey T. Fritz is a Microsoft MVP in ASP.Net and an ASPInsider with more than a decade of experience writing and delivering large scale multi-tenant web applications. After building applications with ASP, ASP.NET and now ASP.NET MVC, he is crazy about building web sites for all sizes on any device. You can read more from Jeffrey on his personal blog or on Twitter at @csharpfritz. Google Profile CodeProject