Skip to content

7. Ajaxification of an application ASP.NET MVC

7.1. The role of AJAX in a web application

For now, the training examples studied have the following architecture:

To navigate from a [Vue1] view to a [Vue2] view, the browser:

  • sends a request to the web application;
  • receives the [Vue2] view and displays it in place of the [Vue1] view.

This is the classic pattern:

  • request from the browser;
  • the web server generates a view in response to the client;
  • display of this new view by the browser.

There is another mode of interaction between the browser and the web server: AJAX (Asynchronous Javascript and Xml). This actually refers to interactions between the view displayed by the browser and the web server. The browser continues to do what it does best—display a view—but it is now controlled by Javascript embedded within the displayed view. The process works as follows:

  • in [1], an event occurs on the page displayed in the browser (click on a button, change of text, etc.). This event is intercepted by Javascript (JS) embedded in the page;
  • In [2], the Javascript code makes a HTTP request just as the browser would have done. The request is asynchronous: the user can continue to interact with the page without being blocked while waiting for the response to the HTTP request. The request follows the standard processing flow. Nothing (or very little) distinguishes it from a standard request;
  • in [3], a response is sent to the client JS. Rather than a complete HTML view, it is instead a partial HTML view, a XML or JSON (JavaScript Object Notation) that is sent;
  • in [4], Javascript retrieves this response and uses it to update a region of the displayed HTML page.

For the user, there is a change in view because what they see has changed. However, there is no full page reload, but simply a partial modification of the displayed page. This helps make the page more fluid and interactive: because there is no full page reload, we can handle events that we previously could not. For example, offering the user a list of options as they type characters into an input field. With each new character typed, a AJAX request is sent to the server, which then returns additional suggestions. Without Ajax, this type of input assistance was previously impossible. It was not possible to reload a new page with every character typed.

7.2. Basics of JQuery and Javascript

We have often included the Javascript and JQuery libraries in our pages. They contain the following line:


  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>

Note: Adjust the version from jQuery to match that of your version in Visual Studio.

The Ajax technology of ASP.NET MVC uses JQuery. We will write a few JQuery scripts ourselves. So let’s now cover the basics of JQuery you need to know to understand the scripts in this chapter.

We create a new [Exemple-04] project within our [Exemples] solution:

To use Ajax with ASP.NET MVC, a line must be present in the configuration file [Web.config] [1]:


  <appSettings>
...
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

Line 3 enables the use of Ajax in the ASP.NET views. It is present by default.

We create a file named HTML [JQuery-01.html] in the [Content] folder of the new [2] project:

This file will have the following content:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>JQuery-01</title>
  <script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
  <h3>Rudiments de JQuery</h3>
  <div id="element1">
    Elément 1
  </div>
</body>
</html>
  • line 6: import of JQuery (adapt version to match your version from Visual Studio);
  • lines 10–12: an element from the id [element1] page. We’re going to play around with this element.

View this file in the Google Chrome browser:

In Google Chrome, press [Ctrl-Maj-I] to bring up the developer tools [6]. The [Console] [7] tab allows you to run code Javascript. Below, we provide Javascript commands to type and explain them.

JS
result
$("#element1")
: returns the collection of all id elements, so normally a collection of 0 or 1 element because you cannot have two identical id elements on a single page.
$("#element1").text("blabla")
: sets the text to [blabla] for all elements in the collection. This changes the content displayed on the page
$("#element1").hide()
hides the elements in the collection. The text [blabla] is no longer displayed.
$("#element1")
: displays the collection again. This allows us to see that the element id [element1] has the attribute CSS style='display: none;', which causes the element to be hidden.
$("#element1").show()
: displays the elements in the collection. The text [blabla] appears again. This is due to the attribute CSS style='display: block;'.
$("#element1").attr('style','color: red')
: sets an attribute on all elements in the collection. The attribute here is [style] and its value is [color: red]. The text [blabla] turns red.
Tableau
Dictionnaire

Note that the browser's URL has not changed during all these operations. There has been no communication with the web server. Everything happens within the browser. Now, let's view the page's source code:

 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>JQuery-01</title>
  <script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
  <h3>Rudiments de JQuery</h3>
  <div id="element1">
    Elément 1
  </div>
</body>
</html>

This is the original text. It does not reflect the changes made to the element in lines 10–12. It is important to keep this in mind when debugging Javascript. In such cases, it is often unnecessary to view the source code of the displayed page. To view the source code of the currently displayed page, proceed as follows:

 

We now know enough to understand the JS scripts that follow.

7.3. Updating a page with a feed

7.3.1. The Views

We will examine the following application:

  • in [1], the page load time;
  • in [2], the four arithmetic operations are performed on two real numbers A and B;
  • in [3], the server’s response is displayed in a region of the page;
  • in [4], the time of the calculation. This is different from the page load time [5]. The latter is equal to [1], indicating that the region [6] has not been reloaded. Furthermore, the URL and [7] of the page have not changed.

7.3.2. The controller, actions, model, view

We create a controller named [Premier]:

To display the initial view, we create the following [Action01Get] action:


    [HttpGet]
    public ViewResult Action01Get()
    {
      ViewModel01 modèle = new ViewModel01();
      modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      return View(modèle);
}
  • line 4: instantiation of the view model;
  • line 5: initializing the view's load time;
  • line 6: displays the [Action10Get.cshtml] view and its model.

The [ViewModel01] model is as follows:


using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
 
namespace Exemple_04.Models
{
  [Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreur, HeureChargement, HeureCalcul")]
  public class ViewModel01
  {
    // form
    [Required(ErrorMessage="Donnée requise")]
    [Display(Name="Valeur de A")]
    [Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre positif ou nul")]
    public double A { get; set; }
    [Required(ErrorMessage = "Donnée requise")]
    [Display(Name = "Valeur de B")]
    [Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre positif ou nul")]
    public double B { get; set; }
 
    // results
    public string AplusB { get; set; }
    public string AmoinsB { get; set; }
    public string AmultipliéparB { get; set; }
    public string AdiviséparB { get; set; }
    public string Erreur { get; set; }
    public string HeureChargement { get; set; }
    public string HeureCalcul { get; set; }
  }
}
  • lines 11–14: the value A of the form;
  • lines 15–18: the value B from the form;
  • lines 21–24: the results of the four arithmetic operations on A and B;
  • line 25: the text of any error;
  • line 26: the time the view was loaded in the browser;
  • line 27: the time the fields in lines 21–24 were calculated;
  • line 7: this view template is also an action template. Fields that are not submitted by the browser are excluded from the latter.

The view [Action01Get.cshtml] is as follows:


@model Exemple_04.Models.ViewModel01
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    UpdateTargetId = "résultats",
    HttpMethod = "post",
    Url = Url.Action("Action01Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}
 
<!DOCTYPE html>
 
<html lang="fr-FR">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-01</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
  <script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
 
  <h2>Ajax - 01</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
    <table>
      <thead>
        <tr>
          <th>@Html.LabelFor(m => m.A)</th>
          <th>@Html.LabelFor(m => m.B)</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.TextBoxFor(m => m.A)</td>
          <td>@Html.TextBoxFor(m => m.B)</td>
        </tr>
        <tr>
          <td>@Html.ValidationMessageFor(m => m.A)</td>
          <td>@Html.ValidationMessageFor(m => m.B)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
  <hr />
  <div id="résultats" />
</body>
</html>
  • line 1: the view is based on a [ViewModel01] model;
  • line 21: JQuery is required for both validations and Ajax;
  • lines 22-23: the validation libraries;
  • lines 24–26: the internationalization libraries;
  • line 27: the Ajax library;
  • line 28: a local Javascript library;
  • line 33: displays the time the view was loaded;
  • line 35: an Ajax form—we’ll come back to this;
  • lines 40–41: labels for the A and B number inputs;
  • lines 46–47: input fields for numbers A and B;
  • lines 50-51: error messages for the A and B number inputs;
  • line 56: the button that submits the form. This will be submitted via an Ajax request;
  • line 57: a loading image displayed during the Ajax request;
  • line 58: a link to submit the form with an Ajax request;
  • line 62: a <div> tag with the id [résultats]. This is where we will place the HTML feed returned by the web server.

This view displays the following page:

 

Now let’s examine the code that handles the form’s Ajax request:


...
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    UpdateTargetId = "résultats",
    HttpMethod = "post",
    Url = Url.Action("Action01Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}
 
...
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
....
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
}
...
 <div id="résultats" />
  • line 15: instead of using [@Html.BeginForm], we use [@Ajax.BeginForm]. This method supports numerous overloads. The one used has the following signature:
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)

Here, we use the following actual parameters:

Action01Post: the name of the action that will process the POST from the form,

null: there is no route information to provide,

ajaxOpts: the Ajax call options. They were defined in lines 6–10,

new { id = "form" }: to assign the [id='formulaire'] attribute to the generated <form> tag;

The Ajax options used are as follows:

  • line 8: the URL target of the HTTP Ajax request;
  • line 7: method of the HTTP Ajax request;
  • line 6: the page region that will be updated by the response to the Ajax request;
  • line 9: id of the page region that will be displayed during the Ajax request – usually a loading image. Here, line 20 will be displayed. It contains an animated image symbolizing a loading state. Initially, this image is hidden by the style [display : none];
  • Line 10: Wait time in milliseconds before the animated image is displayed, here 1 second.

The HTML code generated by the Ajax form is as follows:


<form action="/Premier/Action01Post" data-ajax="true" data-ajax-loading="#loading" data-ajax-loading-duration="1000" data-ajax-method="post" data-ajax-mode="replace" data-ajax-update="#résultats" data-ajax-url="/Premier/Action01Post" id="formulaire" method="post">    <table>
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
</form>
<hr />
<div id="résultats" />
  • Line 1: the generated <form> tag. Note the [data-ajax-attr] attributes, which reflect the values of the fields in the [AjaxOptions] object associated with the Ajax request. These attributes are managed by the Ajax library. Without them, the <form> tag becomes:

<form action="/Premier/Action01Post" id="formulaire" method="post">
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
</form>

This is a standard HTML form. This code will be executed if the user disables Javascript in their browser. Lines 5–6 are then unused.

7.3.3. The [Action01Post] action

The [Action01Post] action that handles the HTTP Ajax request is as follows:


    [HttpPost]
    public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
    {
      // wait simulation
      Thread.Sleep(2000);
      // action model instantiation
      ViewModel01 modèle = new ViewModel01();
      // calculation time
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // model update
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // returns an error
        modèle.Erreur = getErrorMessagesFor(ModelState);
        return PartialView("Action01Error", modèle);
      }
      // every other time, an error is simulated
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        modèle.Erreur = "[erreur aléatoire]";
        return PartialView("Action01Error", modèle);
      }
      // calculations
      modèle.AplusB = string.Format("{0}", modèle.A + modèle.B);
      modèle.AmoinsB = string.Format("{0}", modèle.A - modèle.B);
      modèle.AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
      modèle.AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
      // view
      return PartialView("Action01Success", modèle);
}
  • line 1: the action only processes a [POST];
  • line 2: it accepts the following action model:
    • [FormCollection postedData]: all values posted by the Ajax request POST,
    • [SessionModel session]: the session elements. Here, we use a technique described in section 4.10;
  • line 2: the action will return a fragment HTML rather than a complete page HTML;
  • line 5: artificially, we pause for two seconds to simulate a long Ajax action;
  • line 7: a model of type [ViewModel01] is instantiated;
  • line 9: the calculation time is initialized;
  • line 11: we attempt to update the [ViewModel01] model with the posted values. Recall that there are two: the values of numbers A and B;
  • line 12: we check whether this update was successful;
  • line 15: if an error occurs, the [Erreur] field of the model is populated;
  • line 16: return a partial view [Action01Error.cshtml] using the model [ViewModel01];
  • lines 19–24: every other time, an error is simulated;
  • line 19: a random integer is generated in the range [0,1]. The random number generator is taken from the session;
  • line 20: if the generated value is 0, an error is simulated;
  • line 22: the error message is placed in the template;
  • line 23: a partial view [Action01Error.cshtml] is rendered using the template [ViewModel01];
  • lines 26–29: arithmetic calculations on numbers A and B are performed, and the results are placed in the template as character strings;
  • line 31: a partial view [Action01Success.cshtml] is rendered using the template [ViewModel01];

7.3.4. The view [Action01Error]

The view [Action01Error.cshtml] is as follows:


@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p style="color: red;">Une erreur s'is produced: @Model.Erreur</p>

Note that this partial HTML feed will be sent in response to the HTTP Ajax request of type POST and placed on the page in theid [résultats]. All this information comes from the Ajax configuration used in the main page [Action01Get.cshtml]:


@model Exemple_04.Models.ViewModel01
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    UpdateTargetId = "résultats",
    HttpMethod = "post",
    Url = Url.Action("Action01Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}

Here is an example of a response with an error:

 

7.3.5. The view [Action01Success]

The [Action01Success.cshtml] view is as follows:


@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>

Again, this partial HTML feed will be sent in response to the HTTP Ajax request of type POST and placed on the page in theid [résultats]:

 

7.3.6. G ession Management

We have seen that [Action01Post] uses the session. The session model is the following [SessionModel] type:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    public Random Randomizer { get; set; }
  }
}

The session is initialized in [Global.asax]:


    // Session
    protected void Session_Start()
    {
      SessionModel sessionModel=new SessionModel();
      sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
      Session["data"] = sessionModel;
}

The session is associated with a model in [Application_Start]:


    protected void Application_Start()
    {
...
      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}

The [SessionModelBinder] class has been described.

7.3.7. Handling the placeholder image


@model Exemple_04.Models.ViewModel01
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
...
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}
 
...
<body>
 
...
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
...

When the Ajax request starts, the region in line 7 is displayed after one second. This region is the image of line 21, which was initially hidden. This results in the following interface:

Let’s examine the link [Calculer] on the main page [Action01Get.cshtml]:


<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-01</title>
  ...
  <script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
 
  <h2>Ajax - 01</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
  <hr />
<div id="résultats" />
  • Line 18: Clicking the [Calculer] link triggers the execution of the JS [postForm] function. This function is defined in the [myScripts-01.js] file on line 5. The script is as follows:

function postForm() {
  // on fait un appel Ajax à la main avec JQuery
  var loading = $("#loading");
  var formulaire = $("#formulaire");
  var résultats = $('#results');
  $.ajax({
    url: '/Premier/Action01Post',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'html',
    begin: loading.show(),
    success: function (data) {
      loading.hide()
      résultats.html(data);
    }
  })
}
 
// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}
 
$.validator.methods.date = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseDate(value));
}
 
jQuery.extend(jQuery.validator.methods, {
  range: function (value, element, param) {
    //Use the Globalization plugin to parse the value        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

The functions in lines 19–37 were already covered in Section 6.1. They handle the internationalization of pages. We will not revisit them here. In lines 1–17, we manually make the Ajax call, which in the case of the [Calculer] button was handled by the project’s Ajax library. To do this, we use the JQuery library associated with the project.

  • Line 3: a reference to the id component [loading]. [$("#loading")] returns the collection of elements from id [loading]. There is only one;
  • line 4: a reference to the component of id [formulaire];
  • line 5: a reference to the id [résultats] component;
  • line 6: the Ajax call with its options;
  • line 7: the URL target of the Ajax call;
  • line 8: the method used;
  • line 9: the posted data. [formulaire.serialize] creates the string [A=val1&B=val2] from the POST of the id [formulaire] form;
  • line 10: the expected return data type. We know that the server will return a HTML stream;
  • line 11: the method to execute when the request starts. Here, we specify that the id [loading] component must be displayed. This is the animated loading image;
  • line 12: the method to execute if the Ajax request is successful. The parameter [data] is the complete response from the server. We know this is a HTML stream;
  • line 13: we hide the loading indicator;
  • line 14: we update the id component with the HTML from the [data] parameter.

The reader is invited to test the [Calculer] link. It functions like the [Calculer] button, with one exception: an error message appears. Once this link has been used, invalid values for A and B can then be submitted:

  • in [1] and [2], invalid values were entered. They are flagged by the client-side validators;
  • in [3], we clicked on the link [Calculer];
  • In [4], there was a [POST] since we get the response [4].

When the values are invalid and the [Calculer] button is clicked, the [POST] request to the server does not occur. In the same case, with the [Calculer] link, the [POST] request to the server is sent. There is therefore a behavior of the [Calculer] button that we were unable to reproduce with the [Calculer] link. Rather than trying to resolve this issue now, we are leaving it for a later example that will also illustrate another client-side validation issue.

7.4. Updating a HTML page with a JSON feed

In the previous example, the web server responded to the HTTP Ajax request with a HTML response. This response contained data accompanied by HTML formatting. We propose to revisit the previous example, this time with JSON responses (JavaScript Object Notation) containing only the data. The advantage is that fewer bytes are transmitted.

7.4.1. The [Action02Get] action

The [Action02Get] action will be the entry point for the new application. Its code is as follows:


@model Exemple_04.Models.ViewModel02
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    HttpMethod = "post",
    Url = Url.Action("Action02Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000,
    OnBegin = "OnBegin",
    OnFailure = "OnFailure",
    OnSuccess = "OnSuccess",
    OnComplete = "OnComplete"
  };    
}
 
<!DOCTYPE html>
 
<html lang="fr-FR">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-02</title>
....
  <script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
</head>
<body>
  <h2>Ajax - 02</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  @using (Ajax.BeginForm("Action02Post", null, ajaxOpts, new { id = "formulaire" }))
  {
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
  <hr />
  <div id="entete">
    <h4>Résultats</h4>
    <p><strong>Heure de calcul : <span id="heureCalcul"/></strong></p>
  </div>
  <div id="résultats">
    <p>A+B=<span id="AplusB"/></p>
    <p>A-B=<span id="AmoinsB"/></p>
    <p>A*B=<span id="AmultipliéparB"/></p>
    <p>A/B=<span id="AdiviséparB"/></p>
  </div>
  <div id="erreur">
    <p style="color: red;">Une erreur s'is produced: <span id="msg"/></p>
  </div>
</body>
</html>
  • lines 4–14: the Ajax call options;
  • line 10: the JS function to be executed when the request starts. This function is defined in the JS file referenced on line 24;
  • line 11: the JS function to be executed if the request fails;
  • line 12: the JS function to be executed if the request succeeds;
  • line 13: the JS function to be executed after the Ajax request has returned its result (failure or success);
  • lines 40–43: a region of id [entete];
  • lines 44–49: a region of id [résultats]. It will display the results of the four arithmetic operations;
  • lines 50-52: a region of id [erreur]. It will display any error messages.

7.4.2. The [Action02Post] action

The Ajax request is handled by the following [Action02Post] action:


[HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // wait simulation
      Thread.Sleep(2000);
      // model validation
      ViewModel02 modèle = new ViewModel02();
      // loading and calculation times
      string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // model update
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // returns an error
        return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
      }
      // every other time, an error is simulated
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        // returns an error
        return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
      }
      // calculations
      string AplusB = string.Format("{0}", modèle.A + modèle.B);
      string AmoinsB = string.Format("{0}", modèle.A - modèle.B);
      string AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
      string AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
      // we return the results
      return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
    }
  • line 2: the method returns a [JsonResult] type, i.e., text in JSON format;
  • line 16: the information is returned as an anonymous class instance serialized in JSON. The [getErrorMessagesFor] method has already been presented. The JSON string sent to the browser will have the following form:
{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}
  • Line 31: same approach for arithmetic results. This time, the string JSON sent to the browser will have the following format:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}

7.4.3. The client-side code Javascript

Recall the configuration of the Ajax call in the HTML page sent to the client browser:


  AjaxOptions ajaxOpts = new AjaxOptions
  {
    HttpMethod = "post",
    Url = Url.Action("Action02Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000,
    OnBegin = "OnBegin",
    OnFailure = "OnFailure",
    OnSuccess = "OnSuccess",
    OnComplete = "OnComplete"
};    

The JS functions referenced on lines 7–10 (to the right of the "=" sign) are defined in the following [myScripts-02.js] file:


// global data
var entete;
var loading;
var résultats;
var erreur;
var heureCalcul;
var msg;
var AplusB;
var AmoinsB;
var AmultipliéparB;
var AdiviséparB;
var formulaire;
...
function postForm() {
...
}
 
// document loading
$(document).ready(function () {
  formulaire = $("#formulaire");
  entete = $("#entete");
  loading = $("#loading");
  erreur = $("#erreur");
  résultats = $('#résultats');
  heureCalcul = $("#heureCalcul");
  msg = $("#msg");
  AplusB = $("#AplusB");
  AmoinsB = $("#AmoinsB");
  AmultipliéparB = $("#AmultipliéparB");
  AdiviséparB = $("#AdiviséparB");
 
  // hide certain page elements
  entete.hide();
  résultats.hide();
  erreur.hide();
});
 
// start
function OnBegin() {
....
}
 
// end of query
function OnComplete() {
...
}
 
// success
function OnSuccess(data) {
....
}
 
// error
function OnFailure(request, error) {
...
}
  • line 19: the JS function executed at the end of the page load in the browser;
  • lines 20–30: we retrieve the references of all the page components we are interested in. Searching for a component on a page incurs a cost, so it is best to do this only once;
  • lines 33–35: the components [entete], [résultats], and [loading] are hidden;

When the Ajax request starts, the following function is executed:


// start
function OnBegin() {
  // wait signal on
  loading.show();
  // hide certain page elements
  entete.hide();
  résultats.hide();
  erreur.hide();
}
  • line 4: the [loading] component is displayed. This is the animated image;
  • lines 6–8: the components [entete], [résultats], and [erreur] are hidden;

If the Ajax request is successful, the following JS code is executed:


// success
function OnSuccess(data) {
  // displaying results
  heureCalcul.text(data.HeureCalcul);
  entete.show();
  if (data.Erreur != '') {
    msg.text(data.Erreur);
    erreur.show();
    return;
  }
  // no error
  AplusB.text(data.AplusB);
  AmoinsB.text(data.AmoinsB);
  AmultipliéparB.text(data.AmultipliéparB);
  AdiviséparB.text(data.AdiviséparB);
  résultats.show();
}

To understand this code, you need to recall the two strings JSON that may be sent back to the browser:

{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}

in case of an error, otherwise the string:

{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}

If we call this string [data], the value of the field [Erreur] is obtained using the notation [data.Erreur] or [data["Erreur"]], as desired. The same applies to the other fields in the string JSON. Furthermore, to assign unformatted text to a component of id X, we write [X.text(chaine)]. Let’s return to the code for the [OnSuccess] function:

  • line 2: [data] is the received string JSON;
  • line 4: the component [heureCalcul] is assigned its value;
  • line 5: the component [entete] is displayed;
  • line 6: testing the [Erreur] field of the string JSON;
  • line 7: the component [msg] is assigned a value;
  • line 8: the component [erreur] is displayed;
  • line 9: the error case is complete;
  • line 12: the [AplusB] component receives its value;
  • line 13: the component [AmoinsB] is assigned a value;
  • line 14: the component [AmultipliéparB] receives its value;
  • line 15: the component [AdiviséparB] is assigned a value;
  • line 16: the component [résultats] is displayed.

The function [OnFailure] ( ) will be executed if the HTTP Ajax request fails. This failure is indicated by the code HTTP returned by the server. For example, the 500 code [Internal Server Error] indicates that the server was unable to execute the request. The [OnFailure] function is as follows:


// error
function OnFailure(request, error) {
  alert("L'erreur suivante s'est produite :" + error);
}

We simply display a dialog box with the error that occurred. In practice, we need to be more specific. We will soon propose another solution.

Finally, the [OnComplete] function is executed when the request is complete, whether it succeeds or fails.


// end of query
function OnComplete() {
  // wait signal off
  loading.hide();
}

Note that it is the configuration of the Ajax call in view [Action02Get.cshtml] that causes these various functions to be called:


  AjaxOptions ajaxOpts = new AjaxOptions
  {
...
    OnBegin = "OnBegin",
    OnFailure = "OnFailure",
    OnSuccess = "OnSuccess",
    OnComplete = "OnComplete"
};

The code HTML for the link [Calculer] in the view [Action02Get.cshtml] is as follows:


      <a href="javascript:postForm()">Calculer</a>

The function JS [postForm] is found in the imported file [myScripts-02.js]:


  <script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>

Its code is as follows:


function postForm() {
  // make a manual Ajax call with JQuery
  $.ajax({
    url: '/Premier/Action02Post',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'json',
    beforeSend: OnBegin,
    success: OnSuccess,
    error: OnFailure,
    complete: OnComplete
  })
}

We have already encountered similar code.

  • line 4: URL target of the Ajax call;
  • line 5: command HTTP used by the Ajax call;
  • line 6: posted values. These are the result of serializing the form values. The form, identified by id [formulaire], is referenced by the variable [formulaire]. [data] will be a string in the form [A=val1&B=val2];
  • line 7: expected response format type. This is a string JSON;
  • line 8: function JS to be executed when the Ajax call starts;
  • line 9: function JS to be executed if the Ajax call succeeds;
  • line 10: function JS to be executed if the Ajax call fails;
  • line 11: function JS to be executed once the server response is received, regardless of whether it is a success or an error.

Let’s revisit the Javascript function, which handles the case where the Ajax call fails (line 10). The Ajax call fails in various situations, for example when the server returns an error code such as [403 Forbidden], [404 Not Found], [500 Internal Server Error], [301 Moved Permanently], ...

In the previous example, the function [OnFailure] is as follows:


// error
function OnFailure(request, error) {
  alert("L'erreur suivante s'est produite :" + error);
}

Generally, displaying the [error] object does not provide any useful information. If an Ajax call is made using JQuery, you can use the following [OnFailure] method;


// error
function OnFailure(jqXHR) {
  alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
  msg.html(jqXHR.responseText);
  erreur.show();
}

The JQuery [jqXHR] object has the following properties:

  • responseText: the text of the server response;
  • status: the error code returned by the server;
  • statusText: the text associated with this error code.
  • line 3: display the error code and its corresponding label;
  • line 4: the server's response HTML is placed in the id [msg] component;
  • line 5: display the region of id [erreur].

To test this error handling function, we will artificially create an exception in the [Action02Post] action:


    [HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // an artificial exception to test the Ajax call error function
      throw new Exception();
      // wait simulation
      Thread.Sleep(2000);
      // model validation
...

Line 5 throws an exception. Now let's test the application:

We get the following response: [1] and [2]:

The server response allows us to see on which line of the server code the error occurred. This is often useful information to know. From now on, we will use this technique to handle errors in Ajax calls.

7.5. Single-Page Web Application

Ajax technology allows us to build single-page applications:

  • The first page is generated by a standard browser request;
  • the subsequent pages are retrieved via Ajax calls. As a result, the browser never leaves URL and never loads a new page. This type of application is called a Single Page Application (APU).

Here is a basic example of such an application. The new application will have two views:

  • in [1], the action [Action03Get] allows us to access the first page, page 1;
  • in [2], a link allows us to navigate to page 2 via an Ajax call;
  • In [3], URL has not changed. The page displayed is page 2;
  • In [4], a link allows us to return to page 1 via an Ajax call;
  • In [5], URL has not changed. The page displayed is page 1.

The code for the [Action03Get] action is as follows:


    [HttpGet]
    public ViewResult Action03Get()
    {
      return View();
}
  • Line 4: The view [Action03Get.cshtml] is displayed.

The view [Action03Get.cshtml] is as follows:


@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action03Get</title>
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
  <h3>Ajax - 03 - Single Page Application</h3>
  <div id="content">
    @Html.Partial("Page1")
  </div>
</body>
</html>
  • lines 16-18: an element of id [content]. This is the element where the various pages will be displayed;
  • line 17: by default, the [Page1.cshtml] page will be displayed first.

The [Page1.cshtml] page is as follows:


<h4>Page 1</h4>
  <p>
    @Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
  • line 1: the page title to distinguish it from page 2;
  • line 3: an Ajax link with the following parameters:
    • the link label [Page 2];
    • the target action of the link [Action04];
    • the parameters of the requested URL. This will be [/Premier/Action04?Page=2];
  • the options for the Ajax call. Here, only the region to be updated with the server response is specified. For the other options, default values are used when they exist. The default method is HTTP.

Let’s see what happens when the link is clicked. The URL [/Premier/Action04?Page=2] is requested with a GET. The [Action04] action is then executed:


    [HttpGet]
    public PartialViewResult Action04(string page = "1")
    {
      string vue = "Page1";
      if (page == "2")
      {
        vue = "Page2";
      }
      return PartialView(vue);
}
  • line 2: the action returns a partial HTML flow;
  • line 2: the action uses the string [page] as its template. However, we know that URL contains this information: [/Premier/Action04?Page=2]. Note that the template is case-insensitive;
  • lines 4–8: [vue] will be assigned the value [Page2];
  • line 9: the partial view [Page2.cshtml] is returned.

The partial view [Page2.cshtml] is as follows:


<h4>Page 2</h4>
  <p>
    @Ajax.ActionLink("Page 1", "Action04", new { Page = 1 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>

The server therefore returns the HTML stream shown above in response to the Ajax call GET [/Premier/Action04?Page=2]. Recall that this Ajax call uses this response to update the region of id [content] (line 3 below):


<h4>Page 1</h4>
  <p>
    @Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>

This results in the following new display: [1]:

Following the same logic, we see that clicking the link [Page 1] from [1] will trigger the display of [2].

Let’s return to the general diagram of a ASP.NET MVC application:

Thanks to the Javascript embedded in the HTML pages and executed in the browser, we can offload code to the browser and achieve the following architecture:

  • In [1], the ASP.NET MVC web layer has become a web interface for accessing data, typically stored in a database. The views delivered contain only data and no HTML layout, such as XML or JSON feeds;
  • In [2]: the browser displays static views (i.e., not dynamically generated) delivered by a web server that may or may not be on the same machine as the [1] server. These static views are then enriched with data obtained by Javascript from the [1] web interface;
  • the Javascript code embedded in the HTML pages can be structured in layers:
    • the [présentation] layer handles user interactions,
    • the [DAO] layer handles data access via the [1] web server,
    • the [métier] layer corresponds to the [métier] layer, which was previously on the [1] server and has been moved to the [2] browser;

The advantage of this architecture is that it involves different skill sets:

  • the code on the [1] web server requires .NET skills but not Javascript, HTML, or CSS skills;
  • the code embedded in the [2] browser requires skills Javascript, HTML, CSS but is independent of the web server technology [1].

Thus, this architecture facilitates parallel work by teams with different skill sets. It is particularly well-suited to Single-Page Applications.

7.6. Single-Page Web Application and Client-Side Validation

We previously mentioned an anomaly in the Ajax-01 example. Here is a recap of the context:

  • in [1] and [2], invalid values were entered. These are flagged by the client-side validators;
  • in [3], the link [Calculer] was clicked;
  • In [4], there was a [POST] since we get the response [4].

When the values are invalid and the [Calculer] button is clicked, the [POST] request to the server does not occur. In the same case, with the link [Calculer], the [POST] request to the server is sent. There is therefore a behavior of the [Calculer] button that we were unable to reproduce with the [Calculer] link.

We will revisit this example in a new context: the application will have multiple views and will be of the type [Application à Page Unique] that we just described.

7.6.1. The views in the example

The example has several views:

  • in [1], the view [Action05Get];
  • in [2], the partial view [Formulaire05];
  • in [3], the partial view [Failure05];
  • in [4], the partial view [Success05].

The application is a single-page application: the page is loaded by the browser during the first request. It is then updated via Ajax calls.

The previous pages are generated by the following [cshtml] views:

The view initially loaded is the following [Action05Get.cshtml] view:


@model Exemple_04.Models.ViewModel05
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html lang="fr-FR">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-05</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
  <script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
</head>
<body>
 
  <h2>Ajax - 05, Page unique - Validation formulaire côté client</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
  <div id="content">
    @Html.Partial("Formulaire05", Model)
  </div>
</body>
</html>

Note the following points:

  • line 1: the view model is of type [ViewModel05], which we will cover shortly;
  • lines 13–19: these contain the Javascript scripts required for Ajax and client-side validation;
  • line 20: we will add our own Javascript functions to [myScripts-05.js];
  • line 27: the animated loading image;
  • lines 28–30: an id tag. This is where the partial views will be inserted;
  • line 29: insertion of the partial view [Formulaire05].

The view [Action05Get] is responsible for displaying the [1] section of the initial page:

The partial view [Formulaire05] will generate the [2] section above. Its code is as follows:


@model Exemple_04.Models.ViewModel05
 
@using (Html.BeginForm("Action05Post", "Premier", FormMethod.Post, new { id = "formulaire" }))
{
  <table>
    <thead>
      <tr>
        <th>@Html.LabelFor(m => m.A)</th>
        <th>@Html.LabelFor(m => m.B)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>@Html.TextBoxFor(m => m.A)</td>
        <td>@Html.TextBoxFor(m => m.B)</td>
      </tr>
      <tr>
        <td>@Html.ValidationMessageFor(m => m.A)</td>
        <td>@Html.ValidationMessageFor(m => m.B)</td>
      </tr>
    </tbody>
  </table>
  <p>
    <table>
      <tbody>
        <tr>
          <td><a href="javascript:calculer()">Calculer</a>
          </td>
          <td style="width: 20px" />
          <td><a href="javascript:effacer()">Effacer</a>
          </td>
        </tr>
      </tbody>
    </table>
  </p>
}
  • line 1: the partial view uses a [ViewModel05] type as its template;
  • line 3: the form generated by the [Html.BeginForm] method. Because this form will be submitted via an Ajax call, the first three parameters of the method will be ignored. Unless the user has disabled Javascript in their browser. We are ignoring this possibility here. The fourth parameter is important. The form will have the id [formulaire];
  • lines 5–22: the form for entering the numbers A and B;
  • line 27: a link Javascript that initiates the execution of the four arithmetic operations on A and B;
  • Line 30: a link (Javascript) that clears the entries and any associated error messages.

Note that the form does not have a [submit] button. We will need to manually generate the [Post] for the entered values A and B.

If there are no errors, the results are displayed:

The [4] section above is generated by the following partial view [Success05.cshtml]:


@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • line 1: the partial view [Success05.cshtml] receives a template of type [ViewModel05];
  • line 12: a link Javascript to return to entries.

In case of an error, another partial view [3] is displayed:

This view is generated by the following [Failure05.cshtml] code:


@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model.Erreurs)
  {
    <li>@msg</li>
  }
</ul>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • line 1: the partial view [Failure05.cshtml] receives a template of type [ViewModel05];
  • line 14: a link Javascript to return to entries.

7.6.2. The view template

All of the previous views share the same template [ViewModel05]:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
 
namespace Exemple_04.Models
{
  [Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreurs, HeureChargement, HeureCalcul")]
  public class ViewModel05
  {
    // form
    [Required(ErrorMessage="Donnée A requise")]
    [Display(Name="Valeur de A")]
    [Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre A positif ou nul")]
    public string A { get; set; }
    [Required(ErrorMessage = "Donnée B requise")]
    [Display(Name = "Valeur de B")]
    [Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre B positif ou nul")]
    public string B { get; set; }
 
    // results
    public string AplusB { get; set; }
    public string AmoinsB { get; set; }
    public string AmultipliéparB { get; set; }
    public string AdiviséparB { get; set; }
    public List<string> Erreurs { get; set; }
    public string HeureChargement { get; set; }
    public string HeureCalcul { get; set; }
  }
}

This is the [ViewModel01] model already presented, with a few minor differences:

  • lines 15 and 19: fields A and B are now of type [string] in order to display empty input fields rather than fields with the value 0 when the input form is first displayed;
  • lines 14 and 18: this does not prevent the entered value from being validated using a [Range] validator;
  • line 26: a list of error messages displayed by the [Failure05] view.

7.6.3. Scope data [Session]

In section 7.3.6, we saw that the session data was encapsulated in the following [SessionModel] model:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    // the random number generator
    public Random Randomizer { get; set; }
  }
}

This session model is extended to include the values of A and B:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    // the random number generator
    public Random Randomizer { get; set; }
    // the values of A and B
    public string A { get; set; }
    public string B { get; set; }
  }
}

It is indeed necessary to store the values of A and B in the session, as shown in the following sequence:

Request 1

Query 2

In [4], we find the entries made in [1]. However, there are two distinct requests, HTTP. We know that the session serves as the memory between two HTTP requests. For the second request to retrieve the values posted by the first, those values must be stored in the session.

7.6.4. The server action [Action05Get]

The [Action05Get] action is the action that displays the initial single page. Its code is as follows:


    [HttpGet]
    public ViewResult Action05Get()
    {
      ViewModel05 modèle = new ViewModel05();
      modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      return View(modèle);
}
  • line 6: the previously discussed [Action05Get.cshtml] view is displayed with a model of type [ViewModel05];

7.6.5. The [Calculer] client action

Let’s examine the user’s interactions with the views:

The link [1] is a link to Javascript:


<a href="javascript:calculer()">Calculer</a>

The function Javascript [calculer] is found in the file [myScripts-05.js]:


  <script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>

The code for the Javascript [calculer] function is as follows:


// global data
var content;
var loading;
 
function calculer() {
  // first the references on DOM
  var formulaire = $("#formulaire");
  // then validate the form
  if (!formulaire.validate().form()) {
    // invalid form - terminated
    return;
  }
  // make a manual Ajax call
  $.ajax({
    url: '/Premier/Action05FaireCalcul',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
    },
    error: function (jqXHR) {
      // display server response
      content.html(jqXHR.responseText);
    }
  })
}
 
function retourSaisies() {
 ...
}
 
function effacer() {
  ...
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
  // we hide the moving image
  loading.hide();
});
  • Note that the code Javascript is always executed on the client side, in the browser;
  • line 44: the JS function is executed when the initial loading of the single page is complete;
  • line 46: reference to the animated image id [loading];
  • line 47: reference to the region of id [content]. It is this region that receives the partial views [Formulaire05, Success05, Failure05];
  • lines 2-3: the variables in lines 46-47 are declared global so that other functions can access them. There is a cost associated with searching for elements on a page (lines 46-47). There is no need to repeat this search if it can be avoided;
  • line 5: the function [calculer];
  • line 7: a reference to the form is retrieved. The partial view [Formulaire05] assigned it the id [formulaire];
  • line 9: this instruction executes the client-side form validators. This is what was missing in the issue noted on page 183. This method is provided by the [jquery.unobstrusive-ajax] library used by the single page:

  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>

The statement returns [false] if the form is declared invalid;

  • line 11: the Ajax call to the server is not made if the form is invalid;
  • lines 14–32: the Ajax call is made to the server;
  • line 15: the URL target is the server action [Action05FaireCalcul];
  • line 16: it is requested via a [POST];
  • Line 17: the entered values. These are the values entered in the form, in this case the values for A and B;
  • Lines 22–24: if the Ajax call is successful, the [calculer] function updates the id region with the HTML data stream sent by the server.

This HTML stream is the one sent by the [Action05FaireCalcul] action targeted by the Ajax call. The code for this server-side action is as follows:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // model
      ViewModel05 modèle = new ViewModel05();
      // calculation time
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // model update
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // returns an error
        modèle.Erreurs = getListOfMessagesFor(ModelState);
        return PartialView("Failure05", modèle);
      }
...
}
  • line 1: the action only accepts a [post];
  • line 2: it returns a partial view;
  • line 2: it receives the posted values (postedData) and the session model (session) as parameters;
  • line 5: the partial view template is created;
  • line 7: it is updated with the calculation time;
  • line 9: we attempt to apply the posted values to the model. Its validators will then be executed. One might wonder why we go to this trouble when the client-side validators prevent POST from being posted if the entered data is invalid. In fact, we are not sure of the origin of the POST. It may have been generated by code that is not ours. Therefore, we must always perform server-side checks;
  • line 10: we check if the validators were successful;
  • line 13: if the model is invalid, we update it with a list of errors. We will not detail the internal method [getListOfMessagesFor], which is analogous to the method [GetErrorMessagesFor] described on page 65;
  • line 14: the partial view [Failure05] is displayed with its model. Here is the code for this view;

@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model.Erreurs)
  {
    <li>@msg</li>
  }
</ul>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • Lines 7-12: The list of errors in the template is displayed using a <ul> tag.

Recall that the function JS [calculer], which generates [Post] at theserver action [Action05FaireCalcul] will place this HTML stream in the region of id [content]. This results in something like this:

Let’s continue examining the code for the [Action05FaireCalcul] action:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // model
      ViewModel05 modèle = new ViewModel05();
...
      // we put the values of A and B in session
      session.A = modèle.A;
      session.B = modèle.B;
      // no errors so far
      List<string> erreurs = new List<string>();
      // every other time, an error is simulated
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        erreurs.Add("[erreur aléatoire]");
      }
      if (erreurs.Count != 0)
      {
        modèle.Erreurs = erreurs;
        return PartialView("Failure05", modèle);
      }
      // calculations
      double A = double.Parse(modèle.A);
      double B = double.Parse(modèle.B);
      modèle.AplusB = string.Format("{0}", A + B);
      modèle.AmoinsB = string.Format("{0}", A - B);
      modèle.AmultipliéparB = string.Format("{0}", A * B);
      modèle.AdiviséparB = string.Format("{0}", A / B);
      // view
      return PartialView("Success05", modèle);
}
  • line 7: the model has been declared valid;
  • lines 8-9: the entered values A and B are stored in the session. We want to be able to retrieve them in the following query;
  • lines 11–22: we randomly generate an error every other time;
  • lines 24–29: we perform the four arithmetic operations on the entered real numbers;
  • line 31: the partial view [Success05] is returned with its model. This partial view is as follows:

@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>

Recall that the function JS [calculer], which generated [Post] at theserver action [Action05FaireCalcul] will place this HTML flow in the region of id [content]. This results in something like this:

7.6.6. The client action [Effacer]

The link Javascript [Effacer] allows the form to be reset to its initial state:

In the form, the link JS [Effacer] is defined as follows:


<a href="javascript:effacer()">Effacer</a>

The function JS [effacer] is defined in the file [myScripts-05.js] as follows:


// global data
var content;
var loading;
 
function calculer() {
...
}
 
function retourSaisies() {
...
}
 
function effacer() {
  // first the references on DOM
  var formulaire = $("#formulaire");
  var A = $("#A");
  var B = $("#B");
  // valid values are assigned to entries
  A.val("0");
  B.val("0");
  // then validate the form to make
  // any error msg
  formulaire.validate().form();
  // then assign empty strings to the input fields
  A.val("");
  B.val("");
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
  // we hide the moving image
  loading.hide();
});
  • lines 15-17: retrieve references to various elements of DOM (Document Object Model);
  • lines 19-20: we enter valid values into the input fields for numbers A and B;
  • line 23: run the client-side validators. Since the values of A and B are valid, this will cause any error messages that might be displayed to disappear;
  • lines 25-26: empty strings are entered into the input fields for numbers A and B;

7.6.7. The client action [Retour aux Saisies]

The link Javascript [Retour aux Saisies] allows you to return to the form after obtaining results:

In the form, the link JS [Retour aux Saisies] is defined as follows:


  <a href="javascript:retourSaisies()">Retour aux saisies</a>

The function JS [retourSaisies] is defined in the file [myScripts-05.js] as follows:


// global data
var content;
var loading;
 
function calculer() {
...
}
 
function retourSaisies() {
  // make a manual Ajax call
  $.ajax({
    url: '/Premier/Action05RetourSaisies',
    type: 'POST',
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
      // IMPORTANT!! validation
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}
 
function effacer() {
...
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
  // we hide the moving image
  loading.hide();
});
  • lines 11-29: an Ajax call;
  • line 12: the URL target;
  • line 13: it will be requested by a HTTP POST command. This is a POST without posted parameters. That is why we do not find a line of the type:
    data: formulaire.serialize(),

in the Ajax call;

  • line 14: the expected response from the server is a HTML response;
  • lines 18–20: this HTML stream will be used to update the region of id [content];

The server action [Action05RetourSaisies] is as follows:


    [HttpPost]
    public PartialViewResult Action05RetourSaisies(SessionModel session)
    {
      // view
      return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
  • line 2: the action receives as a parameter the session model in which we previously stored the entered values of A and B;
  • line 5: we return the partial view [Formulaire05] with a model of type [ViewModel05], in which we take care to initialize the A and B fields with the values of A and B taken from the session;

Now let’s return to the code for the Javascript and [retourSaisies] functions:


function retourSaisies() {
  // make a manual Ajax call
  $.ajax({
    url: '/Premier/Action05RetourSaisies',
    type: 'POST',
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
      // IMPORTANT!! validation
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}
  • line 13: the method executed when the Ajax call is complete;
  • line 14: the animated loading image is hidden;
  • line 16: a somewhat cryptic instruction I found on net to resolve the following issue: in the form displayed by the link [Retour aux saisies], the client-side validators were no longer working. While searching for information on the JS [jquery.unobtrusive-ajax] library, I found the solution in line 16. It parses the form, perhaps to activate the client-side validators.

7.7. Making a ASP.NET application accessible on the Internet

See section 9.26.

7.8. Generating a native Android application from a single-page application APU

See section 9.27.