Intro
Welcome to Part 4 of our uber-awesome chess project! In parts 1, 2, and 3 we covered project requirements, Web API creation, and consuming a Web API via C# and JavaScript. Here in part 4 we'll do a little bit of data validation; could life get any better?! Believe it or not, the data validation for this service will get quite complex so we're in for a long ride.Validate Input Game
We need to validate that the client sent up a game object, at least 1 board must be present in the game, all boards must be the correct size of 8x8, we need to validate the board (array) contains only valid chess pieces, and eventually many more things. Let's see what a list of our validation requirements looks like:- Leave enough flexibility for custom games such as extra pieces, extra turns for player(s), etc.
- game param is not null
- game has at least 1 board position
- For each board in the game, verify
- board dimensions are correct: 8x8
- short values are valid piece values or 0 for no piece
- Empty (no piece present): 0
- Pawn: 1 (white), -1 (black)
- Knight: 2 (white), -2 (black)
- Bishop: 3 (white), -3 (black)
- Rook: 4 (white), -4 (black)
- Queen: 5 (white), -5 (black)
- King: 6 (white), -6 (black)
- one board position logically follows the next (moves are valid)
- The passed in GameStatus accurately reflects victory/draw conditions
Requirement 1
Starting with requirement 1, we don't really need to code much. This is just something to keep in mind while setting up the remaining requirements. For example, how do we allow for a client to send in a board that has 12 knights per side? That's easy, we just don't validate the number of knights. So basically requirement #1 actually lightens the load on us for setting up the remaining requirements.Requirements 2-4
I probably shouldn't lump these all together, but we're going to do a little bit of refactoring from previous weeks' work so it's kind of hard to separate out requirements 2-4. Let's start with an easy task: we need to define constants for each of the piece types on the board. As you can see from our requirements above, pieces/squares range from -6 to +6 in value, so let's define those.Add a file Constants.cs to BlogChess.Backend. Right-click on the BlogChess.Backend project and select Add-->Class.
Name the class Constants.cs and click the Add button.
Now just replace the code in your brand-spankin-new file with what you see here:
namespace BlogChess.Backend { public class Constants { public const short Empty = 0; public const short Pawn = 1; public const short Knight = 2; public const short Bishop = 3; public const short Rook = 4; public const short Queen = 5; public const short King = 6; public const short BlackTransform = -1; public const short WhiteTransform = 1; public const short MinPiece = King * BlackTransform; public const short MaxPiece = King * WhiteTransform; } }
I don't think this code really needs much explanation so let's save my typin-fingers fer something more complicated.
Now let's discuss how I envision validation working. I figure we'll have a game validation class that validates the game as a whole. It will call into a board validation class that validates the board. Any validation issues will be represented by another new class. Our Web API Controller class (from way back in part 2!) will call the game validator class, thus offloading any validation logic into our backend dll.
Some of you might start at the top, but hey I'm feeling risky here so let's start right in the middle; we'll create the board validation class, ChessBoardValidator. Right-click on the BlogChess.Backend project and select Add-->Class. It's the exact same step from a couple paragraphs ago so no screenshot for you! Name this class ChessBoardValidator then click the Add button. Now replace the code in your new file with what you see below:
using System; using System.Collections.Generic; namespace BlogChess.Backend { public class ChessBoardValidator { private ChessBoard m_board; public IList<ValidationIssue> ValidationIssues { get; set; } ////// Constructor /// /// the board to validate public ChessBoardValidator(ChessBoard board) { m_board = board; ValidationIssues = new List<ValidationIssue>(); } ////// Validates the board. /// ///true if the board validated, otherwise false public bool Validate() { ValidationIssues.Clear(); //check for invalid board dimensions if (m_board.Board.GetLength(0) != 8 || m_board.Board.GetLength(1) != 8) ValidationIssues.Add(new ValidationIssue() { Message = "Invalid board dimensions. Must be 8x8.", ValidationCode = ValidationCode.InvalidBoardDimensions }); //check for invalid pieces on the board for (int row = 0; row < m_board.Board.GetLength(0); row++) { for (int col = 0; col < m_board.Board.GetLength(1); col++) { if (m_board.Board[row, col] < Constants.MinPiece || m_board.Board[row, col] > Constants.MaxPiece) ValidationIssues.Add(new ValidationIssue() { Message = String.Format("Invalid piece on board at position {0},{1}", row, col), ValidationCode = ValidationCode.InvalidPieceOnBoard }); } } return ValidationIssues.Count == 0; } } }
Starting near the top, our class has 2 member variables. The first one m_board is of type ChessBoard, and it will represent the board that this validator class needs to validate. The 2nd member variable, ValidationIssues is an IList of type ValidationIssue. Yeah I haven't shown you a ValidationIssue class yet, I know! Geez ma. We'll get to that next I promise. This is just my process. The next thing you'll see in the class is the constructor, conveniently labeled as such. It expects the caller to pass in the board to validate and it initializes the member ValidationIssues as an empty list of type ValidationIssue. The real meat of this class is in the Validate method. First we clear out the list of ValidationIssues, just in case some smarmy little client decides to call us multiple times. We don't want to repeat ourselves with duplicate validation issues after all! Next we ensure the board is in fact 8x8, and if not we add a new ValidationIssue to our list of issues. The next section of the method ensures that our lovely 8x8 array of short (the board) contains only valid numeric values that represent chess pieces, and if something's out of whack we again add an object of type ValidationIssue. Lastly we return true if there are no validation issues and false if we failed. That wasn't too bad!
Clearly this code won't compile yet, as we don't yet have a ValidationIssue class. Let's take care of that problem so we don't have to go too far before feeling the warm comfort of compilation. Right-Click on the project BlogChess.Backend, select Add-->Class, name the class ValidationIssue, click the Add button. Replace your spifferiferous new class with the code you see here:
namespace BlogChess.Backend { ////// The types of validation errors we can encounter /// public enum ValidationCode { ////// the dimensions of the board are not a chess-standard 8x8 /// InvalidBoardDimensions, ////// A piece on the board isn't valid (not one of the following: empty square, pawn, knight, bishop, rook, queen, king) /// InvalidPieceOnBoard, ////// The game was not supplied in the request /// NoGameSupplied, ////// No positions/boards were supplied in the game, so no evaluation of the position can occur! /// NoPositionsSuppliedInGame, } ////// A validation message to be sent back to the user upon validation failure /// public class ValidationIssue { ////// The human-friendly english message /// public string Message { get; set; } ////// The computer-friendly code /// public ValidationCode ValidationCode { get; set; } ////// The index of the board within a list of boards. If null then this field is not applicable. 0-based. /// public int? BoardIndex { get; set; } } }
Our class Validation issue couldn't be much simpler. It has 3 member variables. A string named Message to hold our user-friendly message. an Enum of type ValidationCode to hold our computer-friendly validation code, and a nullable int named BoardIndex to store which board in the list is bad. That last member won't be used yet, but I figured "hey, why come back and do this later when I know I'll need it for game validation?". So it's there. Nyah. We've also declared our enum in here ValidationCode and put a few validation errors within. You might be saying to yourself "Hey Peeticus, we'll have more validation errors than that when we validate the board positions next week won't we?" Yes you are correct. Now shut yer trap! This here's my blog!
2 more steps to go; next up, our class to validate the whole game, ChessGameValidator! Create a class named ChessGameValidator. You don't need me to tell you the steps again I'll wager. Replace the code with what you see here:
using System.Collections.Generic; using System.Linq; namespace BlogChess.Backend { ////// Validates a chess game. /// public class ChessGameValidator { private ChessGame m_game; public IList<ValidationIssue> ValidationIssues { get; set; } ////// Constructor /// /// the game to be validated public ChessGameValidator(ChessGame game) { m_game = game; ValidationIssues = new List<ValidationIssue>(); } ////// Validates the game. /// ///true if the game validated, otherwise false public bool Validate() { ValidationIssues.Clear(); if (m_game == null) ValidationIssues.Add(new ValidationIssue() { Message = "A game must be supplied in the request and cannot be null.", ValidationCode = ValidationCode.NoGameSupplied }); if (m_game.Positions == null || m_game.Positions.Count() == 0) ValidationIssues.Add(new ValidationIssue() { Message = "At least 1 position must be supplied in the game.", ValidationCode = ValidationCode.NoPositionsSuppliedInGame }); if (m_game != null && m_game.Positions != null) { int boardIndex = 0; foreach (var board in m_game.Positions) { var boardValidator = new ChessBoardValidator(board); boardValidator.Validate(); foreach (var issue in boardValidator.ValidationIssues) { issue.BoardIndex = boardIndex; ValidationIssues.Add(issue); } boardIndex++; } } return ValidationIssues.Count == 0; } } }
The structure of this class should look pretty similar. It's laid out quite like the ChessBoardValidator after all. We start with a couple member variables, 1 to represent the game to be validated and one to represent any issues found. Next we have the constructor where the caller must pass in the game to be validated, and we initialize the ValidationIssues to an empty list. Once again the real meat though is in the Validate method. We clear out the issues again, same reason as last time. Next we check to see if the game is null, and if so we throw a hissy fit. Then we check to make sure that there is at least 1 position in the game (a chess board), and if not then another tantrum. Then we might as well validate all the individual positions (boards), so we loop through the list of positions and call their validator method. We of course must add any validation issues found therein to our master list out here, so we do that in a nested loop.
Pretty cool huh? Well the code should compile, but we haven't actually called any of this fancy new junk yet so it doesn't do anything! Open up the controller class named BlogChessController under BlogChessApi.Controllers. This is the controller we created back in week 2 that the Web API uses to do all its work. If you'll recall, the method PostBestMove is the method we created for clients of our Web API to use, so that's where we need to make modifications. In the 2nd post of the series we had put some very basic validation in there, so we're going to rip it out and replace it with our new stuff. You will end up with a method that looks like this:
public HttpResponseMessage PostBestMove(ChessGame game) { try { //validate the game var validator = new ChessGameValidator(game); if (!validator.Validate()) return Request.CreateResponse(HttpStatusCode.BadRequest, validator.ValidationIssues); //calculate the best move string bestMove = "e4"; //return the best move wrapped in an http "ok" result return Request.CreateResponse(HttpStatusCode.OK, bestMove); } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); } }
This method started simple, but now it's even more simple! We start by creating an instance of our validator and passing in the game object that the client sent in. Then we check to see if the game validated; if not, we create a response with an HttpStatusCode of "BadRequest" so that the caller knows something went wrong, and we pass back down the list of validation issues that was encountered. Sweeeeeeet...
Testing Validation
We need to verify that our validation is functional, so we will break our client on purpose. Go into the project BlogChessApiFlexer and open up Default.aspx. In the javascript where we're initializing the game, change that chunk of code to this:var game = { GameStatus: 4, Positions: [{ "Board": [[22, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]] }] };
Notice anything strange? Go ahead and take a second to look it over. OK time's up, it's the #22 right there in the first board position. Remember those constants we defined up near the top of this post? 22 is not a valid piece number as they must be between -6 and +6 inclusively. This means that when we call the Web API, it should fail validation and throw an error. If you drop a breakpoint in the error function of your $.ajax call you can see it die and look at what gets passed down. In fact, I'm just so nice that I included a screenshot of that very occurrence!
I will admit that if you then let the debugger go on about its business and view the error right there in your web page, the error looks kind of useless. However, cleaning up that error handler is a great exercise for your brain so I'll leave that task up to you. Just in case you don't know what I'm talking about here's a screenshot of the error on the page:
What's Next?
- Calculate what is a valid move
- Determine victory conditions
- The remaining 2 validation steps from above, #'s 5 and 6.
- AI
- Modify the client to call our Web API method with real information
No comments:
Post a Comment