Jquery with mvc3

JQuery has been always an interesting topic, but I didn’t find time to try it… looking into MVC3 finally I get in touch with it.

I succeded in using asp.net engine together with MVC3 in the same web app.

For some reason global.asax events didn’t fire up, but I found that deleting the file and adding back fixes this problem.PaceSpeedCalculatorModel

I tried to reuse asp.net master pages, without success. I gave up when I got an error saying that System.Web.Mvc.ViewMasterPage doesn’t derive from the correct class. I‘ll wait when this will be natively supported (MVC4?) or when someone else will discover a workaround.

The solution proposed works fine for reusing aspx master pages in a razor view (extensions methods does the magic!!!)

The goal I want to achieve is a simple conversion between pace and speed (pace is how much minutes and seconds I takes to cross 1km, speed is in km/h).

So 3 textbox minutes, seconds, speed.

I created a model for this, with appropriate annotations.

PaceSpeedCalculatorModel.cs
  1. namespace FitCalc.Models
  2. {
  3.     public class PaceSpeedCalculatorModel
  4.     {
  5.         [Required]
  6.         [StringLength(2, MinimumLength = 1)]
  7.         [Display(Name = "paceMinutes")]
  8.         [RegularExpression(@"[0-9]{1,2}", ErrorMessage = "Minutes must be in the range 00-99")]
  9.         [ScaffoldColumn(false)]
  10.         [DataType(DataType.Text)]
  11.        
  12.         [Range(0,99)]
  13.         public string paceMinutes { get; set; }
  14.  
  15.         [Required]
  16.         [StringLength(2, MinimumLength = 1)]
  17.         [Display(Name = "paceSeconds")]
  18.         [RegularExpression(@"[0-9]{1,2}", ErrorMessage = "Seconds must be in the range 00-59")]
  19.         [ScaffoldColumn(false)]
  20.         [DataType(DataType.Text)]
  21.         [Range(0, 59)]
  22.         public string paceSeconds { get; set; }
  23.  
  24.         [Required]
  25.         [StringLength(2, MinimumLength = 1)]
  26.         [Display(Name = "speed")]
  27.         [ScaffoldColumn(false)]
  28.         [DataType(DataType.Text)]
  29.         [Range(0,99)]
  30.         public string speed { get; set; }
  31.  
  32.  
  33.     }
  34. }

Well, not really appropriate… It would be better if the DataType was an integer value for minutes and seconds, but I didn’t find the corresponding value (maybe custom, but for now Text is enough).

Created the controller. I wanted to calculate values either by posting the form data and by doing calculation as you type.

CalculatorController.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Mvc;
  6. using FitCalc.Models;
  7.  
  8. namespace FitCalc.Controllers
  9. {
  10.     public class CalculatorController : Controller
  11.     {
  12.         //
  13.         // GET: /Calculator/
  14.         private static PaceSpeedCalculatorModel _paceSpeed = new PaceSpeedCalculatorModel();
  15.  
  16.  
  17.         public ActionResult Index()
  18.         {
  19.             //_paceSpeed.paceMinutes = "1";
  20.             //_paceSpeed.paceSeconds = "0";
  21.  
  22.             return this.RazorView(_paceSpeed);
  23.         }
  24.         [HttpPost]
  25.         public ActionResult Index(PaceSpeedCalculatorModel pscm)
  26.         {
  27.             _paceSpeed = pscm;
  28.  
  29.  
  30.             return RedirectToAction("Index");
  31.         }
  32.  
  33.         [HttpPost]
  34.         public ActionResult PaceToSpeed(PaceSpeedCalculatorModel pscm)
  35.         {
  36.             if (pscm.speed == null)
  37.                 pscm.speed = RunningCalc.PaceToSpeed(new RunningTime(pscm.paceMinutes, pscm.paceSeconds)).ToString();
  38.             else
  39.             {
  40.                 double speed = 0;
  41.                 RunningTime rt = null;
  42.                 if (double.TryParse(pscm.speed, out speed))
  43.                     rt = RunningCalc.SpeedToPace(speed);
  44.                 pscm.paceMinutes = rt.Minutes.ToString();
  45.                 pscm.paceSeconds = rt.Seconds.ToString();
  46.             }
  47.             return this.View(pscm);
  48.  
  49.         }
  50.     }
  51. }
For the first the Action is “Index”. Being an asp, asp.net folk it takes a moment to understand how the data posted by the form is handled. Normally there will be an Index.asp with the form that post to a Calculate.asp. With MVC the action is the same, differentiated by the “HttpPost” attribute. The first time (Index.asp) the browser uses a GET and calls “public ActionResult Index()”. When you submit the form (Calculate.asp) the browser uses a POST activating the “public ActionResult Index(PaceSpeedCalculatorModel pscm)” method. MVC runtime fills automagically the PaceSpeedCalculatorModel parameter, matching POST payload names with class ones.

Creating the view based on the annotated model class is really easy and everything went smooth.

Index.cshtml
  1. @model FitCalc.Models.PaceSpeedCalculatorModel
  2. <script src="../../Scripts/jquery-1.4.4-vsdoc.js" type="text/javascript"></script>
  3. <script src="../../Scripts/json2.js" type="text/javascript"></script>
  4. @{
  5.     ViewBag.Title = "Index";
  6.     Layout = "~/Views/Shared/_Layout.cshtml";
  7. }
  8. <script type="text/javascript">
  9.    
  10.     $(InitPage);
  11.     function InitPage()
  12.     {
  13.     
  14.  
  15.           var paceSpeed = { paceMinutes: $("#paceMinutes").val(),
  16.                         paceSeconds: $("#paceSeconds").val(),
  17.                         speed: $("#speed").val()
  18.                     };
  19.  
  20.         $(function ()
  21.         {   setTimeout(checkSearchChanged, 0.1);
  22.         });
  23.  
  24.         function checkSearchChanged()
  25.         {   var currentPaceSpeed = { paceMinutes: $("#paceMinutes").val(),
  26.                 paceSeconds: $("#paceSeconds").val(),
  27.                 speed: $("#speed").val()
  28.             };
  29.             if (true &&
  30.                 ((currentPaceSpeed.paceMinutes)
  31.                 && (paceSpeed.paceMinutes)
  32.                 && currentPaceSpeed.paceMinutes != paceSpeed.paceMinutes
  33.                  && currentPaceSpeed.paceMinutes != ''
  34.                   && currentPaceSpeed.paceMinutes > 0
  35.                   )
  36.                 || ((currentPaceSpeed.paceSeconds) && currentPaceSpeed.paceSeconds != paceSpeed.paceSeconds && currentPaceSpeed.paceSeconds != '' && currentPaceSpeed.paceSeconds > -1)
  37.                 || ((currentPaceSpeed.speed) &&  currentPaceSpeed.speed != paceSpeed.speed && currentPaceSpeed.speed != '' && currentPaceSpeed.speed.replace(',','.') > 0)
  38.  
  39.                 )
  40.             {
  41.                 //alert("ok inizioe");
  42.                 if ((currentPaceSpeed.speed) && (paceSpeed.speed != null) && currentPaceSpeed.speed != paceSpeed.speed)
  43.                 {
  44.                     // if speed changed
  45.                     paceSpeed =
  46.                     { paceMinutes: null,
  47.                         paceSeconds: null,
  48.                         speed: $("#speed").val()
  49.                     };
  50.                 }
  51.                 else
  52.                 {
  53.                     paceSpeed =
  54.                     { paceMinutes: $("#paceMinutes").val(),
  55.                         paceSeconds: $("#paceSeconds").val(),
  56.                         speed: null
  57.                     }
  58.                 }
  59.  
  60.                 var jsonString = JSON.stringify(paceSpeed);
  61.                 $.ajax(
  62.                 {
  63.                     url: '/Calculator/PaceToSpeed',
  64.                     type: "POST",
  65.                     data: jsonString,
  66.                     dataType: "json",
  67.                     contentType: "application/json; charset=utf-8",
  68.                     dataFilter: function(data, dataType) {
  69.                     if (data.indexOf("<!-") > 0)
  70.                         {
  71.                             //alert(data.indexOf("<!-"));
  72.                             //alert(data.substring(0, data.indexOf("<!-")));
  73.                             return data.substring(0, data.indexOf("<!-"));
  74.                         }
  75.                         else
  76.                             return data;
  77.                     },
  78.                     success: function (objResult)
  79.                     {
  80.                         var paceSpeedResult = objResult;
  81.                         
  82.                         $("#speed").val(objResult.speed);
  83.                         $("#paceSeconds").val(objResult.paceSeconds);
  84.                         $("#paceMinutes").val(objResult.paceMinutes);
  85.                         paceSpeed = { paceMinutes: $("#paceMinutes").val(),
  86.                             paceSeconds: $("#paceSeconds").val(),
  87.                             speed: $("#speed").val()
  88.                         };
  89.                         setTimeout(checkSearchChanged, 0.1);
  90.                     },
  91.                     error: function (objResult)
  92.                     {
  93.                         alert("errore" + objResult);
  94.                         setTimeout(checkSearchChanged, 0.1);
  95.                     }
  96.                 });   
  97.             }
  98.             else
  99.             {
  100.                 setTimeout(checkSearchChanged, 0.1);
  101.             }
  102.         }
  103.  
  104.     }
  105. </script>
  106. <h2>
  107.     Index</h2>
  108. @using (Html.BeginForm())
  109. {
  110.     @Html.ValidationSummary(true)
  111.     <fieldset>
  112.         <legend>PaceSpeedCalculatorModel</legend>
  113.         <div class="editor-label">
  114.             @Html.LabelFor(model => model.paceMinutes)
  115.         </div>
  116.         <div class="editor-field">
  117.             @Html.EditorFor(model => model.paceMinutes)
  118.             @Html.ValidationMessageFor(model => model.paceMinutes)
  119.         </div>
  120.         <div class="editor-label">
  121.             @Html.LabelFor(model => model.paceSeconds)
  122.         </div>
  123.         <div class="editor-field">
  124.             @Html.EditorFor(model => model.paceSeconds)
  125.             @Html.ValidationMessageFor(model => model.paceSeconds)
  126.         </div>
  127.         <div class="editor-label">
  128.             @Html.LabelFor(model => model.speed)
  129.         </div>
  130.         <div class="editor-field">
  131.             @Html.EditorFor(model => model.speed)
  132.             @Html.ValidationMessageFor(model => model.speed)
  133.         </div>
  134.         <p>
  135.             <input type="submit" value="Save" />
  136.         </p>
  137.     </fieldset>
  138. }
  139. <div>
  140.     @Html.ActionLink("Back to List", "Index")
  141. </div>

Doing calculation as you type it’s a little bit tricky. I found Scott Allen “jQuery for the .NET Developer” video very interesting. Then followed Scott post when he talks about using jquery and JSON to post data to the server. In the sample it wasn’t clear where the JSON.stringify method comes from. To make it work I downloaded Json2.js from www.json.org (well, from github because Json.org wasn’t available). Here the solution found in comments.

With keypress event you didn’t catch all modification to the textbox (say mouse paste), so I switched to this solution.

With the previous view, I got what I was looking for. I send and receive JSON data to the PaceToSpeed Action. In the view I send the model JSON encoded with raw HTML.

@Html.Raw(@Json.Encode(@Model))

My hoster append some ad to the HTML stream, so I have to filter it when received by the ajax call using the dataFilter method.

Upgrading to asp.net 4.0 & MVC 3

Time to get my hand dirty with some new web app.

I setup a new account on somee, that hosts asp.net 4 apps for free.

Uploaded a fresh new “hello world” app created by Visual studio 2010.

Wanting to make something with asp.net and MVC I configured the web app to also support MVC 3 following instruction foun here. Nothing fancy, only few steps to get things done.

To ease this process I created another web app configured as MVC 3 and compared the two.

So I added references to appropriate assembly (some assembly not found directly, but for System.Web.Mvc.dll under C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 3\Assemblies\System.Web.Mvc.dll).

Then modified web.config adding relevant tags.

Same thing for global.asax.cs, registering filters into the app.

Also modify the .csproj file and especially add proper ProjectTypeGuids i.e.

<ProjectTypeGuids>{E53F8FEA-EAE0-44A6-8774-FFD645390401};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

To start quickly I followed the pluralsight training video as suggested by Scott Guthrie.

In the /Controllers dir there’s (guess what..) the controllers. Named with the name we choose terminating with a “Controller” string.

Views are stored under the /Views dir, grouped in a dir named as the controller.

So if we want to create a “Hello” controller the directory structure will be:

/Controllers/HelloControlles

/Views/Hello/Index.cshtml

HelloController.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Mvc;
  6.  
  7. namespace FitCalc.Controllers
  8. {
  9.     public class HelloController : Controller
  10.     {
  11.         //
  12.         // GET: /Hello/
  13.  
  14.         public ActionResult Index()
  15.         {
  16.             ViewBag.Message = "Welcome to ASP.NET MVC!";
  17.             return View();
  18.         }
  19.  
  20.     }
  21. }

.cshtml is the razor file implementing the view.

The URL for this view is /Hello/Index.
«February»
SunMonTueWedThuFriSat
303112345
6789101112
13141516171819
20212223242526
272812345
6789101112