// TODO: choose a tagline

ASP.NET MVC returning 302 (Found) HTTP status code on unauthorized Ajax calls instead of 401(Unauthorized) like classic ASP.NET

July 2nd, 2009 Catalin

Classic ASP.NET

On classic ASP.NET when calling a [WebMethod] with Ajax in an unauthorized context (most likely in the case where the session expired) the response http status code is 401. You can handle this in the “error” handler (provided by most Ajax frameworks) and redirect to login page. For example I use this code with jQuery:

WebMethod;

[WebMethod]
public static MyModel GetStuff()
{
    return new MyModel();
}

Javascript:

$.ajax({
    type: "POST",
    url: "Default.aspx/GetStuff",
    data: "{}",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(msg) {
        // do stuff
    },
    error: function(xhr, status, ex) {
        if (xhr.status == 401) // unauthorized
        {
            window.location = "Login.aspx?ReturnUrl=" + window.location.pathname;
        }
    }
});

ASP.NET MVC

To do the same in ASP.NET MVC I have this method in my controller:

[Authorize]
public ActionResult GetStuff()
{
    return Json(new MyModel());
}

The ASP.NET MVC infrastructure doesn’t return 401 http status code on failed authorization but 302 http status code (actually the 401 status code is returned initially but later, in the same request, is intercepted by the infrastructure and replaced by 302 status code). XMLHttpRequest object handles this internally automatically following the redirect (no event is fired client side). The Ajax call will end in “success” but the message won’t be the expected JSON but the html of the login page.

The best (well, it’s a hack, if you a better way please tell me) way I found is to replace 302 status code by 401 status code on request end. I added the following code to Global.asax :

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302 &&
        Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
    {
        Context.Response.Clear();
        Context.Response.StatusCode = 401;
    }
}

And the client side code (using Ext JS this time) is:

Ext.Ajax.on('requestexception', function(conn, response, options) {
    if (response.status == 401) {
        window.location = '<%= Html.ActionUrl("Account", "LogOn") %>?ReturnUrl=' +
            window.location.pathname;
    }
});

Calling SharePoint web services using jQuery

October 2nd, 2008 Catalin

I needed to update a SharePoint list item directly on client side in a classic SharePoint list form. I found this blog who explains very well how to do it by using Prototype but I was already using jQuery and didn’t want to add another javascript framework to my solution. Sure this is very easily portable to jQuery but I wanted a cleaner solution.

I found this: jQuery SOAP Client, a jQuery plug-in (it’s really an add-on because it doesn’t use jQuery extension mechanism) who was looking very promising. So I started to code…

I was going to call the UpdateListItems of the list web service (http://server/_vti_bin/lists.asmx). This method requires two arguments: listName and updates. Updates are the CAML commands describing the changes to the list and looks like this:


<Batch OnError="Continue">
    <Method ID="1" Cmd="Update">
       <Field Name="ID">1</Field>
       <Field Name="Title">updated title</Field>
    </Method>
</Batch>

I think that’s pretty readable: we want to change the Title of the element with the ID=1 to “updated title”.

Next: the javascript code using jQuery SOAP Client:


var wsMethod = "UpdateListItems";
var soapNs = "http://schemas.microsoft.com/sharepoint/soap/";

var soapBody = new SOAPObject(wsMethod);
soapBody.ns = soapNs;
soapBody.appendChild(new SOAPObject("listName")).val("list1");
soapBody.appendChild(new SOAPObject("updates")).val(batch);

var sr = new SOAPRequest(soapNs + wsMethod, soapBody);

SOAPClient.Proxy = "http://server/_vti_bin/lists.asmx";

SOAPClient.SendRequest(sr, processResponse);

For this to work it needs to be executed from an already authenticated SharePoint web page. Internet Explorer automatically passes the credentials with the XHR call. You could pass the credentials in the jQuery call but I wouldn’t do that if I where you.

Well, this didn’t work… I was getting some generic SharePoint error page, not very useful for debugging so I fired up Fiddler2.
I found out that the response was a HTTP error code 415 - Unsupported Media Type. I noticed some differences between the SOAP request created by jQuery SOAP Client and the SOAP code expected by the service and modified the plug-in accordingly. But I was still getting the same error.

And then I saw it: the Content-Type sent was application/x-www-form-urlencoded, text/xml; charset= "utf-8"; Thing is when you make an AJAX call with jQuery it automatically ads application/x-www-form-urlencoded content-type if no content-type is specified. But jQuery SOAP Client added the content-type not by using jQuery but directly into the XmlHttpRequest object:


$.ajax({
	 type: "POST",
	 url: SOAPClient.Proxy,
	 dataType: "xml",
	 processData: false,
	 data: content,
	 complete: getResponse,
	 beforeSend: function(req) {
		req.setRequestHeader("Method", "POST");
		req.setRequestHeader("Content-Length", SOAPClient.ContentLength);
		req.setRequestHeader("Content-Type", SOAPClient.ContentType + "; charset=\"" + SOAPClient.CharSet + "\"");
		req.setRequestHeader("SOAPServer", SOAPClient.SOAPServer);
		req.setRequestHeader("SOAPAction", soapReq.Action);
	 }
});

Fixing that was simple and… it worked!!!


$.ajax({
	 type: "POST",
	 url: SOAPClient.Proxy,
	 dataType: "xml",
	 processData: false,
	 data: content,
	 contentType : SOAPClient.ContentType + "; charset=\"" + SOAPClient.CharSet + "\"",
	 complete: getResponse,
	 beforeSend: function(req) {
		req.setRequestHeader("Method", "POST");
		req.setRequestHeader("Content-Length", SOAPClient.ContentLength);
		req.setRequestHeader("SOAPServer", SOAPClient.SOAPServer);
		req.setRequestHeader("SOAPAction", soapReq.Action);
	 }
});

You can download the source code here