Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MVC Basic Site: Step 1 – Multilingual Site Skeleton

0.00/5 (No votes)
25 Oct 2013 1  
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.

MVC Basic Site

Table of Contents

Introduction

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.

Software Environment

  • .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)

Site Skeleton

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.

Users Authentication from Scratch

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()
{
    //
    // Create the model.
    //
    LogOnModel model = new LogOnModel();
    model.Username = string.Empty;
    model.Password = string.Empty;
    //
    // Activate the view.
    //
    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)
    {
        //
        // Verify the user name and password.
        //
        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
        {
            //
            // User logined succesfully ==> create a new site session!
            //
            FormsAuthentication.SetAuthCookie(model.Username, false);
            //
            SiteSession siteSession = new SiteSession(_db, user);
            Session["SiteSession"] = siteSession; // Cache the user login data!
            //
            return RedirectToAction("Index", "Home");
        }
    }
    //
    // If we got this far, something failed, redisplay form!
    //
    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).

Changing and Caching the Current Culture

When the user changes the current culture in the user interface, the AccountController's next method will be invoked:

public ActionResult ChangeCurrentCulture(int culture)
{
    //
    // Change the current culture for this user.
    //
    SiteSession.CurrentUICulture = culture;
    //
    // Cache the new current culture into the user HTTP session. 
    //
    Session["CurrentUICulture"] = culture;
    //
    // Redirect to the same page from where the request was made! 
    //
    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
    {
        //
        // Set the thread's CurrentUICulture.
        //
        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;
        //
        // Set the thread's CurrentCulture the same as CurrentUICulture.
        //
        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;
    //
    // Invokes the action in the current controller context.
    //
    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.

Users Registration from Scratch

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)
    {
        //
        // Verify if exists other user with the same username.
        //
        User existUser = _db.Users.FirstOrDefault(item => 
            item.Username.ToLower() == model.Username.ToLower());
        if (existUser == null)
        {
            //
            // Save the user data.
            //
            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(); 
            //
            // Go to RegisterFinalized page!
            //
            return View("RegisterFinalized");
        }
        else
        {
            //
            // Exists other user with the same username, so show the error message.
            //
            ModelState.AddModelError("", Resources.Resource.RegisterInvalidUsername);
            model.Address = modelAddress;
            //
            return View(model);
        }
    }
    //
    // If we got this far, something failed, redisplay form!
    //
    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.

Data Entities Model and Logic Classes

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.

Before running this code

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:

  1. Create a database named MvcBasicSite in your SQL Server (or SQL Express), then restore the provided database MvcBasicSiteDatabase.bak on it.
  2. Optionally you can create a login user for this database in your SQL Server (or SQL Express).
  3. Modify the connection string in the Web.config file of the MvcBasicSite web application according to your settings from step 2 and step 3.

References

History

  • 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here