JavaScript in a .NET MVC App

Harnessing the power of JavaScript in a web application can improve speed, flexibility, and overall user experience. Here are some basics regarding the use of JavaScript in a .NET MVC app.

When retrieving server data, there are a few choices of format. Most commonly, the server will return data formatted as JSON or data formatted as HTML. If JSON is returned, the client will parse the response and decide for itself what to do with the information. If HTML is returned, the client will most likely place the html somewhere on the page. Both options have advantages and disadvantages.

As a common theme that will be seen in many of my future articles, jQuery isn’t the only library for doing ajax requests. Any of the following are perfectly good (and lighter-weight) alternatives.

ajax | request | reqwest

Retrieving HTML Data

scripts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
reqwest({
url: '/Client/Index',
method: 'GET',
type: 'html',
success: function (html) {
console.log(html);
},
error: function (err) {
console.log(err);
}
});

/*
prints:
<ul>
<li>Bob Johnson</li>
<li>Sally Sue</li>
<li>Chris Howard</li>
</ul>
*/
ClientController.cs
1
2
3
4
5
6
7
8
9
public partial class ClientController : Controller
{
private BlogContext db = new BlogContext();

public ActionResult Index()
{
return PartialView("List", db.Clients());
}
}
List.cshtml
1
2
3
4
5
6
7
8
@model IEnumerable<Blog.Client>

<ul>
@foreach(Blog.Client c in Model)
{
<li>@c.FullName</li>
}
</ul>

Retrieving JSON Data

scripts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
reqwest({ // reqwest library
url: '/Client/Index',
method: 'GET',
type: 'json', // means we will get a json object passed to our success function
success: function (json) {
console.log(json);
},
error: function (err) {
console.log(err);
}
});

/*
prints:
[
{
"FirstName" : "Bob",
"LastName" : "Johnson",
"Location" : null,
"BirthDate" : "\/Date(344502000000)\/"
},
...
]
*/
ClientController.cs
1
2
3
4
5
6
7
8
9
public class ClientController : Controller
{
private BlogContext db = new BlogContext();

public JsonResult Index()
{
return Json(db.Clients(), JsonRequestBehavior.AllowGet);
}
}

Gotchas

  1. “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource…”
    Browsers prevent javascript from making requests to other domains. Fortunately, modern browsers – clear back to IE8 – support CORS. Refer to the CORS section below for how to implement it.

Sending Data to the Server

scripts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var data = "FirstName={{FirstName}}&LastName={{LastName}}&Location={{Location}}&BirthDate={{BirthDate}}";
data = data.replace(/{{FirstName}}/g, document.getElementById("FirstName").value);
data = data.replace(/{{LastName}}/g, document.getElementById("LastName").value);
data = data.replace(/{{Location}}/g, document.getElementById("Location").value);
data = data.replace(/{{BirthDate}}/g, document.getElementById("BirthDate").value);
// note that jQuery makes this more succinct: 'data = $("#client-form").serialize();'

reqwest({
url: document.forms["client-form"].action,
method: document.forms["client-form"].method,
type: 'html',
data: data,
success: function () {
console.log("success");
},
error: function () {
console.log("error");
}
});
ClientController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public partial class ClientController : Controller
{
private BlogContext db = new BlogContext();

public ActionResult Create()
{
return View();
}

[HttpPost()]
public ActionResult Create(Client model)
{
db.AddClient(model);
return RedirectToAction("Index");
}
}

Please note that the above action method accepts a complex object as a parameter. This works because of the .NET framework, but there is a gotcha that is explained below.

Create.cshtml
1
2
3
4
5
6
<form id="client-form" action="@Url.Action("Create")" method="post">
<label for="FirstName">First Name:</label> <input type="text" name="FirstName" id="FirstName" />
<label for="LastName">Last Name:</label> <input type="text" name="LastName" id="LastName" />
<label for="Location">Location:</label> <input type="text" name="Location" id="Location" />
<label for="BirthDate">Date of Birth:</label> <input type="datetime" name="BirthDate" id="BirthDate" />
</form>

Submitting json data:

scripts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var data = {"FirstName": ..., "LastName": ...};

reqwest({
url: document.forms["client-form"].action,
method: document.forms["client-form"].method,
type: 'json',
data: data,
success: function () {
console.log("success");
},
error: function () {
console.log("error");
}
});

The server does not need to change to accept json data.

Gotchas

  1. The action method parameter isn’t being populated even though the data is being sent.
    By far the sneakiest cause of this issue is that the .NET framework only populates properties of an object. Thus, even if the object type has the correct members, the members have to be declared as properties, not just public fields.

  2. The action method parameter isn’t being populated, but I’m not sure if the data is being sent correctly or not.
    Use the browser’s developer tools to inspect the request being sent. It should show exactly what data was sent to the server. Make sure the data was actually sent and that the variables are named correctly.

Cross-Origin Resource Sharing (CORS)

It is common to see the “cross-origin request blocked…” error when playing with JavaScript. Unfortunately, the solution is less common; most resort to server proxies or JSONP. There is, however, another option that is both elegant and unobtrusive, and the best part is that it works in most browsers clear back to IE8! Here is how it works:

  1. A request is made with JavaScript.
  2. Seeing that it is a cross-origin request, the browser first sends an HTTP OPTIONS request (no different than POST, PUT, etc.) to the exact same url.
  3. If the server responds to the OPTIONS request with the correct headers, the browser allows the request to continue.

Nifty, right? In order to implement it, the server must respond with the “Access-Control-Allow-Origin” header set to the origin of the requester. In .NET, this can be done with the following code:

1
2
String origin = HttpContext.Request.UrlReferrer.Scheme + "://" + HttpContext.Request.UrlReferrer.Authority;
HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", origin);

Note that some say to add “Access-Control-Allow-Origin: *”, but that doesn’t work it all situations. Lists also do not work, as in “Access-Control-Allow-Origin: domain.com, another.com.” The safest path is the one described above. In the case of cross-origin file uploads, the following header may also be required:

1
Access-Control-Allow-Headers: Content-Type, Content-Range, Content-Disposition, Content-Description

If cookies are being sent along in the request, set the Access Control Allow Credentials to true.

1
Access-Control-Allow-Credentials: true

Gotchas

  1. It still says “cross-origin request blocked…”, and the headers are not being set correctly.
    Make sure the server isn’t throwing an exception. The headers may not be set correctly in that case, causing the request to fail. Also, make sure the server responds correctly to HTTP OPTIONS requests. Try making a request with HTTPRequester or other tool.

  2. It still says “cross-origin request blocked…”, and the headers are being set correctly.
    Make sure the access-control-allow-origin header is exactly the same as the origin header property in the request, e.g. no trailing slashes. Also keep in mind that the browser gives this “cross-origin request blocked…” error very liberally even in situations where the request wasn’t blocked, the server just returned an error code.

Comments