views:

1640

answers:

5

I am using jQuery to call web service (*.asmx) methods. The web service uses FormsAuthentication to determine whether the calling user is authenticated. I am unable to return a redirect status code from the web method, e.g.

[WebMethod(EnableSession=true)]
public Dictionary<string, object> GetArchivedFiles(int pageSize, int page)
{
    if(HttpContext.Current.User.Identity.IsAuthenticated && Session["UserId"] != null)
        // Do some computing and return a Dictionary.       

    // Method will fall through here if the user is not authenticated.
    HttpContext.Current.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
    return null;
}

The redirect does not work, I always get 500 Internal Server Error when doing this. I tried different codes. What would be the recommended way to go here? I need to read the redirect information from JavaScript and load the new page (or maybe display a login control AJAX way).

I actually get a JSON object back, which looks like this:

{"Message":"Authentication failed.","StackTrace":null,"ExceptionType":"System.InvalidOperationException"
}

I tried running the debugger, but it doesn't show that any methods are entered. As you can see the StackTrace is null...

When invoked in Fiddler as a standard POST request (not XMLHttpRequest) it returns an exception actually:

HTTP/1.1 500 Internal Server Error
Server: ASP.NET Development Server/9.0.0.0
Date: Wed, 04 Mar 2009 14:46:02 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Length: 1739
Connection: Close

System.NotSupportedException: The type System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] is not supported because it implements IDictionary.
   at System.Xml.Serialization.TypeScope.GetDefaultIndexer(Type type, String memberInfo)
   at System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, MemberInfo memberInfo, Boolean directReference)
   at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError)
   at System.Xml.Serialization.ModelScope.GetTypeModel(Type type, Boolean directReference)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root)
   at System.Web.Services.Protocols.XmlReturn.GetInitializers(LogicalMethodInfo[] methodInfos)
   at System.Web.Services.Protocols.XmlReturnWriter.GetInitializers(LogicalMethodInfo[] methodInfos)
   at System.Web.Services.Protocols.MimeFormatter.GetInitializers(Type type, LogicalMethodInfo[] methodInfos)
   at System.Web.Services.Protocols.HttpServerType..ctor(Type type)
   at System.Web.Services.Protocols.HttpServerProtocol.Initialize()
   at System.Web.Services.Protocols.ServerProtocol.SetContext(Type type, HttpContext context, HttpRequest request, HttpResponse response)
   at System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing)
+6  A: 

You should not redirect from the Web service. It should return a value to indicate whether or not you should redirect (This could be a separate Authenticate method that returns a string, if it's null, it means it's authenticated, if it isn't it'll contain the redirection URL) Then in javascript, you could check the return value and redirect appropriately by setting the window.location property.

By the way, access to the Web service should not require authentication.

Mehrdad Afshari
I have forms authentication in place, but also tried allowing anonymous access to the web service (via <location/>), it still throws that error.What do you suggest? Adding a custom value in the JSON result, such as redirect=true? Would that be fine? That was my another idea.
Pawel Krakowiak
Which line causes the error? Have you tried using a debugger?
Mehrdad Afshari
I think it's framework stuff, see my explanation in the question, I added some additional info.
Pawel Krakowiak
What happens if you are already authenticated and then you call that web service? It'll cause the same?
Mehrdad Afshari
I get 200 OK + data. I just ran Fiddler, it's interesting how it work differently when called in a non-AJAX way.
Pawel Krakowiak
That's normal. Since AJAX calls are serialized to JSON which supports dictionaries and SOAP uses XML which doesn't support IDictionary by default. Are you sure the <location> tag is set correctly to enable anonymous access to the web service?
Mehrdad Afshari
<location path="Service.asmx"> <system.web> <authorization> <allow users="?"/> </authorization> </system.web></location>
Pawel Krakowiak
In another method I also return null if the user is not authenticated, but there's no status code part. It returns 200 OK with an empty array.
Pawel Krakowiak
Remove the status code line. Alter <allow users="?" /> to <allow users="*" /> Not supposed to do anything but worth trying.
Mehrdad Afshari
It works fine after removing the status code line. I guess I will go a similar route to what Dan Roberts suggested, because I already thought of that, but was hoping that HTTP redirects would work normally.
Pawel Krakowiak
If you want to do so, I suggest raising your own exception type and checking it on the client side.
Mehrdad Afshari
Throwing any exception gets you a 500. I end up with a similar error as when trying to redirect. Anyway, thanks for your help. That's a lengthy discussion...
Pawel Krakowiak
Oh, I see. You're welcome. I learned some stuff in the process too. Thanks for sharing the facts.
Mehrdad Afshari
+1 went to you, of course. :) By the way, I also thought of having an Authenticate method in the web service, but essentially it would give me the same info as a JSON object with some status codes, but would require an additional call prior to fetching the file list, so I didn't like that.
Pawel Krakowiak
Isn't returning a null sufficient? Btw, thanx for the upvote but I was on my daily cap long ago :))
Mehrdad Afshari
Yes, it is... I must have broken something (probably because of all the solutions I tried) and thought it was not returning null. It's not a perfect solution, but it works.
Pawel Krakowiak
Thank you, Mehrdad, the comments discussion with you helped me actually. I am not accepting your answer just because I did not solve it exactly as you proposed, but I sure used your suggestions. +1 for that.
Pawel Krakowiak
A: 

Throw an exception in the webservice. Catch this in the caller and redirect as necessary.

ck
+3  A: 

I don't have experience specifically with asp.net but in general I like to use an ajaxResponse object that has properties such as "success", "content", "errortype". A login required response in this case would have a success of "false" and errortype "login", or whatever you choose. The javascript code then decides how to handle the returned object based on these properties, showing the user a login form or redirecting to a new page.

Dan Roberts
A: 

What I did was to get rid of the status code at all and just return null. A method returns an empty list if there are no files, with a total page count of 0. But if the user is not authenticated, it returns null, in such case I use window.location to redirect the browser to the login page.

Pawel Krakowiak
A: 

In fact, if your post to the webservices (ajax or not) contains a "Content-Type" header with value "application/json; charset=utf-8", then the ASMX stack will send you back a json formated string with an exception of the supposed redirect that usually you expect including status code 401 (unauthorized), but, if you don't send any content type header, you well get a browser redirection to the login page as response with status code OK and using ajax, you will get the login page html as response.

Hope that this help on clarifying.

Regards

Nestor