This beginner’s tutorial is all about the Razor View Engine and MVC 3. You will be building a trivia
game that simulates the world cup final. By completing this tutorial you will start to develop a basic
understanding of MVC 3, build an understanding of the Razor View Engine and have some fun along
the way.
game that simulates the world cup final. By completing this tutorial you will start to develop a basic
understanding of MVC 3, build an understanding of the Razor View Engine and have some fun along
the way.
The Razor View Engine is a syntax utilising .NET (C# or VB.NET) to build clean functional web pages using standard html.
Scott Guthrie sums up it up really well:
We had several design goals in mind as we prototyped and evaluated “Razor”:
Compact, Expressive, and Fluid: Razor minimizes the number of characters
and keystrokes required in a file, and enables a fast, fluid coding workflow. Unlike most template syntaxes, you do not need to interrupt your coding to explicitly
denote server blocks within your HTML. The parser is smart enough to infer this from your code. This enables a really compact and expressive syntax which is
clean, fast and fun to type.
denote server blocks within your HTML. The parser is smart enough to infer this from your code. This enables a really compact and expressive syntax which is
clean, fast and fun to type.
Easy to Learn: Razor is easy to learn and enables you to quickly be productive
with a minimum of concepts. You use all your existing language and HTML skills.
Is not a new language: We consciously chose not to create a new imperative
language with Razor. Instead we wanted to enable developers to use their
existing C#/VB (or other) language skills with Razor, and deliver a template
markup syntax that enables an awesome HTML construction workflow with your language of choice.
Works with any Text Editor: Razor doesn’t require a specific tool and enables
you to be productive in any plain old text editor (notepad works great).
Has great Intellisense: While Razor has been designed to not require a specific
tool or code editor, it will have awesome statement completion support within Visual Studio. We’ll be updating Visual Studio 2010 and Visual Web Developer 2010 to have full editor intellisense for it.
Unit Testable: The new view engine implementation will support the ability to unit
test views (without requiring a controller or web-server, and can be hosted in any unit test project - no special app-domain required).
Scott Guthrie, 2010
If you want to know about Razor have a look at Scott’s blog post in full:
This is one of my first applications built using MVC3 and I am sure I will be refactoring soon. I’ll be
posting further updates to this game on my blog at www.domscode.com so check there soon for the
latest instalment.
latest instalment.
Prerequisites
You will need VS2010 or Express Version and the latest version of MVC 3.0 (Including the recent
Tools Update). Just go to http://www.asp.net/mvc and click on the MVC 3 Installer.
Tools Update). Just go to http://www.asp.net/mvc and click on the MVC 3 Installer.
Whilst some knowledge of MVC would be really useful, there are lots of great resources out there to start including:
http://weblogs.asp.net/jgalloway/archive/2011/03/17/asp-net-mvc-3-roundup-of-tutorials-videos-
labs-and-other-assorted-training-materials.aspx
labs-and-other-assorted-training-materials.aspx
The Cricket World Cup 2011
First of all I must confess to being a cricket fanatic and If you can’t get excited about the world cup of
Cricket what can you get excited. Yes, unfortunately Australia did not win and my tears have filled
my keyboard, but to be honest , my once mighty Aussies were not the same force they have been
and despite Ricky Ponting’s fine century against India in the quarter finals , Australia simply did not
get enough runs. On the contrary India’s success was based on a mighty top 6 batting line up and
few teams rarely dismantled their top order. Well done India. It may be a bit harder in 2015 in OZ.
Now back to the code...
Cricket what can you get excited. Yes, unfortunately Australia did not win and my tears have filled
my keyboard, but to be honest , my once mighty Aussies were not the same force they have been
and despite Ricky Ponting’s fine century against India in the quarter finals , Australia simply did not
get enough runs. On the contrary India’s success was based on a mighty top 6 batting line up and
few teams rarely dismantled their top order. Well done India. It may be a bit harder in 2015 in OZ.
Now back to the code...
The game
You play as India in the final and you will need to chase down a competitive target by correctly
answering WC trivia cricket questions. You can select an easy, medium or hard question every over
and an incorrect answer will mean a wicket has been lost or a correct answer means runs based on
the following: easy: 2 runs an over, medium: 4 runs an over and hard:8 runs an over. You will notice
when answering questions there are no spaces to enter and when you answer names surnames are
all you need.
and an incorrect answer will mean a wicket has been lost or a correct answer means runs based on
the following: easy: 2 runs an over, medium: 4 runs an over and hard:8 runs an over. You will notice
when answering questions there are no spaces to enter and when you answer names surnames are
all you need.
You’ll notice I have only started with 30 questions but will be building this up in the future - feel free to add additional questions!!!
Let’s get coding
Start Visual Studio
Select File->New Project and then select Visual C# -> Web -> ASP.NET MVC 3 Web
Application
Application
Enter the name MVCCricketWorldCupTrivia make sure Create directory for solution is
ticked and click OK
ticked and click OK
Then select Internet Application, Razor View Engine and untick the Use HTML semantic
markup. Also, Don’t worry about adding any unit test projects at this stage.
markup. Also, Don’t worry about adding any unit test projects at this stage.
This will create a basic starting MVC3 Project. First thing we need to do is get rid of some
unnecessary files that are not of any use - they are mainly the account views and models
unnecessary files that are not of any use - they are mainly the account views and models
Delete the following:
o AccountController.cs from the Controller folder
o AccountModel.cs from the Models folder
o Account folder in the View folder
o LogOnPartial.cshtml in the /Views/Shared folder
We are going to start by adding our models. We need 3 main classes for our game.
o Game - Key class that provides all the game logic and data
o Question - loads the questions from an XML file.
o ShotResult - Simple class storing the result of our answer or played shot.
Now we need to add our first model so right click on the Models folder and select Add
Class...
Name the class Game
Replace the existing code with the following code:
This is really a simple class. The properties are simply the main properties of the Game Class. The CreateGame method will add a Game object to the session. GetNextQuestion returns a new random question. GetResult determines the outcome of a shot and returns a shotresult object. IsGameActive simply determines if there is a game running.
Now we need to add the model for Questions so right click on the Models folder and select
Add Class...
Name the class Question
Replace the existing code with the following code:
#region private methods
private static List<Question> LoadQuestionsFromFile()
{
{
XDocument loaded = XDocument.Load(
System.Web.HttpContext.Current.Server.MapPath("~") +
GameSettings.CricketQuestionsXMLFileLocation);
GameSettings.CricketQuestionsXMLFileLocation);
var questions = from q in loaded.Element("Questions").Descendants("Question")
select new Question
select new Question
{
QuestionID = int.Parse(q.Element("ID").Value),
Level = int.Parse(q.Element("Level").Value),
QuestionText = (string)q.Element("QuestionText").Value,
Answer = (string)q.Element("AnswerText").Value
};
Answer = (string)q.Element("AnswerText").Value
};
return questions.ToList();
}
#endregion
#region public methods
public static Question GetNextQuestion(int questionLevel)
{
{
Random r = new Random();
int questionIndex = r.Next(GameSettings.RandomQuestionsPerLevel);
var questionsAtLevel = Questions.Where(q => q.Level == questionLevel);
return questionsAtLevel.ToList().ElementAt(questionIndex);
}
return questionsAtLevel.ToList().ElementAt(questionIndex);
}
#endregion
}
}
The properties represent the attributes of the question class and you can see we are using data annotations for Answer and AttackLevel that enable us to facilitate the use of
unobtrusive javascript a little later on when we create our views.
unobtrusive javascript a little later on when we create our views.
LoadQuestionsFromFile simply allows us to load the XML file into a collection of Questions.
Now we need to add the class for ShotResult so right click on the models folder and select
Add Class...
Add Class...
Name the class ShotResult
Replace the existing code with the following code:
And finally lets add a class for GameSettings
so right click on the models project and select Add Class... Name the class GameSettings
Insert the following code:
namespace MVCCricketWorldCupTrivia.Models
{
{
public static class GameSettings
{
{
#region properties
public static int BaseRunValue
{
{
get { return 2; }
}
public static int MaxOvers
{
get { return 50; }
}
public static int MaxWickets
{
get { return 10; }
}
public static int RandomQuestionsPerLevel
{
get { return 9; }
}
public static int StartAttackLevel
{
get { return 1; }
}
public static string CricketQuestionsXMLFileLocation
{
get { return @"\Models\CricketQuestions.xml"; }
}
public static int SriLankaScore
{
get { return 274; }
}
#endregion
}
}
Note in this class that the RandomQuestionsPerLevel (9) is for a 0 based array - so it refers to 10 items. In future versions I will replace the constant with a linq query to determine the questions per level.
Now we need to add our controllers. Let’s start by adding the controller for the Questions.
This is the key to our controllers because it is the director of all the logic within our Game. In
general you will find that the controllers are the directors or facilitators of traffic in your
MVC application.
This is the key to our controllers because it is the director of all the logic within our Game. In
general you will find that the controllers are the directors or facilitators of traffic in your
MVC application.
Right click on the Controller folder and select Add Controller
Name it QuestionController and ensure the template is set to empty controller
Replace the existing code with the following:
using System.Web.Mvc;
using MVCCricketWorldCupTrivia.Models; using System.Configuration;
namespace MVCCricketWorldCupTrivia.Controllers
{
{
public class QuestionController : Controller
{
{
public ActionResult Play(int? attackLevel)
{
{
//If there is no attack level for this game lets set it to the //start attack level
if (!attackLevel.HasValue || attackLevel == 0)
{
{
attackLevel = GameSettings.StartAttackLevel;
}
//Get the next question
Question question = Question.GetNextQuestion(attackLevel.Value); //question.AttackLevel = attackLevel.Value;
if (HttpContext.Session["Game"] == null)
{
{
//No game in the session so lets create it
Game game = new Game();
game.CreateGame();
question.CurrentGame = game;
question.AttackLevel = attackLevel.Value;
}
return View(question);
}
[HttpPost]
public ActionResult Play(Question question)
{
{
if (question.CurrentGame == null)
{
{
return RedirectToAction("Index", "Home");
}
//get the shot result for by calling the getresult method
ShotResult result = question.CurrentGame.GetResult(question); if (result.Out)
{
question.CurrentGame.Wickets++;
}
else
{
question.CurrentGame.Runs += result.Runs;
}
//Increment the Overs then check if the game is over
question.CurrentGame.Overs++;
if (question.CurrentGame.Overs >= GameSettings.MaxOvers ||
question.CurrentGame.Wickets >= GameSettings.MaxWickets ||
question.CurrentGame.Runs > question.CurrentGame.SriLankaScore)
{
{
return View("Complete", question.CurrentGame);
}
//Next over will begin
return RedirectToAction("Play",
new { AttackLevel = question.AttackLevel });
}
}
}
There are two methods that we use in our controller. The first is:
public ActionResult Play(int? attackLevel)
This is called on a HTTP Get and you should think of it as the controller action that is called
when the page is loaded or refreshed. We pass this method the Attack Level the user has
entered so that when we display the question we load the appropriate question based on
the Attack Level.
when the page is loaded or refreshed. We pass this method the Attack Level the user has
entered so that when we display the question we load the appropriate question based on
the Attack Level.
The second method (overloaded version of the first) is:
[HttpPost]
public ActionResult Play(Question question)
This method is only called on a HTTP Post from the form in the Game View that we will see
in a moment. The attribute is needed to tell the controller that this method will only be called on a Post. We can see that the we pass the Question model to this controllers action. Again in a moment we will see that this is the model that our view is bound to and is posted to the controller so that we can access that model within the controller.
in a moment. The attribute is needed to tell the controller that this method will only be called on a Post. We can see that the we pass the Question model to this controllers action. Again in a moment we will see that this is the model that our view is bound to and is posted to the controller so that we can access that model within the controller.
Just before we create the views, we need a minor change to the index controller. So open the index controller and replace the existing code with:
using System.Web.Mvc;
using MVCCricketWorldCupTrivia.Models;
namespace MVCCricketWorldCupTrivia.Controllers
{
{
public class HomeController : Controller
{
{
public ActionResult Index()
{
{
Game game = new Game();
return View(game);
}
public ActionResult About()
{
return View();
}
}
}
Now right click Right click within either of the Play methods and select Add View
Name it Play and accept all other defaults
Replace the existing code with the following:
@model MVCCricketWorldCupTrivia.Models.Question
@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.validate.js" type="text/javascript"></script> <script src="../../Scripts/jquery.validate.unobtrusive.js"
type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#PlayersAnswer').focus(); });
$('#PlayersAnswer').focus(); });
</script>
<fieldset>
<h3>Scoreboard</h3>
{
<div class="display-label">Sri LankaScore</div>
<div class="display-field">@Model.CurrentGame.SriLankaScore</div>
<div class="display-label">Wickets</div>
<div class="display-field">@Model.CurrentGame.Wickets</div>
<div class="display-label">Runs</div>
<div class="display-field">@Model.CurrentGame.Runs</div>
<div class="display-label">Overs</div>
<div class="display-field">@Model.CurrentGame.Overs</div>
}
}
</div>
</fieldset>
et>
<h3>Question</h3>
@Html.HiddenFor(m => m.QuestionID)
<div class="display-label">Question:</div>
<div class="display-field"> @Model.QuestionText </div>
@Html.LabelFor(model => model.PlayersAnswer)
<div class="editor-field" >
@Html.EditorFor(model => model.PlayersAnswer)
@Html.ValidationMessageFor(model => model.PlayersAnswer)
</div>
</div>
<div class="editor-label">
@Html.LabelFor(model => model.AttackLevel)
</div>
</div>
<div class="editor-field" >
@Html.EditorFor(model => model.AttackLevel)
@Html.ValidationMessageFor(model => model.AttackLevel)
</div>
</div>
<p>
<input type="submit" value="Play" />
</p>
</fieldset>
}
Now there’s a little bit going on here so I’ll break it down.
First:
First:
@model MVCCricketWorldCupTrivia.Models.Question
@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
This indicates that the model for this page is the Question model and we are also setting the View Title and the layout (similar to asp.net master pages).
Next:
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.validate.js" type="text/javascript"></script> <script src="../../Scripts/jquery.validate.unobtrusive.js"
type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#PlayersAnswer').focus(); });
$('#PlayersAnswer').focus(); });
</script>
The scripts would normally be reference in the /views/shared/_layout.cshtml, but I found that there was a problem with getting unobtrusive javascript to work without them in this view.
Then we have a neat snippet of jQuery that enables the focus on each page load to be the question answer textbox of the form.
Next:
<fieldset>
<h3>Scoreboard</h3>
{
<div class="display-label">Sri LankaScore</div>
<div class="display-field">@Model.CurrentGame.SriLankaScore</div>
<div class="display-label">Wickets</div>
<div class="display-field">@Model.CurrentGame.Wickets</div>
<div class="display-label">Runs</div>
<div class="display-field">@Model.CurrentGame.Runs</div>
<div class="display-label">Overs</div>
<div class="display-field">@Model.CurrentGame.Overs</div>
}
}
</div>
</fieldset>
This is just the scoreboard element of the view. Nothing really complex here we just display all the
important elements of the Models Current Game object. Although, we could potentially use a partial
view here - I am just keeping things really simple and we’ll get to partial views in future tutorials.
view here - I am just keeping things really simple and we’ll get to partial views in future tutorials.
Finally:
{
@Html.ValidationSummary(true) <fieldset>
<h3>Question</h3>
@Html.HiddenFor(m => m.QuestionID)
<div class="display-label">Question:</div>
<div class="display-field"> @Model.QuestionText </div>
@Html.LabelFor(model => model.PlayersAnswer)
<div class="editor-field" >
@Html.EditorFor(model => model.PlayersAnswer)
@Html.ValidationMessageFor(model => model.PlayersAnswer)
</div>
</div>
<div class="editor-label">
@Html.LabelFor(model => model.AttackLevel)
</div>
</div>
<div class="editor-field" >
@Html.EditorFor(model => model.AttackLevel)
@Html.ValidationMessageFor(model => model.AttackLevel)
</div>
</div>
<p>
<input type="submit" value="Play" />
</p>
</fieldset>
}
So this is the main form for the View and contains all the important fields from the Game model
class. Although its a very simple piece of markup it provides a lot of functionality through the Razor Engine and also built in validation via the models data annotations.
Now just a minor change let’s modify the /Views/Home/Index.cshtml to look like
@model MVCCricketWorldCupTrivia.Models.Game
@{
ViewBag.Title = "Home Page";
}
<h3>Welcome to the MVC 3 World Cup Cricket 2011 Trivia Game</h3>
@if (!Model.IsGameActive())
{
<p>Game is not active</p>
@Html.ActionLink("Play Game","Play", "Question");
}
Also the/Views/Shared /_Layout.cshtml needs to be modified to include:
Change the title to:
<div id="title">
<h3>World Cup 2011 Trivia Game</h3>
</div>
Add a new action link into the markup
<li>@Html.ActionLink("Game", "Play", "Question")</li
Now, We need to just add our final view.
Right click on the /Views/Question folder and select Add->View
Name the view Complete and accept all other default settings
Replace the existing code with
@model MVCCricketWorldCupTrivia.Models.Game
@{
ViewBag.Title = "End Game";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Final Scoreboard</h2>
<fieldset>
<div class="Scoreboard">
<div class="display-label">OppositionScore</div>
<div class="display-field">@Model.SriLankaScore</div>
<div class="display-label">Wickets</div>
<div class="display-field">@Model.Wickets</div>
<div class="display-label">Runs</div>
<div class="display-field">@Model.Runs</div>
<div class="display-label">Overs</div>
<div class="display-field">@Model.Overs</div>
</div>
@Html.ActionLink("Back to main page","Index","Home")
</p>
@{
//Clean up the Session because we don't want anything else now
HttpContext.Current.Session["game"] = null;
HttpContext.Current.Session["game"] = null;
}
Of course we probably should be using a partial view here and removing the session in the view is also not great but for a tutorial it will do.
Just to add a nice black background to the scoreboard part of the view lets change the main css file
sightly.
sightly.
Open /Content/Site.css
Add the following at the very bottom of the file
.Scoreboard
{
background-color:Black; color:White;
}
The last thing we need to do is add the XML file. You can find it with the sourcecode for this project (It will be residing in the model folder of the solution)
I hope you have enjoyed this simple introduction to MVC 3 and well done again to the Indian cricket team for winning the 2011 world cup.
Dom
References
www.asp.net/mvc
http://weblogs.asp.net/scottgu/
ttp://www.hanselman.com/blog/
ttp://www.hanselman.com/blog/
No comments:
Post a Comment