Telerik blogs

American Football!!
DUKESTA22 AT EN.WIKIPEDIA [CC-BY-SA-3.0 (HTTP://CREATIVECOMMONS.ORG/LICENSES/BY-SA/3.0) OR GFDL (HTTP://WWW.GNU.ORG/COPYLEFT/FDL.HTML)], FROM WIKIMEDIA COMMONS
Like many Americans in August, I signed up to play in a fantasy football league.  For the uninitiated, this is a competition over the course of the NFL (National Football League) season in which players get a chance to “own” a team of (American) football players.  During each week of the NFL season, the collection of players you have on your team are compared against another ‘owners’ team, and a winner is decided.  The owner whose team wins the most weeks throughout the season is deemed the league champion and typically receives some cash prize.  The season starts with a ‘draft’ where each owner gets a chance to choose the players they will initially stock their teams with.

 

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.

How Do I Manage the Data?

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";
    }
 
}
Code Listing 1: The ProPlayer data class

 

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")
    };
 
}
Code Listing 2 - FantasyTeam class

 

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.

Getting the Data in to the Database

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);
 
}
Code Listing 3 - IPlayerRepository Interface

 

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();
 
    }
}
Code Listing 4 - A unit test to verify the parsing and loading of a player

 

Just Code's Unit Test RunnerI extracted the first line of one of my player files, copied it into the code and wrote a verification to ensure that the player my ImportHandler class created was in-fact the player I expect.   I used the repo.Assert() method to ensure that my expected call defined as a repo.Arrange method with a specific parameter constraint was executed.  If JustMock detects that it was not executed with the constraints defined, an error is thrown

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);
 
    }
 
}
Code Listing 5 - The Real Data Access to Add a ProPlayer to the Collection

Completing Data Access – Get and Update

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);
 
}
Code Listing 6 - The Get and Update Methods of the Repository

Writing the User Interface – Touch Friendly and Speedy Too!

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>
Code Listing 7 - The Simple Splitter Layout of My Screen

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>
Code Listing 8 - The Fantasy Teams Monitor Markup

 

Combined with the Metrotouch theme for enhanced touch capabilities, this gave me a great little UI that would look like


Figure 2 - Monitoring Team Players

 

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();
 
}
Code Listing 9 - Capturing the Average Team's Score

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.

Now, where should I put this wonderful dataset that would allow me to plunder my competitors?  Answer: in a bar chart, to show them at all times just how much I’m crushing their hopes and dreams!

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>
Code Listing 10 - The 'Crush Their Dreams Bar Chart of Truth' Markup

How Do We Work With the List of Players?

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>
Code Listing 11 - The Players Grid Layout

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);
 
}
Code Listing 12 - The Modelbinding Methods for the Players Grid

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>
Code Listing 13 - A Simple RadFilter for the Players Grid

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:


Figure 3 - My Final User Interface for Managing the Draft

How Do I Draft Players onto a Team?

The last piece to be built, to update players with a team, is the implementation of the BatchEdit functionality in the RadGrid.  This is a simple edit on a row in the players grid.  I made all of the columns readonly and added an EditTemplate to the FFL column.  The final markup for the RadCombobox in this column 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>
Code Listing 14 - The EditTemplate for the FantasyTeam - the 'Draft' Action of the Screen

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:


Figure 4 - Updating the Grid to Select a Team for a ProPlayer and 'drafting' them onto a team

Summary

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!

Telerik ASP.Net AJAX Controls - Download a trial version

About the Author

Jeffrey T. Fritz

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


Related Posts

Comments

Comments are disabled in preview mode.