1
votes

I am seeing some very odd behavior when trying to do a redirect with .NET MVC. I read somewhere to not do the redirect in a subroutine so I changed that part of the code, but I am still having the problem. This only happens in production (particularly with bots), and I can't reproduce it in the dev environment. I added in some logging to try to help but still don't understand what is happening (it was a poor man's solution because I don't have access to put debug symbols on the production server). The call stack doesn't even make sense because it says there is a recursive call that doesn't exist. Thanks in advance for any help!

Error details:

Step Number: 16, LangID: 1033, Language: en, Culture: us -

System.NullReferenceException: Object reference not set to an instance of an object. at [REMOVED]_Web.Controllers.MVC.HomeController.setLanguage(String language, String _culture)

User Name: Anonymous

URL: [REMOVED]'A=0
User Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)

Exception Details:

System.Exception: Step Number: 16, LangID: 1033, Language: en, Culture: us - System.NullReferenceException: Object reference not set to an instance of an object.
    at [REMOVED]_Web.Controllers.MVC.HomeController.setLanguage(String language, String _culture)
    at [REMOVED]_Web.Controllers.MVC.HomeController.setLanguage(String language, String _culture)
    at [REMOVED]_Web.Controllers.MVC.HomeController.Init(String language, String culture)
    at [REMOVED]_Web.Controllers.MVC.HomeController.ProdCatSearch(String language, String culture)
    at lambda_method(Closure , ControllerBase , Object[] )
    at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters)
    at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult2.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.b__3d()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.b__3f()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass33.b__32(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<>c__DisplayClass2b.b__1c()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.b__1e(IAsyncResult asyncResult)
    at System.Web.Mvc.Controller.b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.MvcHandler.b__5(IAsyncResult asyncResult, ProcessRequestState innerState)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
    at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Source code:

Private Sub setLanguage(language As String, _culture As String)
    Dim stepNumber As Integer = 0
    Try
        Dim langCookie As HttpCookie
        Dim hasException As Boolean = False

        Try
            If (String.IsNullOrEmpty(language) OrElse String.IsNullOrEmpty(_culture)) Then
                language = "en"
                _culture = "us"
            End If

            stepNumber = 1
            Dim languages = clsAppSettings.Instance.GetAppSettingTable(Of String)("Language")

            For Each lang In languages
                supportedLangs.Add(New LanguageInfo(lang))
            Next
            supportedLangs.Sort(Function(x, y) x.SortOrder.CompareTo(y.SortOrder))

            langCookie = HttpContext.Request.Cookies.Get("langID")
            stepNumber = 2
            If langCookie IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(langCookie.Value) Then
                stepNumber = 3
                LangId = langCookie.Value
                CultureInfo.CreateSpecificCulture(language + "-" + _culture)
                stepNumber = 4
            Else
                stepNumber = 5
                Dim culture As CultureInfo
                If language.ToLower = "en" And _culture.ToLower = "us" Then
                    stepNumber = 6
                    Dim langIdFromBrowser As String = String.Empty
                    Dim browserLangs = Request.UserLanguages
                    For Each browserLang In browserLangs
                        culture = CultureInfo.CreateSpecificCulture(browserLang.ToLowerInvariant().Trim())
                        langIdFromBrowser = culture.LCID.ToString
                        If Not String.IsNullOrWhiteSpace(langIdFromBrowser) Then
                            If (From items In supportedLangs Where items.Id = CInt(langIdFromBrowser)).Any Then
                                LangId = langIdFromBrowser
                                Exit For
                            End If
                        End If
                    Next
                    stepNumber = 7
                    If String.IsNullOrEmpty(LangId) Then
                        LangId = "1033"
                    End If
                Else
                    stepNumber = 8
                    culture = CultureInfo.CreateSpecificCulture(language + "-" + _culture)
                    LangId = culture.LCID.ToString
                    If Not String.IsNullOrWhiteSpace(LangId) Then
                        If Not (From items In supportedLangs Where items.Id = CInt(LangId)).Any Then
                            LangId = "1033"
                        End If
                    End If
                    stepNumber = 9
                End If

                ' Correct for the fact that SharePoint uses Spanish Traditional vs Spanish Modern Sort
                If LangId = "1034" Then
                    LangId = "3082"
                End If
                stepNumber = 10

            End If

        Catch ex As CultureNotFoundException

            stepNumber = 11
            If String.IsNullOrEmpty(LangId) Then
                LangId = "1033"
            End If
            hasException = True

        Finally

            ' Final check to ensure NO empty value ever makes it past this point.
            If String.IsNullOrWhiteSpace(LangId) Then
                LangId = "1033"
            End If

            langCookie = New HttpCookie("langID", LangId)
            langCookie.Expires = Date.Now().AddYears(1)
            Web.HttpContext.Current.Response.Cookies.Add(langCookie)
            stepNumber = 12
            If hasException OrElse CultureInfo.CreateSpecificCulture(language + "-" + _culture).LCID.ToString() <> LangId Then
                stepNumber = 13
                Dim newCulture As New CultureInfo(CInt(LangId))
                stepNumber = 14
                Dim cultureValues As String() = newCulture.Name.Split("-"c)
                Dim rUrl As String = Request.Url().OriginalString()
                Dim replacement As String = (cultureValues(0) + "-" + cultureValues(1)).ToLower()
                rUrl = rUrl.Replace((language + "-" + _culture), replacement)
                'Response.Redirect(rUrl, False)
                'HttpContext.ApplicationInstance.CompleteRequest()
            End If

            stepNumber = 15
            ViewData("SelectedLang") = LangId
            ViewData("SupportedLangs") = supportedLangs
            transObj = Translations.GetTranslations(LangId)
            ViewData("Translations") = New JavaScriptSerializer().Serialize(transObj)

            stepNumber = 16
        End Try

    Catch ex As Exception
        Throw New Exception(String.Format("Step Number: {0}, LangID: {1}, Language: {2}, Culture: {3} - {4}", stepNumber, LangId, language, _culture, ex.ToString()))
    End Try

End Sub
1
I like that you've put the steps in as debugging points but there's one issue I see. The "finally" block will run after the exception catch. That means you won't get the real step number you're looking for to track down at what point the exception happened (because you overwrite it in the finally block). If you're using a variable to track the execution path you could use a string or StringBuilder so you see the actual execution through to the end and then report that in the exception (you might see 1, 2, 3, 4, 11, 12, 13) so you can see how it actually flowed.b.pell
@b.pell - Thanks so much for that observation! I didn't have a good understanding of how the finally block actually works. I didn't write the core part of the code originally. I am just porting it from SharePoint to MVC. I took your suggestion, and I think when it gets installed that will really help isolate and solve the problem. Thanks again for your input!Justin Pavatte
Glad to help. ;-) The Finally of a try/catch is supposed to always execute exception or not, it should run with or without an exception (it gives you a final place that is guaranteed to run after your block of code, you may cleanup resources there or report to a log, etc.). The only caveat is if you create an unhandled exception in the finally (it will stop execution there unless handled).b.pell
@b.pell If the exception is caught in the outer catch block from earlier in the script, will it re-enter the inner finally block before leaving the function? I assume since the inner exception is only catching the CultureNotFoundException that the outer catch would have to be the one catching the NullReferenceException? If so, does that catch execute and then step back into the finally block? I'm not familiar enough with the execution path regarding .NET exceptions.War10ck

1 Answers

2
votes

To resolve this post so no one potentially wastes time on reading that huge method and to potentially help someone, I'm going to write up the solution.

First of all, @b.pell started off on the right foot by suggesting to fix the logging. I changed stepNumber to be a list of ints then just did String.Join(" ", steps) to print it to the log.

Secondly, this was a poor use of Finally. Finally should really just be used for cleanup. In this case, the code in the Finally block shouldn't be running when an exception is thrown.

Another obstacle is this method is huge, making things difficult to isolate, especially when you are running with no debug symbols and just have a stack trace.

Ultimately, this line of code was creating the null causing the NullReferenceException. So the fix is just a simple null check.

culture = CultureInfo.CreateSpecificCulture(language + "-" + _culture)

This could have been tracked down very quickly if we were able to drop the debug symbols on the production server or done some of the other things I mentioned.