MVC Basic Site
Table of Contents
MVC Basic Site is intended to be a series of tutorial articles about the creation of a basic and extendable web site that uses ASP.NET MVC.
ASP.NET MVC is a web development framework from Microsoft that combines the efficiency and tidiness of the Model View Controller (MVC) architecture pattern, the newest ideas and techniques from agile development, and the best parts of the ASP.NET platform.
MVC applications include the following components:
- Models: are parts of the application that implement the logic for the application's data domain. Often, models objects retrieve and store their state in a database.
- Views: are the components that display the application's user interface by using model data.
- Controllers: are components that handle user interaction, work with the model, and finally select a view to render. The view only displays information; the controller handles and responds to user input and interaction.
This article is intended to be the first one from this series and is focused mainly in the creation of a multilingual MVC web site skeleton. It also provides good examples of user authentication and registration mechanisms implemented from scratch, and also the usage of the data entity framework and LINQ.
The provided source code is well commented and cleaned so there should be no problem in reading and understanding of it.
From the user interface point of view, this article provides the skeleton for a MVC web site that includes: the Master Page (main layout with menu, header, and footer), the skeleton for “Home” (Index) and “About” pages, the full implementation for “LogOn” and “Register” pages, and other common partial pages for users and addresses data editing.
The entire user interface has fully implemented internationalization and localization for these three languages: English, Romanian (Română), and German (Deutch), and can be extended for other languages.
Figure: MVC Basic Site - Log On page.
When the user changes the used language from the drop down list from the upper-right corner (see the picture above) the used user interface texts, numbers, validations, and messages will be from that point in and for the specified language.
- .NET 4.0 Framework
- Visual Studio 2010 (or Express edition)
- ASP.NET MVC 3.0
- SQL Server 2008 R2 (or Express Edition version 10.50.2500.0, or higher version)
The provided solution skeleton contains two projects:
- MvcBasicSite: is the main project, of type ASP.NET MVC 3.0, that contains some of the used models, all views, all controllers, and other used resources and source code. This project will be explained in detail in this chapter.
- MvcBasic.Logic: is a class library project that contains the data entity framework diagram, the partial classes associated with the generated entity classes, and other business logic used classes. This project will be explained in the next chapter.
Figure: MVC Basic Site - Site Skeleton
Like you can see in the image above, after creating a project of type ASP.NET MVC 3.0, Visual Studio generates and groups projects items into this structure:
- app_code: is an optional folder and if used contains the global code accessible for the entire project. In our case this folder contains the Content.cshtml file that defines a helper Razor code for including easier scripts in our views.
- App_Data: is the physical store for data. This folder has the same role as it does in ASP.NET web sites that use Web Forms pages. In our case this folder is empty, but by default Visual Studio adds the SQL database used for membership and authentication.
- App_GlobalResources: is an optional folder that contains the resource files used for implementing the multilingual feature. Note that the names of the files are very important, and each resource file contains all texts for a specific language. In our case it contains these three files associated with each language used:
- Resource.resx: is the default resource file associated with the English language (the invariant culture);
- Resource.de.resx: is the resource file associated with the German language (culture info code: de-DE);
- Resource.ro.resx: is the resource file associated with the Romanian language (culture info code: ro-RO).
- Content: is the recommended location for static content files such as cascading style sheet files (CSS), images, themes, and so on. In our case in this folder we have:
- Images: subfolder that contains the used images;
- theme: subfolder that contains the base jQueryUI theme files;
- dd.css: the CSS file used by the change used language drop down list;
- site.css: the site main CSS file.
- Controllers: is the recommended location for controllers. The MVC framework requires the names of all controllers to end with "Controller”. In our case we have three controllers in this folder:
- BaseController: was created by me to be the base class for all controller classes. It contains the common data and functionalities needed in all controllers;
- AccountController: manages user registration, login, and logout;
- HomeController: manages the action of Home (Index) and About pages.
- Model: is provided for classes that represent the application model for your MVC Web application. In our case the actual model objects are separated in the MvcBasic.Logic class library project, and in this folder we have only these two classes:
- LogOnModel: the model used for user authentication in the LogOn page;
- SiteSession: the class used to store the site session specific data for a login user: UserID, Username, UserRole, and CurentUICulture.
- Scripts: is the recommended location for script files that support the application. By default, this folder contains ASP.NET AJAX foundation files and the jQuery library.
- Views: is the recommended location for views. This folder contains a subfolder for each controller; the folder is named with the controller-name prefix. By default, there is also a subfolder named Shared, which does not correspond to any controller, and is used for views that are shared across multiple controllers. In our case the Views folder has the next content:
- Account: contains the views associated with AcountController:
- Logon.cshtml: the page used for user authentication;
- Register.cshtml: the main page used for user registration;
- RegisterFinalized.cshtml: the page used for success registration.
- Home: contains the views associated with HomeController:
- About.cshtml: the skeleton of About page;
- Index.cshtml: the skeleton of Home page.
- Shared: contains the views that are shared across multiple controllers:
- _Address.cshtml: the partial view used to edit address data;
- _Header.cshtml: the partial view used to render the header of the master page;
- _Layout.cshtml: the master page with the main layout of the site;
- _UserAndAddress.cshtml: the partial view used to edit user data and its address;
- Error.cshtml: the error page shown in the case of unexpected exceptions.
- _ViewStart.cshtml: defines the URL of the main layout used in the entire site;
- Web.config: defines the web configuration applicable only to view pages. Normally we should add nothing here manually.
- Global.asax: contains the class
MvcApplication
(derived from HttpApplication
) that is used to set global URL routing defaults, and also to manage other global events like Session_End
;
- packages.config: is managed by the NuGet infrastructure and is used to track installed packages with their respective versions. By default it tracks packages such as jQuery, EntityFramework, Modernizr;
- Web.config: the web site main configuration file and the configuration files associated with different active solutions' configurations; by default we have:
- Web.Debug.config: the configuration file with the transformations that are applicable to the main configuration file only in the case of Debug version;
- Web.Release.config: the configuration file with the transformations that are applicable to the main configuration file only in the case of Release version;
Figure: MVC Basic Site - Site Skeleton Class Diagram
Like you can see from the class diagram above, the BaseController
class contains two data members:
_db
: is an object of type MvcBasicSiteEntities
used to access the site entities data from the database (see details in the next chapter);
CurrentSiteSession
: is a property of type SiteSession
used to access the current logged in user main data that was cached at login into the HTTP session context. This way the user's most important data including ID, role, and current UI culture are preserved between postbacks.
All controllers classes from the MVC site must extend the BaseController
class, because this class provides the common data described above and the functionalities used for internationalization and localization, by overriding the ExecuteCore()
method. Also unhandled exceptions are managed by this base class, but errors and exceptions management will be detailed into other articles.
When we create a new ASP.NET MVC project, the skeleton generated by the Visual Studio contains users authentication implemented by using memberships and an associated SQL database that contains the needed tables. This could be OK for many applications, but in real situation may be the case that you do not want to use the membership and the generated tables and code (maybe you already have users tables used in common with other software solutions and you have to reuse them); so for these scenarios the own authentication mechanism implemented from scratch may be useful.
LogOnModel
is the model class used for authentication. It has two properties that contain validation attributes, which use error message text from the resource files described above.
Note 1: For each validation message from the model classes, we should have associated texts with the same key added in each used resource file; and in each resource file the texts must be translated in its associated language.
[Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "ValidationRequired ")]
[DataType(DataType.Password)]
public string Password { get; set; }
In the example above the Username
property from the LogOnModel
class has two attributes:
DataType
: specifies that this is a password field so the text will not be visible;
Required
: is used for validation and has two properties:
ErrorMessageResourceType
: specifies that the validation error message will be read from the specified resource file name (only the name without any extension like .de.resx, or .ro.resx because the right resource file will be chosen automatically when the current thread UI culture will be changed in the controller);
ErrorMessageResourceName
: specifies the name of the resource key from the resource files.
AccountControler
is the class that fully implements the actions used for user authentication and registration and also manages the change current culture used by the user in the entire site. When the user clicks on the Login link the AccountControler
’s next method will be invoked:
public ViewResult LogOn()
{
LogOnModel model = new LogOnModel();
model.Username = string.Empty;
model.Password = string.Empty;
return View(model);
}
This method creates the LogOn model then uses the newly created model to activate the LogOn view.
As you can see in the example bellow, in the LogOn view, all texts for page title, heading, labels, links, and buttons are implemented by using the associated resource keys by using the syntax: @Resource.[ResourceKeyName]
. Also for all text boxes there are two instructions used, the first one that links the property from the model with the text box by using the @Html.TextBoxFor
syntax, and the second one that links the validation message by using the @Html.ValidationMessageFor
syntax.
<div class="editor-label"/>
@Resource.LogOnModelUsername
</div/>
<div class="editor-field"/>
@Html.TextBoxFor(m => m.Username)
@Html.ValidationMessageFor(m => m.Username)
</div/>
Note 2: For all views used in the solution, all texts used in the user interface items (title, headings, links, labels, buttons, etc.) should be implemented by using resources, and the values of these texts should be inserted in all used resource files in their associated language.
The user authentication begins with the basic validation that is done on the browser by using the validation rules defined in the LogOnModel
model class. Then the AccountControler
’s next method will be invoked.
[HttpPost]
public ActionResult LogOn(LogOnModel model)
{
if (ModelState.IsValid)
{
User user = _db.Users.FirstOrDefault(item =>
item.Username.ToLower() == model.Username.ToLower()
&& item.Password == model.Password);
if (user == null)
{
ModelState.AddModelError("", Resources.Resource.LogOnErrorMessage);
return View(model);
}
else
{
FormsAuthentication.SetAuthCookie(model.Username, false);
SiteSession siteSession = new SiteSession(_db, user);
Session["SiteSession"] = siteSession; return RedirectToAction("Index", "Home");
}
}
return View(model);
}
The LogOn
method will verify the user name and password and in the case of errors the error message will be shown into the LogOn view, otherwise for a successfull login, a new site session object will be created and used to store the user login data, then the entire site session object will the cached into the current HTTP session, and finally the user will be redirected to the site home page (Index view of the HomeController
).
When the user changes the current culture in the user interface, the AccountController
's next method will be invoked:
public ActionResult ChangeCurrentCulture(int culture)
{
SiteSession.CurrentUICulture = culture;
Session["CurrentUICulture"] = culture;
return Redirect(Request.UrlReferrer.ToString());
}
In the code above the static property CurrentUICulture
of the class SiteSession
is invoked (see details bellow).
public static int CurrentUICulture
{
get
{
if (Thread.CurrentThread.CurrentUICulture.Name == "ro-RO")
return 1;
else if (Thread.CurrentThread.CurrentUICulture.Name == "de-DE")
return 2;
else
return 0;
}
set
{
if (value == 1)
Thread.CurrentThread.CurrentUICulture = new CultureInfo("ro-RO");
else if (value == 2)
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
else
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
}
}
As you can see in the code above, CurrentUICulture
property contains the code that changes the current used culture, and also is used to read the current culture as int
(values meaning: 0 = InvariantCulture, 1 = ro-RO, 2 = de-DE).
Note that the current used culture settings above at the current thread level are executed on the server, and they are lost between postbacks.
To prevent this, in overridden ExecuteCore
method of the BaseController
class, the SiteSession
's CurrentUICulture
property must be invoked. On each postback and for each controller, ASP.NET MVC framework automatically invokes ExecuteCore
method, before to invoke any action of the controller.
protected override void ExecuteCore()
{
int culture = 0;
if (this.Session == null || this.Session["CurrentUICulture"] == null)
{
int.TryParse(System.Configuration.ConfigurationManager.AppSettings["Culture"], out culture);
this.Session["CurrentUICulture"] = culture;
}
else
{
culture = (int)this.Session["CurrentUICulture"];
}
SiteSession.CurrentUICulture = culture;
base.ExecuteCore();
}
Also in the code above, when the users access first time the application, the default current culture that are read from web.config
file is used.
When we create a new ASP.NET MVC project, the skeleton generated by the Visual Studio contains also the users registration implemented like users authentication by using memberships. In this solution I provide you an extensible users registration implemented from scratch that use two partial views that were designed to be reused also in the users management that will be described in a next MVC Basic Site article.
The user registration page can be activated by using the “Register” link from the LogOn page.
Figure: MVC basic site - Register page
In the picture above you can see that all menus, label texts, button texts, and the user registration validation messages are shown are in German, because the current culture selected in the upper-right current is “Deutch”. This validation is done by using the validation rules defined in User
and Address
entities classes (see the details in the next chapter).
So the user must correct the validation errors, then the user registrations will continue and the
AccountControler.Register()
method will be invoked.
[HttpPost]
public ViewResult Register(User model, Address modelAddress)
{
if (ModelState.IsValid)
{
User existUser = _db.Users.FirstOrDefault(item =>
item.Username.ToLower() == model.Username.ToLower());
if (existUser == null)
{
MvcBasic.Logic.User user = new MvcBasic.Logic.User();
user.Username = model.Username;
user.Password = model.Password;
user.UserRole = UserRoles.SimpleUser;
user.Email = model.Email;
if (modelAddress.CountryID <= 0)
modelAddress.CountryID = null;
user.Address = modelAddress;
_db.Users.AddObject(user);
_db.SaveChanges();
return View("RegisterFinalized");
}
else
{
ModelState.AddModelError("", Resources.Resource.RegisterInvalidUsername);
model.Address = modelAddress;
return View(model);
}
}
model.Address = modelAddress;
return View(model);
}
This method tests if another user with the same username already exists; for yes, an error message is shown in the registration page and the user should input another username, otherwise a new user will be created and saved into the database and the RegisterFinalized
page will be shown.
In the project MvcBasic.Logic I added a new item of type ADO.NET Data Model, then I made an association with the used database tables, and finally for each entity class, I created an associated partial class in a separate file that contains the entity logic.
Figure: MVC basic site - The Data Entity model diagram
So the MvcBasic.Logic project contains the partial classes associated with the generated entity classes (from the diagram above), but there are also other business logic used classes. This project contains the next classes:
MvcBasicSiteEntities
: is the main data context used to access the data from the database as collections of entities objects.
User
: is the entity class associated with the Users table that stores the users’ main data. This class is used also as model in the Register
view and the _UserAndAddress
partial view.
Address
: is the entity class associated with the Addresses table that stores address data. This class is used as model in the _Address
partial view.
Country
: is the entity class associated with the Countries table that stores countries data;
UserValidation
: defines the validation rules and messages for the User entity. All properties of this validation class have properties that contain validation attributes, which use error message text from the resource files existing in the current project, like in the next example:
[Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "ValidationRequired")]
[Email(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "ValidationEmail")]
[Compare("Email", ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "ValidationComfirmEmail")]
[DataType(DataType.EmailAddress)]
[StringLength(128)]
public string ComfirmEmail { get; set; }
AddressValidation
: defines the validation rules and messages for the Address entity;
EmailAttribute
: extends RegularExpressionAttribute
and implements email validation by using a Regular Expression. This attribute is used in UserValidation
to validate the user email.
EmailValidationRule
: extends ModelClientValidationRule
and defines the email validation rule.
Note that in the case of validation classes, all properties use validation messages from the resource files existing in the MvcBasic.Logic project, and these resource files have similar names like the resource files from MvcBasicSite described above.
The link between the entity classes and validation classes is done by using the MetadataType
attribute in the entity class definition.
Note that all needed tools for database and source code are given as links in the References section, and they can be downloaded and used (for testing) by you without licensing problems, because they are express versions.
Before running this code, you should do these steps:
- Create a database named MvcBasicSite in your SQL Server (or SQL Express), then restore the provided database MvcBasicSiteDatabase.bak on it.
- Optionally you can create a login user for this database in your SQL Server (or SQL Express).
- Modify the connection string in the Web.config file of the MvcBasicSite web application according to your settings from step 2 and step 3.
- 11th January, 2013: Version 1.0.0.1 - Draft version.
- 13th January, 2013: Version 1.0.0.2 - Small changes after review.
- 15th January, 2013: Version 1.0.0.2 - Add the missing line in the source code.
- 25th January, 2013: Version 1.0.1.1 - More details were added in the article.
- 30th January, 2013: Version 1.0.2.1 - New details added in the article.
- 14th February, 2013: Version 1.0.2.2 - Link to "MVC Basic Site: Step 2 - Exceptions Management"
- 21th February, 2013: Version 1.0.2.3 - Basic Site steps.
- 23rd April, 2013: Version 1.0.2.4 - Update the Basic Site steps.
- 18th May, 2013: Version 1.0.2.5 - Update the Basic Site steps.