Translating ASP.NET MVC with resource files

Resource files are the easiest way to translate ASP.NET MVC applications into multiple languages. They are XML files containing name-value pairs for a specific language.

The resource file editor in Visual Studio

WelcomeTexts.resx
WelcomeTexts.de.resx
WelcomeTexts.sv-SE.resx
WelcomeTexts.uz-Cyrl-UZ.resx

Above are examples of resource file names. The language of the resource file is denoted by a BCP 47 language tag. When .NET looks up a resource by name it will choose the most appropriate file available (e.g. using en if en-US doesn’t exist). The last resort is to use the resource file without a language identifier. This file must exist and it must contain all resource names you want to use or .NET will not find them.

Using the resource files

When you create a new resource file in Visual Studio it will also generate a .NET class with members for each name. The class is regenerated when you rebuild the project to keep up to date with the resource file.

Note: By default Visual Studio will set the access modifier of the class to Internal. To be able to use them in your Razor files you want to change this to Public. This can be changed in the GUI, just above the editor. Or you can change the Custom Tool property from ResXFileCodeGenerator to PublicResXFileCodeGenerator manually in the Properties.

I prefer to use the postfix Texts on all my resource files to keep the generated class names from clashing with my other classes.

@using TranslatedApplication.Resources
<!DOCTYPE html>
<html>
<head>
    <title>@WelcomeTexts.Welcome</title>
</head>
<body>
<h1>@WelcomeTexts.Welcome</h1>

<p>
    @WelcomeTexts.Introduction
</p>
</body>
</html>

Setting the language

.NET chooses which resource file to use based on the language of the current thread. Specifically, it looks at the CurrentUICulture of the thread. We must set it at the start of every HTTP request.

The user should be given the option to change the language. But I also like to start with an educated guess based on the user’s language settings, which are sent to us by the browser.

To set the language I recommend creating an action filter attribute that performs the necessary steps before each controller action executes.

namespace TranslatedApplication.Infrastructure.Attributes
{
    public class LocalizeAttribute : ActionFilterAttribute, IActionFilter
    {
        private readonly string[] SupportedLanguages = {"en", "sv"};
        private const string DefaultLanguge = "en";

        void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
        {
            string language = DefaultLanguge;

            HttpRequestBase request = filterContext.HttpContext.Request;

            HttpCookie cookie = request.Cookies["Language"];
            if(cookie != null && SupportedLanguages.Contains(cookie.Value))
            {
                language = cookie.Value;
            }
            else if(request.UserLanguages != null)
            {
                foreach(string userLang in request.UserLanguages)
                {
                    string lang = userLang;
                    if(lang.Length < 2) continue;
                    if(lang.Contains('-'))
                        lang = lang.Substring(0, lang.IndexOf('-'));
                    if(SupportedLanguages.Contains(lang))
                    {
                        language = lang;
                        break;
                    }
                }
            }

            CultureInfo culture = new CultureInfo(language);
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
        }
    }
}

To make the attribute apply to all controller actions, add it to the global filters in FilterConfig.cs. This file should be in the App_Start directory, create it if it doesn’t exist.

namespace TranslatedApplication
{
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new LocalizeAttribute());
        }
    }
}

Finally, make sure the following line is in the Application_Start method in Global.asax.

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

To let the user change the language themselves, add a controller action for it. The page has to be reloaded to reflect the change.

Note: Keep in mind that since we use a cookie to remember the user’s choice we must inform the user about it under EU law.

public class LocaleController : Controller
{
    public ActionResult Switch(string language)
    {
        Response.Cookies["Language"].Value = language;
        return Redirect("/");
    }
}
Advertisements

Data in a web page

There are several places to store data in an ASP.NET web page. These places are far from interchangeable. Careless placement of data can lead to a poor web page and bad user experience. The following are some guide lines that I consider appropriate.

URL

Use for: Data that defines the web page. Such as the ID for a product or the terms for search results.

Remember: This becomes permanent if the page is bookmarked. Don’t store temporary modifiers in the URL. For example, if the user bookmarks the login page after a failed login attempt they shouldn’t have to see the error message every time they return.

DOM

Use for: Data that makes up the web page. Apart from the obvious, text and HTML, this can include JSON to be used by JavaScript.

Remember: Data attributes are a good place to store data for use in JavaScript.

Global JavaScript variables

Use for: Keeping state in single page applications or other long lived web pages with lots of JavaScript.

Remember: In JavaScript all scripts share the same namespace. Take care that your variables don’t clash with other scripts, for example by putting all your global variables into one global object. There are other ways to handle this as well.

Local storage

Use for: Applications where you can’t or don’t want to store data server side.

Remember: This data is stored permanently in the browser. The data won’t be available if the user returns with a different browser or device.

Cookie

Use for: Remembering users across visits on web sites requiring login. Or remembering user settings on web sites that does not use logins.

Remember: Current EU law dictates that web sites targeting EU citizens must obtain user permission when setting cookies. Though there are some exceptions.

Session

Use for: Data related to the currently logged in user.

Remember: The session is controlled by a session cookie stored in the browser. ASP.NET takes care of this automatically. It is the browser that determines when the session ends by removing the cookie. Session cookies used for authentication purposes does not fall under the EU law described above.

TempData

Use for: Data you want to keep between page requests, but not longer. Good for showing the user that data has been saved for example.

Remember: This data is stored in the session but has the extra property that it disappears after being read.

Application data

Use for: As a cache for common data needed server side.

Remember: This data is common for all users and persists until the application reloads. The data must be handled in a thread safe manner.

Database

Use for: Everything that needs permanent storage.

Remember: Database design and management is a whole science in and of itself.

Testing the view

The view part of ASP.NET MVC is difficult to test in isolation. It is more commonly integration tested together with the rest of the system.

@model IEnumerable<ShipRegister.Models.Ship>
<!DOCTYPE html>

<html>
<head>
    <title>Ship register</title>
    <script src="~/Scripts/jquery-2.1.1.js"></script>
    <script>
        $(function () {
            $("li").on("click", function () {
                $(this).css("color", "red");
            });
        });
    </script>
</head>
<body>
    <h2>Ships</h2>
    <ul>
    @foreach (Ship ship in Model) {
        <li>@ship.Name</li>
    }
    </ul>

    <form action="/Ship/Create" method="post">
        <input type="text" name="Name" placeholder="Ship name" />
        <input type="submit" id="Button" value="Add" />
    </form>
</body>
</html>

The view also includes JavaScript which may be tested at the same time.

[TestClass]
public class ShipViewTests
{
    [TestMethod]
    public void WebRootShowsShipRegister()
    {
        using (IWebDriver driver = new FirefoxDriver())
        {
            // Arrange / Act
            driver.Navigate().GoToUrl("http://localhost:51794/");

            // Assert
            Assert.AreEqual("Ship register", driver.Title);
        }
    }

    [TestMethod]
    public void SubmitNewShipShowsNewShip()
    {
        string shipName = "Ship " + Guid.NewGuid();

        using (IWebDriver driver = new FirefoxDriver())
        {
            // Arrange
            driver.Navigate().GoToUrl("http://localhost:51794/");
            IWebElement textInput = driver.FindElement(By.Name("Name"));
            IWebElement submitButton = driver.FindElement(By.Id("Button"));

            // Act
            textInput.SendKeys(shipName);
            submitButton.Click();

            // Assert
            IWebElement ship = driver.FindElements(By.TagName("li")).Last();
            Assert.AreEqual(shipName, ship.Text);
        }
    }

    [TestMethod]
    public void ClickShipColorsShipRed()
    {
        using (IWebDriver driver = new FirefoxDriver())
        {
            // Arrange
            driver.Navigate().GoToUrl("http://localhost:51794/");
            IWebElement ship = driver.FindElements(By.TagName("li")).First();

            // Act
            ship.Click();

            // Assert
            Assert.AreEqual("color: red;", ship.GetAttribute("style"));
        }
    }
}

These tests are performed in Firefox with the help of Selenium. A new Firefox instance is opened for each test.

Testing the model

The model part of ASP.NET MVC is best tested in conjunction with the database.

public class ShipRepository : IShipRepository
{
    private string ConnectionString;

    public ShipRepository()
    {
        ConnectionString = ConfigurationManager
            .ConnectionStrings["Database"].ToString();
    }

    public ShipRepository(string connectionString)
    {
        ConnectionString = connectionString;
    }

    public IEnumerable<Ship> GetList()
    {
        const string query = "SELECT Name FROM Ships";
        var ships = new List<Ship>();
        using (var connection = new SqlConnection(ConnectionString))
        {
            connection.Open();
            using (var command = new SqlCommand(query, connection))
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    ships.Add(new Ship { Name = reader.GetString(0) });
                }
            }
        }
        return ships;
    }

    public void Insert(Ship ship)
    {
        var ships = new List<Ship>();
        using (var connection = new SqlConnection(ConnectionString))
        {
            connection.Open();
            using (var command = new SqlCommand("spInsertShip", connection))
            {
                command.CommandType = CommandType.StoredProcedure;
                command.Parameters.AddWithValue("@Name", ship.Name);
                command.ExecuteNonQuery();
            }
        }
    }
}

This class uses the repository pattern to manage the model.

[TestClass]
public class ShipRepositoryTests
{
    [TestCleanup]
    public void Cleanup()
    {
        RunNonQuery("TRUNCATE TABLE Ships");
    }

    [TestMethod]
    public void GetListReturnsAllRows()
    {
        // Arrange
        RunNonQuery("INSERT INTO Ships (Name) VALUES ('Ship 1')");
        RunNonQuery("INSERT INTO Ships (Name) VALUES ('Ship 2')");
        var repository = new ShipRepository(GetConnectionString());

        // Act
        IEnumerable<Ship> ships = repository.GetList();

        // Assert
        Assert.AreEqual(2, ships.Count());
        Assert.IsTrue(ships.Select(s => s.Name).Contains("Ship 1"));
        Assert.IsTrue(ships.Select(s => s.Name).Contains("Ship 2"));
    }

    [TestMethod]
    public void InsertShipInsertsRow()
    {
        // Arrange
        var repository = new ShipRepository(GetConnectionString());

        // Act
        repository.Insert(new Ship { Name = "New Ship" });

        // Assert
        var shipNames = RunStringListQuery("SELECT Name FROM Ships");
        Assert.AreEqual(1, shipNames.Count());
        Assert.IsTrue(shipNames.Contains("New Ship"));
    }

    private string GetConnectionString()
    {
        return ConfigurationManager
            .ConnectionStrings["Test"].ToString();
    }

    private void RunNonQuery(string query)
    {
        using (var connection = new SqlConnection(GetConnectionString()))
        {
            connection.Open();
            using (var command = new SqlCommand(query, connection))
            {
                command.ExecuteNonQuery();
            }
        }
    }

    private IEnumerable<string> RunStringListQuery(string query)
    {
        var result = new List<string>();
        using (var connection = new SqlConnection(GetConnectionString()))
        {
            connection.Open();
            using (var command = new SqlCommand(query, connection))
            using(var reader = command.ExecuteReader())
            {
                while(reader.Read())
                {
                    result.Add(reader.GetString(0));
                }
            }
        }
        return result;
    }
}

These tests are run against a separate test database which is emptied after each test.

Testing the controller

The controller is the most straightforward part of ASP.NET MVC to test.

public class ShipController : Controller
{
    private readonly IShipRepository Repository;

    public ShipController() : this(new ShipRepository()) { }

    public ShipController(IShipRepository repository)
    {
        Repository = repository;
    }

    public ViewResult Index()
    {
        IEnumerable<Ship> ships = Repository.GetList();
        return View(ships);
    }

    public RedirectResult Create(Ship ship)
    {
        if (ModelState.IsValid)
        {
            Repository.Insert(ship);
        }
        return Redirect("/");
    }
}

The only thing this controller depends on is the repository which handles the model.

[TestClass]
public class ShipControllerTests
{
    [TestMethod]
    public void IndexReturnsAllShips()
    {
        var ships = new List<Ship>
            {
                new Ship { Name = "Ship 1"},
                new Ship { Name = "Ship 2"}
            };

        // Arrange
        var repository = Mock.Of<IShipRepository>(r => r.GetList() == ships);
        var controller = new ShipController(repository);

        // Act
        ViewResult result = controller.Index();

        // Assert
        Assert.AreEqual(ships, result.Model);
    }

    [TestMethod]
    public void CreateInsertsShip()
    {
        Ship ship = new Ship { Name = "New Ship" };

        // Arrange
        var repository = Mock.Of<IShipRepository>();
        var controller = new ShipController(repository);

        // Act
        controller.Create(ship);

        // Assert
        Mock.Get(repository).Verify(m => m.Insert(ship));
    }

    [TestMethod]
    public void CreateRedirectsToRoot()
    {
        Ship ship = new Ship { Name = "New Ship" };

        // Arrange
        var repository = Mock.Of<IShipRepository>();
        var controller = new ShipController(repository);

        // Act
        RedirectResult result = controller.Create(ship);

        // Assert
        Assert.AreEqual("/", result.Url);
    }
}

This is using Moq to replace the repository and the arrange-act-assert pattern to perform the tests.

Trimming Handlebars with Razor

In JavaScript heavy web applications it is important to generate HTML in an organized way. This is an idea on how to accomplish this with the Handlebars template library, ASP.NET MVC and Razor views.

<div>
  <h2>{{name}}</h2>
  <ul>
    {{#each events}}
      <li>{{> event}}</li>
    {{/each}}
  </ul>
</div>
<strong>{{name}}</strong>

Each Handlebars template is put in a Razor file. By using Razor files we get some support from Visual Studio for writing the HTML. This also gives us access to the whole Razor engine, which could be used to handle localization among other things.

[OutputCache(Duration=604800)]
public ActionResult Compiled()
{
    string path = "~/Views/Templates/";

    var files = Directory.EnumerateFiles(Server.MapPath(path));
    var names = files.Select(f =>
        Path.GetFileNameWithoutExtension(f).ToLowerInvariant());

    var engine = new ScriptEngine();
    engine.ExecuteFile(Server.MapPath("~/Scripts/handlebars.js"));
    engine.Execute(@"var precompile = Handlebars.precompile;");

    var compiled = new StringBuilder(10240);
    compiled.Append("var templates = {");
    foreach (string name in names)
    {
        string file = path + name + ".cshtml";
        string template = RenderRazorViewToString(file);

        compiled.Append(name);
        compiled.Append(": Handlebars.template(");
        compiled.Append(engine.CallGlobalFunction("precompile", template));
        compiled.Append("),");
    }
    compiled.Append("};");

    foreach (string name in names)
    {
        compiled.AppendFormat(
            @"Handlebars.registerPartial(""{0}"", templates.{0});",
            name);
    }

    return Content(compiled.ToString(), "text/javascript");
}

Handlebars templates must be compiled before use. To increase performance we can compile them on the server, combine them and cache the result. To run Handlebars on the server we use Jurassic, an implementation of JavaScript for .NET. To get the templates from the Razor files see this implementation of RenderRazorViewToString.

The second foreach loop registers every template as a partial in order to use it from another template with the {{> name}} syntax. Only the templates used from other templates need to be registered but this just registers them all for simplicity.

<script src="~/Scripts/handlebars.runtime.js"></script>
<script src="~/Templates/Compiled"></script>

Since the templates are already compiled we only need the runtime version of Handlebars on the client. The precompiled templates are included like any other script.

(function () {
    var skiing = {
        name: 'Alpine',
        events: [
            { name: 'Downhill' },
            { name: 'Slalom' },
            { name: 'Super G' }
        ]
    };
    var body = document.getElementById('body');
    body.innerHTML = templates.sport(skiing);
})();

Using the templates is easy. Combine this system with a JavaScript framework and a REST resource and it could be something.

MVC divided (into areas)

Large ASP.NET MVC projects with many controllers and views can become difficult to handle. One solution may be to divide your MVC project into areas.

An area in ASP.NET MVC is a directory with its own MVC directory structure. You can use areas in addition to the default directory structure or you can remove the default directory structure and rely solely on areas.

image

Here is the directory structure of an area called Public. You can add areas in Visual Studio by right clicking the project in the solution explorer and selecting the appropriate menu item.

public class PublicAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get
        {
            return "Public";
        }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "Frontpage",
            "",
            new { controller = "Home", action = "Frontpage" }
        );
    }
}

For each area we need an area registration class with a RegisterArea method. In this method we put our routes for the area. Make sure your routes are unique across all areas.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
}

The code above, which should be in the Global.asax file, takes care of registering all the areas when the application is started.

@Html.ActionLink("Go back", "Frontpage", "Home", new { area = "Public" }, null)

One complication you will run into is how to link to a page in another area. Above is how you would insert a link to the Frontpage view in the Public area using razor syntax.

Areas may also be helpful if you are trying integrate ASP.NET MVC into a legacy ASP.NET Web Forms project.

My ASP.NET is full of eels

When creating a multilingual ASP.NET application there are two sane options. You can either create different pages for each language. This can be done without duplicating the server code by using views in MVC or by using the same code behind file for different pages in web forms. However, in both cases you will have to duplicate your markup.

The second option is to use resources. Commonly in the form of resource files, though there are other options. Resource files are XML files, each file contains resources for one language and each resource is identified with a name. Resource values can contain binary data such as images but for localization purposes they will typically be strings.

Resource.resx
Resource.de.resx
Resource.sv-SE.resx
Resource.uz-Cyrl-UZ.resx

Above are examples of resource file names. The language of a resource file is denoted by an RFC 1766 language identifier before the files .resx extension. When .NET looks up a resource by name it will choose the most appropriate file available (e.g. using en if en-US doesn’t exist). The last resort is to use the resource file without a language identifier. This file must exist and it must contain all resource names you want to use or .NET will not find them.

Visual Studio has good support for editing resource files but for translations you might want to use a third party program. For example Zeta Resource Editor which is free. It allows you to edit several resource files at once.

Local and global resource files

ASP.NET web forms has excellent support for resource files. It uses two types of files, local resource files are bound to a specific page while global resource files are used everywhere. The resource files are put in two folders named App_LocalResources and App_GlobalResources respectively.

<asp:Literal runat="server" meta:resourcekey="Concept" />
<asp:Literal runat="server" Text="<%$ Resources: Concept.Text %>" />
<asp:Literal runat="server" Text="<%$ Resources: Company, Slogan %>" />

You can include text from your local resource files in an ASPX page implicitly. The first line in the code above, which is taken from a page called About.aspx, will look for resources named Concept.X in the local resource file called About.aspx.resx, where X is a valid attribute for the tag. In this case I have a resource named Concept.Text which will set the Text attribute of the literal tag.

The second line does the same thing as the first but looks up the resource name explicitly. The third line gets a resource named Slogan from the global resource file named Company.resx. Global resources cannot be used implicitly.

GetLocalResourceObject("Concept.Text") as string;
GetGlobalResourceObject("Company", "Slogan") as string;
HttpContext.GetLocalResourceObject("~/About.aspx", "Concept.Text") as string;
HttpContext.GetGlobalResourceObject("Company", "Slogan") as string;

You can also use the resources in your C# code. If you are in the code behind for a page you can use either of the first two lines while the last two lines can be used from anywhere. In the latter case you have to specify which page you want if you use a local resource file.

Strongly typed resources

Unfortunately, the local/global resource system doesn’t work very well in MVC. Instead you may want to use the following system. When you create new resource files in Visual Studio it will also generate a C# class with members for each resource. The class is regenerated when you rebuild the project to keep up to date with the resource file.

Resources.Support.Contact;

If you are using strongly typed resources there is no reason to use the directories for local and global resource files, although that should be possible as well, instead I suggest using a directory called Resources for all your resource files. In this case I have put a resource file called Support.resx in the Resources directory. In the example above I retrieve the resource named Contact.

@Project.Resources.Support.Help

You use the same code to get resources into the razor views. You have to include the project name. You may want to simplify the syntax by using using or something similar.

While the strongly typed resources look easy to use there are two major drawbacks. You can’t assemble resource names dynamically in the code, which can be useful sometimes. Secondly the generated classes only work with resource files, if you want to store resources some other way it won’t work. It might be worth putting together your own classes for dealing with resources in MVC.

Setting the language

The resources are pretty useless without a way to set the language. This has to be done at the start of each HTTP request. The best way to decide which language to use is to look at what the user’s browser tells us. But we also want the user to be able to override that decision.

protected void Application_BeginRequest(object sender, EventArgs e)
{
    string[] SupportedLanguages = { "en", "sv" };
    string DefaultLanguge = "en";
    string language = DefaultLanguge;

    HttpRequest request = HttpContext.Current.Request;

    HttpCookie cookie = request.Cookies["Language"];
    if (cookie != null && SupportedLanguages.Contains(cookie.Value))
    {
        language = cookie.Value;
    }
    else if (request.UserLanguages != null)
    {
        foreach (string userLang in request.UserLanguages)
        {
            string lang = userLang;
            if (lang.Length < 2) continue;
            if (lang.Contains('-'))
                lang = lang.Substring(0, lang.IndexOf('-'));
            if (SupportedLanguages.Contains(lang))
            {
                language = lang;
                break;
            }
        }
    }

    CultureInfo culture = new CultureInfo(language);
    Thread.CurrentThread.CurrentCulture = culture;
    Thread.CurrentThread.CurrentUICulture = culture;
}

This function in Global.asax.cs is run at the start of each request (this works in both web forms and MVC). We check if we have a language cookie set. Then we check the browser’s languages. If none of those are acceptable we default to English. Finally we can set the language of the thread. The current culture determines how dates, currencies and such things are formatted. The current UI culture determines which resource files to use.

protected void SwitchToSwedish(object sender, EventArgs e)
{
    Response.Cookies["Language"].Value = "sv";
    Response.Redirect(Request.RawUrl);
}

This is the web forms code we use when the user clicks the Swedish button. We set the cookie and then tell the browser to refresh the page. This is the only way to do it since by the time we reach this function it is too late to change the language. We have to get another HTTP request to be able to do that.

Ajax via jQuery and ASP.NET

In this simple example of Ajax we will create a text field and a button. When the button is pressed the text should be sent to the server where it is upper cased and returned. The result is then displayed to the user.

<h2>Upper case</h2>
<p>
    <input id="input" type="text" />
    <input id="button" type="button" value="DO IT" />
</p>
<p>
    <span id="output"></span>
</p>

In ASP.NET MVC

To receive and process the text in MVC we create a controller method. Don’t forget to make a route for it as well.

[HttpPost]
public JsonResult Uppercase(string text)
{
    return Json(new { Text = text.ToUpper() });
}

Here we use a rather nice feature of MVC. If the controller method takes any parameters MVC will search in several places for a value to populate that parameter with. In our case the text parameter will be found in the query string but it could also have been in the routing data, posted form values or sent as JSON from the client.

After processing we use JSON to return data to the client. Here I’m using an anonymous type but you can use JSON to serialize any object. Because returning JSON in a GET request is a security risk we limit the method to POST requests. MVC will actually not allow us to use GET together with JSON, if you do it will just fail silently.

$("#button").click(function () {
    $("#output").html("processing...");
    $.ajax({
        type: "POST",
        url: "uppercase",
        data: { text: $("#input").val() },
        success: function (response) {
            $("#output").html(response.Text);
        }
    });
});

This is the code we write on the client side to call the server method. When the button is pressed we first write out a message to the user in order to give immediate feedback. If we don’t do this and the server is slow it may seem like the button didn’t do anything. You may also want to disable the button until the request finishes depending on the situation.

To make the actual call we use the ajax method in jQuery. The type and url parameters are hopefully self explanatory. For the data parameter we give it a JavaScript object, jQuery will convert this to a query string for us. The success function is called when the call returns, remember that this is an asynchronous call. The JSON is automatically parsed for us and passed to the function.

In ASP.NET Web Forms

To do the same thing in Web Forms we can use a rather nice feature called web methods. In my case I have put this in the code behind file for the web page but you could put it in a web service instead.

[WebMethod]
public static object Uppercase(string text)
{
    return new { Text = text.ToUpper() };
}

This looks rather similar to the method we used in MVC but there are two differences. The data we return here is returned as JSON but implicitly instead of explicitly. Secondly the data sent to this method must be in JSON format. Using a query string will not work. Note that the return value in my case is object only because I return an anonymous type, it could have been anything.

$("#button").click(function () {
    $("#output").html("processing...");
    $.ajax({
        type: "POST",
        url: "Page.aspx/Uppercase",
        data: JSON.stringify({ text: $("#input").val()}),
        contentType: "application/json; charset=utf-8",
        success: function (text) {
            $("#output").html(text.d.Text);
        }
    });
});

The client side code is mostly the same as for MVC. Notice how the URL looks when calling a web method. The biggest change though is that we have to send the data as JSON. To do this we have to set the content type and create the JSON string ourselves. The method I’m using to create the JSON string is not part of jQuery, it is a native method in modern browsers. If you want to support older browsers you need to include the JSON2 library.

There is also a small puzzling change here. We have to include an extra .d when getting the returned data. This is a security feature that prevents certain XSS attacks.

Web Forms the hard way

If you for some reason don’t want to use web methods you could use the following code. Put it in the page load method of the page you’re calling.

if (Request.Params["Callback"] == "uppercase")
{
    string text = Request.Params["Text"];
    JavaScriptSerializer s = new JavaScriptSerializer();
    string response = s.Serialize(new { Text = text.ToUpper() });
    Response.ContentType = "application/json";
    Response.Write(response);
    Response.End();
}

Call this with Page.aspx?Callback=uppercase and send the data as a query string.

ASP.NET MVC from scratch

ASP.NET MVC is Microsoft’s answer to Ruby on Rails. Apart from the Model-View-Controller design pattern it contains the following:

  • URL routing
  • Code generation
  • Convention over configuration
  • Razor markup instead of ASPX (optional)

When you create a new ASP.NET MVC project in Visual Studio you can start with all the code necessary for creating and logging in users. You can also use code generation to generate CRUD interfaces. While this might be nice to have I suspect it isn’t very useful in bigger projects. But my main problem with this is that far too many tutorials will make use of it. I think this makes it harder to understand how the system actually works.

From nothing to a web page

Therefore let us start with an empty ASP.NET MVC 3 application and take it step by step until we have a web page without generating large quantities of code.

First we must set up a route in the Global.asax.cs file for the root of the web site:

routes.MapRoute(
    "Front", // Name
    "", // URL
    new { controller = "Main", action = "Front" }
);

Now if someone goes to our web site the Front action on the Main controller will be called. Here is where the convention over configuration comes in. MVC will look for a method named Front in a class named MainController in the Controllers namespace. This method must return a type derived from ActionResult.

namespace MvcTest.Controllers
{
    public class MainController : Controller
    {
        public ContentResult Front()
        {
            return Content("Front page", "text/plain");
        }
    }
}

This is one of the biggest strengths with MVC in my opinion. Every request to the web server can be routed to a method in a controller. Then you can return anything you want from there, there are many types of results that you can return.

The front page now works. But you probably want to return a web page and not just text. First change the return value of the method:

public ViewResult Front()
{
    return View();
}

Yet again the convention comes up. MVC will now search for an appropriate file to use. If you run the application it will show an error message with a list of the file it searches for. We will use the following file because we use Razor and C#: Views/Main/Front.cshtml

@{
    ViewBag.Title = "Front page";
}
<h2>Front page</h2>

The ViewBag that we here use to set the title is an object that is available both in the controller and the view. It can be used to pass information from the controller to the view as in the following example:

public ViewResult Front()
{
    ViewBag.Greeting = "Hello world";
    return View();
}

 

@{
    ViewBag.Title = "Front page";
}
<h2>@ViewBag.Greeting</h2>

The last part is an example of the Razor syntax. This is used instead of the ASPX syntax used in ASP.NET Web Forms.

In practice you will use the ViewBag sparingly. Instead you can create classes in the Models namespace. These classes can either be used only for moving data between controllers and views or they can be used to represent tables in a database.