Story Details for articles

Golf Tracker - Ep. 10 - Player Refactor - Part 2

kahanu
Author:
Version:
Views:
4079
Date Posted:
8/24/2011 7:51:58 PM
Date Updated:
8/24/2011 7:51:58 PM
Rating:
0/0 votes
Framework:
ASP.NET MVC 2
Platform:
Windows
Programming Language:
C#
Technologies:
Tags:
golf, golf tracker, attributes, validationattribute
Demo site:
Home Page:
Share:

Golf Tracker - Player Refactor - Part 2

In this episode I physically refactor all the items I stated in the last episode and I build the custom ValidationAttribute class that will validate whether the players index is falls within the prescribed range.

If you recall in my last episode I wanted to refactor the Player form from this...

player edit form before changes

... to this.

player edit form after changes

Now there's a check box to determine whether the player is a "plus" or "minus" handicapper.

The next thing I want to do is make sure that the handicap is actually valid, meaning that it falls within the prescribed range.  That range is a low of +8.0 to a high of -40.4.  I know this seems a bit opposite to the actual values of the numbers, but for the purposes of handicaps, this is correct.  I -24 handicap golfer is a higher handicapper than a +1.2 golfer.

So in order to validate that value of the HandicapIndex, I'll create the custom ValidationAttribute class to handle the calculations.

As a reminder, this is what the player class will look like with the new custom ValidationAttribute class.

custom validation attribute class on player class

You can see the ValidateHandicapIndex attribute on the class.  It takes two parameters for the two properties needed for the calculation.  In this case they are the HandicapIndex property name and the IsPlus property name.

Here is the complete ValidateHandicapIndexAttribute class.

001.using System;
002.using System.ComponentModel;
003.using System.ComponentModel.DataAnnotations;
004.using System.Globalization;
005. 
006.namespace GolfTracker.BusinessObjects.Attributes
007.{
008.    /// <summary>
009.    /// Validate that the incoming handicap index falls between
010.    /// the maximum and minimum acceptable handicap values.  And
011.    /// also make adjustments if it's a plus handicap.
012.    /// MaximumIndex default is -40.4.
013.    /// MinimumIndex default is +8.0.
014.    /// </summary>
015.    ///
016.    [AttributeUsage(AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
017.    public class ValidateHandicapIndexAttribute : ValidationAttribute
018.    {
019.        private const string _defaultErrorMessage
020.            = "Your index of {0}{1} is invalid. It must be between +{2} and {3}.";
021. 
022.        #region ctor
023.        public ValidateHandicapIndexAttribute(
024.            string handicapIndexProperty, string isPlusProperty)
025.            : base(_defaultErrorMessage)
026.        {
027.            IsPlusProperty = isPlusProperty;
028.            HandicapIndexProperty = handicapIndexProperty;
029.        }
030.        #endregion
031. 
032.        #region Properties
033. 
034.        private decimal _incomingIndex = 0.0m;
035.        private bool _isPlus;
036. 
037.        public string HandicapIndexProperty { get; private set; }
038.        public string IsPlusProperty { get; private set; }
039. 
040.        private decimal? _minimumIndex;
041. 
042.        /// <summary>
043.        /// This is the greatest (+) plus index (below par player).
044.        /// </summary>
045.        public decimal MinimumIndex
046.        {
047.            get
048.            {
049.                if (_minimumIndex == null)
050.                    return 8.0m;
051.                return (decimal)_minimumIndex;
052.            }
053.            set { _minimumIndex = value; }
054.        }
055. 
056.        private decimal? _maximumIndex;
057. 
058.        /// <summary>
059.        /// This is the greatest (-) minus index (above par player).
060.        /// </summary>
061.        public decimal MaximumIndex
062.        {
063.            get
064.            {
065.                if (_maximumIndex == null)
066.                    return -40.4m;
067.                return (decimal)_maximumIndex;
068.            }
069.            set { _maximumIndex = value; }
070.        }
071. 
072.        #endregion
073. 
074.        #region Format Error Message
075.        public override string FormatErrorMessage(string name)
076.        {
077.            string plusString = string.Empty;
078.            if (_isPlus)
079.                plusString = "+";
080. 
081.            return string.Format(CultureInfo.CurrentCulture, ErrorMessageString,
082.                plusString, _incomingIndex, MinimumIndex, MaximumIndex);
083.        }
084.        #endregion
085. 
086.        #region IsValid Method
087. 
088.        public override bool IsValid(object value)
089.        {
090.            // If null return true since we are not checking
091.            // for null values. 
092.            if (value == null) return false;
093. 
094.            // Pull the property values from the class object.
095.            PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
096.            object plusValue = properties.Find(IsPlusProperty, true).GetValue(value);
097.            object indexValue = properties.Find(HandicapIndexProperty, true).GetValue(value);
098. 
099.            // Cast the incoming handicap index to a decimal
100.            if (indexValue == null)
101.                throw new ArgumentNullException("HandicapIndex");
102. 
103.            _incomingIndex = (decimal)indexValue;
104. 
105.            // If the Plus value is not null,
106.            // cast it to a boolean.
107.            if (plusValue == null)
108.                throw new ArgumentNullException("IsPlus");
109. 
110.            _isPlus = (bool)plusValue;
111. 
112.            // If the user enter a negative value,
113.            // change it to positive for consistency.
114.            if (_incomingIndex < 0)
115.                _incomingIndex = _incomingIndex * -1;
116. 
117.            // If the handicap index is not a plus index,
118.            // change it to a negative value.
119.            if (!_isPlus)
120.                _incomingIndex = _incomingIndex * -1;
121. 
122.            // The incoming index must fall between the
123.            // minimum and maximum values, otherwise it's invalid.
124.            if (_incomingIndex < MaximumIndex || _incomingIndex > MinimumIndex)
125.            {
126.                return false;
127.            }
128. 
129.            return true;
130.        }
131. 
132.        #endregion
133.    }
134.}

This class can also take the MaximumIndex and MinimumIndex values but if they aren't supplied, the default values are used.  The IsValid overridden method does most of the work.  On lines 96 and 97 it retrieves the actual values from the selected properties.  Once the values are validated that they aren't null, the index value is ensured to be positive for calculation purposes and then they are checked against the handicap range.  If it passes then the class returns True, otherwise False.

Once the ValidationAttribute is in place, we can see if it works.  But first I want to modify one thing in the PlayerController.  I want to make sure that the HandicapIndex is always posted to the database as a positive value.  Then I can handle whether to display a handicap as plus or minus.  To do this I'll create a simple Extension method that will handle the work.

Here's the extension method.

01.namespace GolfTracker.Core.Extensions
02.{
03.    public static class NumericExtensions
04.    {
05.        public static decimal MakePositive(this decimal? source)
06.        {
07.            decimal result = source;
08. 
09.            if (result < 0)
10.            {
11.                result = result * -1;
12.            }
13.             
14.            return result;
15.        }
16.    }
17.}

The source coming in is the HandicapIndex decimal value.  If it's less that zero, then I multiply it by -1 to get a positive value.

This is how it's used in the controller.

01.[HttpPost]
02.public ActionResult Create([Bind(Exclude="Id,rowversion")]Player model)
03.{
04.    try
05.    {
06.        // Create new id
07.        model.ID = Guid.NewGuid();
08.        model.HandicapIndex.MakePositive();
09. 
10.        if (!ModelState.IsValid)
11.            return RedirectToAction("Create");
12. 
13.        service.Insert(model);
14.         
15.        return RedirectToAction("Index");
16.    }
17.    catch (Exception)
18.    {
19.        return RedirectToAction("Create");
20.    }
21.}

You can see I simply append it to the HandicapIndex property of the Player model coming into the method.

Modifying the View

What I want to do now is modify the Index view for the Players to display the Index in a more pleasant way.  The existing way it's displayed is as a simple decimal.  So a 9 handicapper would be displayed as 0.90.  This isn't very helpful especially if this player was a 2 handicapper since it wouldn't be telling us whether he's a plus or minus handicapper.

So I'll modify the Index view to show the handicap in a more friendly way.  Here's the Index view.

01.<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/GolfTrack.Master" Inherits="System.Web.Mvc.ViewPage<GolfTracker.Mvc.Web.ViewData.PlayerViewData>" %>
02. 
03.<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
04.    <%= Model.SiteTitle %> - <%= Model.PageTitle %>
05.</asp:Content>
06. 
07.<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
08. 
09.    <h2><%= Model.PageTitle %></h2>
10. 
11.     
12.    <% Html.Telerik().Grid(Model.PlayerList)
13.           .Name("PlayerGrid")
14.           .DataKeys(key => key.Add(c => c.ID))
15.           .Columns(column =>
16.           {
17. 
18.               column.Bound(model => model.FirstName);
19.               column.Bound(model => model.LastName);
20.               column.Bound(model => model.ClubName);
21.               //column.Bound(model => model.IndexNumber);
22.               column.Template(action =>
23.               {
24.                   if (action.IsPlus == true)
25.                   {
26.                       Response.Write("+");
27.                   }
28.                   Response.Write(action.HandicapIndex);
29.               }).Title("Handicap Index").HtmlAttributes(new { align = "center" });
30.                
31.               column.Template(action =>
32.                   {%>
33.                        <%= Html.ActionLink("Details", "Details", new{ id= action.ID}) %>
34.                   <%});
35.           })
36.           .Pageable()
37.           .Sortable()
38.           .Render(); %>   
39. 
40.</asp:Content>
41. 
42.<asp:Content ID="Content3" ContentPlaceHolderID="HeadContent" runat="server">
43.</asp:Content>

You can see between lines 22 and 29 that I create a new table column that displays the index properly.  It will align the number to the right and display a "plus" (+) symbols in front of the number when necessary.  The resulting view will look like this.

player table

At this point the Player vertical has been modified to work with all the changes I've wanted to make.  But I can already see that things aren't quite right yet.  So I know I'll be refactoring even more.

Stay tuned.

Comments

    No comments yet.

 

User Name:
(Required)
Email:
(Required)