Click here to Skip to main content
Click here to Skip to main content

LaxCraft - Sample MVC3 Site

, 2 Aug 2011
Rate this:
Please Sign up or sign in to vote.
A nice sample application that leverages MVC3, Castle Windsor, and Fluent-NHibernate.

Introduction

LaxCraft is an ASP.NET MVC3 site that allows a youth team to keep track of their effort on and off the field. It is currently designed just to support one team and is geared towards Lacrosse (hence the name) but it could easily evolve to supporting multiple teams or even multiple leagues and sports. Instead of just retiring the site after our season was over, I figured I would break the application down and highlight some of the cool features so others can learn from it because it may be a bit more exciting than the typical Twitter Feed or Hello World application.

Background

The site leverages the technologies below:

  • Castle Windsor for Dependency Injection (Ninject or another IoC product would work just fine)
  • Fluent NHibernate, a nice layer that sits on top of NHibernate that allows for XML-less configuration and allows for true DDD because it will build out your database tables for you
  • Razor Views for presentation which I find a little cleaner than the standard ASP.NET view engine
  • Telerik MVC UI Extensions to have a WYSIWYG editor when editing or creating news items

Using the code

The code with a backup of the database can be grabbed at http://laxcraft.codeplex.com/ because the code is too large to house here at CodeProject.

You can restore the database if you have a SQL Server instance or you can have Fluent-NHibernate build out your database by changing the connection string of the ApplicationServices key in the web.config and ensuring SchemaUpdate is set to "Build".

General Design / Overview

Overview

I built and used LaxCraft to give the kids on my team some motivation to practice on their own. They will play XBox hours on end, but today, kids don't get outside and play as much as we used to. The site mimics some of the video games kids play where they can earn XP and Level Up in certain categories. The site allows for a player to login and then log any effort they have put towards a particular stat. A stat will translate that effort into XP gained and adjust their level for that stat accordingly. The more XP a player earns in a particular stat, the slower they will Level. The application shows a Team View of all players and what level each player is for each stat and a Player View that shows in detail where a player is for each stat with a detailed stat description and how much more XP they need to move on to the next level.

individualstats.png

Domain Design

A user can belong to a Player or be an Administrator. A Player can enter effort for certain stats and get XP for it, while an Administrator can manage all stats across all players. It is a fairly generic design to allow for the flexibility of working with any type of progress tracking, not just Lacrosse or Sports.

classDiagram.jpg

Points of Interest

Dependency Injection

The Global.asax.cs Application_Start method will call the BootstrapContainer() method that will setup which concrete classes will be used for a particular interface. The Install method is going to look for all classes that inherit from IWindsorInstaller within the current assembly and call their Install method. I think I like the way Ninject does Dependency Injection a little better because I find it to be a little cleaner and easier to follow, but Castle's Windsor is what I used for this project.

Global.asax.cs
private static void BootstrapContainer()
{
    container = new WindsorContainer()
        .Install(FromAssembly.This());
    var controllerFactory = new WindsorControllerFactory(container.Kernel);
    ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
PersistenceInstaller.cs
public void Install(IWindsorContainer container, IConfigurationStore store)
{
    container.AddFacility<persistencefacility>();
}

PersistenceFacility will tell the application how it will be storing data and tells Fluent what database it should be talking to for persistence.

PersistenceFacility.cs
protected override void Init()
{
    var config = BuildDatabaseConfiguration();

    Kernel.Register(
        Component.For<isessionfactory>()
            .UsingFactoryMethod(config.BuildSessionFactory),
        Component.For<isession>()
            .UsingFactoryMethod(k => k.Resolve<isessionfactory>().OpenSession())
            .LifeStyle.PerWebRequest);
}

private Configuration BuildDatabaseConfiguration()
{
    return Fluently.Configure()
        .Database(SetupDatabase)
        .Mappings(m => m.AutoMappings.Add(CreateMappingModel()))
        .ExposeConfiguration(ConfigurePersistence)
        .BuildConfiguration();
}

DotNetOpenAuth Authentication

The project leverages the standard DotNetOpenAuth Authentication that gets added when building a new MVC3 Web Application within Visual Studio but it is tweaked in that upon successful authentication, the system checks to see if the email is already in the database and if it is, then the user is mapped to a particular player. If the email doesn't exist, then they are brought to a screen where they can choose which Player they are and then the relationship is created. If this were a production level application, this would need to get tweaked a little, but for what the application was needed for, it was just enough.

AccountController.cs
[HttpPost]
public ActionResult MapPlayer(FormCollection collection)
{
    var user = new User
                   {
                       Username = HttpContext.User.Identity.Name,
                       Email = collection["Email"],
                       Player = team.GetPlayer(int.Parse(collection["PlayerId"]))
                   };
    users.SaveUser(user);
    NHibernateUtil.Initialize(user.Player);

    Session["CurrentUser"] = user;
    
    return RedirectToAction("Index", "Team");
}

Team View

teamstats.png

Controller

The TeamController gets an ITeamRepository injected into it which allows for it to get all the Team Members and the Stats.

public class TeamController : Controller
{
    private readonly ITeamRepository team;

    public TeamController(ITeamRepository team)
    {
        this.team = team;
    }

    public ActionResult Index()
    {
        ViewBag.Players = team.GetPlayers().OrderBy(p => p.Name).ToList();
        ViewBag.Stats = team.GetStats().OrderBy(s => s.Order).ToList();
    
        return View(ViewBag);
    }

View

The view builds out a table by using Razor syntax to iterate through the Stats in the ViewBag and then each Player on the team. There is probably some room for performance optimization or better caching here because of the way XP and Leveling work. The GetLevel method off of the PlayerStat has some work in it and this method gets called for each player for each stat. So on the Team Page, if you have 10 players and 10 stats, the method would get called 100 times.

There may be some presentation guys out there that see those Table tags and cringe, but I am pretty sure if your content is truly data and belongs in a table, then using a table instead of div tags is acceptable.

A jQuery plug-in called TableSorter is leveraged to allow the user to sort the table by a particular stat. A jQuery plug-in called jQueryTools is used for the tooltips and on the Player Detail page, the animated progress bar leverages Twits jQuery Progress Bar.

<table id="teamTable" class="tablesorter">
<thead>
    <tr>
        <th>
            Name
        </th>
        @foreach (var stat in ViewBag.Stats) {
            <th>
                <span class="abbreviation">@stat.Abreviation</span>
                <div class="tooltip">
                    @stat.Name - @stat.Description
                </div>
            </th>
        }
    </tr>
</thead>
<tbody>
    @foreach (var player in ViewBag.Players) {
        <tr>
            <td class="playerName">
                @(Html.ActionLink((string)player.Name, "Player", 
                           "Team", new { id = player.Id }, null))
            </td>
            @foreach (var stat in player.GetCompletePlayerStats(ViewBag.Stats)) {
                <td>
                    @{var level = stat.GetLevel();}
                    @{var title = string.Format("level {0}: {1}", 
                            level, LaxCraftSession.LevelTitles[level]);}
                    @{var src = Url.Content("~/content/images/" + level + ".png");}
                    <img src="@src" alt="@title" title="@title" />
                </td>
            }
        </tr>
    }
</tbody>

News Postings

Controller

The Post Controller is fairly straightforward where it will get an IPostRepository injected into it and it will use that repository to grab all the posts to display.

private readonly IPostRepository blog;

public PostController(IPostRepository blog)
{
    this.blog = blog;
}

public ActionResult Index()
{
    ViewBag.Posts = blog.GetPosts().OrderByDescending(p => p.CreatedOn);
    ViewBag.Admin = LaxCraftSession.CurrentUser.IsAdministrator;

    return View(ViewBag);
}

View

The Post View will iterate through each post and display the HTML and the meta data including the Tags and Author as well as show links to Edit, Delete, or Create if the user is an Admin.

@foreach (var post in ViewBag.Posts) {
    <div class="postTitle">@post.Title</div>
    <div class="postBody">
        @post.GetHtml()
    </div>  
    <div class="postFooter">
        @string.Format("Posted by {0} on {1:g}", post.CreatedBy, post.CreatedOn);
        <br />
        @if (!string.IsNullOrEmpty(post.Tags)) {
             foreach (var tag in post.Tags.Split(",".ToCharArray())) {
            <span class="tag">@tag</span>
              }
            }
    </div>
  
                if (ViewBag.Admin) {
    <div class="adminAction">
        @Html.ActionLink("Edit", "Edit", new { id = post.Id })
        @Html.ActionLink("Delete", "Delete", 
              new { id = post.Id }, new { rel = "nofollow" })
    </div>
    }
}

WYSIWYG Editing

The Create and Edit View for a Post uses Telerik's MVC3 Extensions to have a nice Rich Text Editor.

@Html.ValidationSummary(true)
    <fieldset>
        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title) 
                  @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Body)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(model => model.Body) 
                 @Html.ValidationMessageFor(model => model.Body)
             @{ Html.Telerik().Editor()
                    .Name("editor")
                    .HtmlAttributes(new { style = "height:400px" })
                    .Encode(false)
                    .Value(@<text>
                        <p>
                            <img src="@Url.Content("~/Content/Images/33.png")" 
                              alt="Editor for ASP.NET MVC logo" 
                              style="display:block;margin-left:auto;margin-right:auto;" />
                            Telerik Editor for ASP.NET MVC allows your users 
                            to edit HTML in a familiar, user-friendly way.
                            <br />In this version, the Editor provides 
                            the core HTML editing engine, which includes
                        </p>
                        </text>).Render();
                 }
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Tags)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Tags) 
                  @Html.ValidationMessageFor(model => model.Tags)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.CreatedBy)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.CreatedBy) 
                  @Html.ValidationMessageFor(model => model.CreatedBy)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

And here is the method in the Controller that will handle the Form post and save the new post to the database:

[HttpPost]
[ValidateInput(false)]
public ActionResult Create(Post post)
{
    if (ModelState.IsValid)
    {

        var newPost = new Post { Body = post.Body, Tags = post.Tags, 
            Title = post.Title, CreatedBy = post.CreatedBy, 
            CreatedOn = DateTime.Now };
        blog.Save(newPost);

        return RedirectToAction("Index");
    }

    return View(post);
}

History

  • Article submitted: 8/1/2011.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

James Coleman
Technical Lead Independent Consultant
United States United States
___________________________
J A M E S C O L E M A N
Director, Technical Services
Linked-In: http://www.linkedin.com/in/jameswcoleman
Blog: ledtalks.wordpress.com
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
QuestionYour article is showing some bad formatting [modified] PinmvpSacha Barber1-Aug-11 22:25 
AnswerRe: Your article is showing some bad formatting PinmemberJames Coleman2-Aug-11 3:37 
GeneralRe: Your article is showing some bad formatting PinmvpSacha Barber2-Aug-11 3:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 2 Aug 2011
Article Copyright 2011 by James Coleman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid