Serialization exception for UnauthenticatedPrincipal in IIS 7 (not Cassini)

Serialization exception for UnauthenticatedPrincipal in IIS 7 (not Cassini)

Old forum URL: forums.lhotka.net/forums/t/11501.aspx


rcollette posted on Monday, July 23, 2012

I am running my .NET 4.0 application in Vista 32bit IIS 7 version 7.0.6000.16386.  I am using CSLA.NET 3.8.2

I get a serialization exception, either after a number of cache accesses or after some period of time has expired (not sure which yet).  I was already familiar with the Cassini issues but this is not related to Cassini.  The code that is executing is just going after some cached data.  The code fails on the return statement.  Once it fails, it continues to fail until IIS is reset.  I'm lost on this one.

Sorry the error and stack trace below are being cuttoff in the browser.  The error is:

Exception: System.Runtime.Serialization.SerializationException: Type is not resolved for member 'Csla.Security.UnauthenticatedPrincipal,Csla, Version=3.8.2.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30'.

 

        private static object _lock = new object();
        public static IEnumerable<CountryState> States
        {
            get
            {
                ObjectCache cache = MemoryCache.Default;
                if (!cache.Contains(CacheKey))
                {
                    lock (_lock)
                    {
                        if (!cache.Contains(CacheKey))
                        {
                            CountryStateDao dao = new CountryStateDao();
                            using (IDataReader reader = dao.FetchStatesReader())
                            {
 
                                Dictionary<int,CountryState> states =
                                    reader.AsEnumerable().
                                    Select((record) => new CountryState(record)).
                                                           ToDictionary((state) => state.Id);
                                cache[CacheKey] = states;
                            }
                        }
                    }
                }
                return (IEnumerable<CountryState>)(((Dictionary<int,CountryState>)cache[CacheKey]).Values);
            }
        }

A Webhost unhandled exception occurred.
 Sender Information: System.AppDomain/61002492
 Exception: System.Runtime.Serialization.SerializationException: Type is not resolved for member 'Csla.Security.UnauthenticatedPrincipal,Csla, Version=3.8.2.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30'.
   at System.Web.Hosting.ApplicationManager.GetUpdatedTotalCacheSize(Int64 sizeUpdate)
   at System.Web.Hosting.ObjectCacheHost.System.Runtime.Caching.Hosting.IMemoryCacheManager.UpdateCacheSize(Int64 size, MemoryCache memoryCache)
   at System.Runtime.Caching.CacheMemoryMonitor.GetCurrentPressure()
   at System.Runtime.Caching.MemoryMonitor.Update()
   at System.Runtime.Caching.MemoryCacheStatistics.CacheManagerThread(Int32 minPercent)
   at System.Runtime.Caching.MemoryCacheStatistics.CacheManagerTimerCallback(Object state)
   at System.Threading._TimerCallback.TimerCallback_Context(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading._TimerCallback.PerformTimerCallback(Object state)
 Process Name: w3wp
 Process ID: 6948

Your insight is appreciated,
Rich

 

rcollette replied on Monday, July 30, 2012

No takers from Magenic?

RockfordLhotka replied on Monday, July 30, 2012

I have no idea. It is from one of the Microsoft serializers (BinaryFormatter?).

The best way to narrow this down (in my experience) is to use the object cloner method to clone the ApplicationContext.User property and see if you can make the serialization fail outside the data portal. Primarily because it is easier to debug a simple clone call.

Just use Csla.ObjectCloner.Clone() to clone the object.

Run that in an ASP.NET web page or MVC controller and see what happens.

rcollette replied on Tuesday, July 31, 2012

Thank you for responding Rocky.    I tried the code you suggested, even going so far as to do so in multiple threads like

        protected void Page_Load(object sender, EventArgs e)
        {
             Parallel.For(0, 1000, (p) => Csla.Core.ObjectCloner.Clone(Csla.ApplicationContext.User));
        }

I then executed my caching code above in the loop And I was unable to get the error.  I also tried waiting for the asp.net application to end and no luck.

The only thing similar to this issue that I have been able to find was this MS Connect article. http://connect.microsoft.com/VisualStudio/feedback/details/675188/a-memorycache-hosted-in-asp-net-sometimes-breaks-in-its-timer-callback-to-update-the-cache-size

I have to admit I don't understand what the relationship between MemoryCache and ExecutionContext is or why the Thread principal is being serialized during a cache read operation since everything is in memory. I wouldn't expect any serialization to be occurring unless cloning via serialization is happening under the covers.

RockfordLhotka replied on Tuesday, July 31, 2012

OK, that's unfortunate (that the easy test didn't fail).

Now we probably enter the realm of increasingly obscure possibilities...

.NET itself does sometimes serialize objects. For example, anything put into Session may be serialized using the BinaryFormatter in the case that Session is maintained out of proc. It is extremely likely that any caching scheme will also serialize objects, because they can't cache object references, only data.

In the darker and more obscure corners of serialization there are some holes. Most notably, serialization acts differently depending on the hosting process type. As an example, there's a difference between doing serialization in a .NET EXE as compared to code hosted in COM+ (Enterprise Services).

The primary difference, and perhaps what you are encountering, is that on deserialization the .NET runtime doesn't always find types that it actually knows about. It turns out that this is because the .NET type system apparently maintains four lists of known types, and in a .NET hosted process it checks all the lists. But in some cases (COM+, IE, other non-.NET hosts) it doesn't check all four lists.

Why? Nobody has ever been able to tell me. It was hard enough to find someone that even knew there were four lists in the type system - that took many conversations with many people over a period of years.

If this _is_ the problem, then there's a possible workaround (maybe). If you can run some of your code as the host starts up, you can hook an AppDomain level event that is raised when a type isn't resolved. In that event handler you can ask .NET to resolve the type - which will cause it to check all four lists.

This code is actually in CSLA .NET - look at the SerializationWorkaround method here:

http://www.lhotka.net/cslacvs/viewvc.cgi/core/branches/V4-3-x/Source/Csla/Server/Hosts/EnterpriseServicesPortal.cs?view=markup

This should be invoked exactly one time during the life of the AppDomain, and it will find the known type even when deserialization doesn't.

rcollette replied on Wednesday, August 01, 2012

More details.   This is releated to a WCF 4.0 service call.    A JSON service specifically.   I didn't notice that until I looked in the windows event log.   I will also try the fix you linked to.

The following are specifics of the service config if you are interested.   


    [ServiceBehavior(Name = "QuestionService",
     ConcurrencyMode = ConcurrencyMode.Multiple,
     InstanceContextMode = InstanceContextMode.Single,
     AddressFilterMode = AddressFilterMode.Any),
    System.ServiceModel.Activation.AspNetCompatibilityRequirements(
     RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class UserProfileService : IUserProfileService
    {
        
         WebInvoke(Method = "GET",
               RequestFormat = WebMessageFormat.Json,
               ResponseFormat = WebMessageFormat.Json,
               UriTemplate = "FetchUserProfileByPersonId/{personId}")]
        public UserProfileDto FetchUserProfileByPersonId(string personId)
        {
            int id = Int32.Parse(personId);
            return FetchUserProfileByPersonId(id);
        }

        private static UserProfileDto FetchUserProfileByPersonId(int personId)
        {
            UserProfile profile = UserProfile.Fetch(personId);
            UserProfileDto profileDto = new UserProfileDto(profile);
            return profileDto;
        }

        private static int CurrentPersonId()
        {
            MyHomeWorksIdentity identity = MyHomeWorksIdentity.Current;
            int personId = 0;
            if (identity != null && identity.IsAuthenticated)
            {
                personId = MyHomeWorksIdentity.Current.PersonId;
            }
            return personId;
        }
        
    }
<system.serviceModel>
  <!-- Uncomment to enable service logging-->
  <diagnostics wmiProviderEnabled="false">
    <messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" maxMessagesToLog="3000" />
  </diagnostics>
  <standardEndpoints>
    <webHttpEndpoint>
      <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" />
    </webHttpEndpoint>
  </standardEndpoints>
  <behaviors>
    <endpointBehaviors>
      <behavior name="JsonEndpointBehavior">
        <webHttp defaultOutgoingResponseFormat="Json" automaticFormatSelectionEnabled="true" faultExceptionEnabled="true" helpEnabled="true"/>
        <httpCachePolicy cacheability="NoCache"/>
      </behavior>
    </endpointBehaviors>
    <serviceBehaviors>
      <!--The default service behavior-->
      <behavior>
        <!--Enable http get of service format (wsdl or REST help)-->
        <serviceMetadata httpGetEnabled="true" />
        <!--Exception detail should not be included in production, use retail mode or disable service debug-->
        <serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="true" />
        <serviceAuthorization principalPermissionMode="Custom">
          <authorizationPolicies>
            <add policyType="Hsb.ServiceModel.Security.Web.FormsAuthenticationAuthorizationPolicy, Hsb.ServiceModel" />
          </authorizationPolicies>
        </serviceAuthorization>
        <serviceCredentials>
          <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Hsb.ServiceModel.Security.Web.FormsAuthenticationTicketUserNamePasswordValidator, Hsb.ServiceModel" />
        </serviceCredentials>
      </behavior>
      <behavior name="FormsAuthenticationBehavior">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug httpHelpPageEnabled="true" />
        <serviceAuthorization principalPermissionMode="Custom">
          <authorizationPolicies>
            <add policyType="Hsb.ServiceModel.Security.Web.FormsAuthenticationAuthorizationPolicy, Hsb.ServiceModel" />
          </authorizationPolicies>
        </serviceAuthorization>
        <serviceCredentials>
          <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Hsb.ServiceModel.Security.Web.FormsAuthenticationTicketUserNamePasswordValidator, Hsb.ServiceModel" />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <services>
    <service name="MyHomeWorks.ServiceModel.UserProfile.UserProfileService">
      <endpoint kind="webHttpEndpoint" behaviorConfiguration="JsonEndpointBehavior" contract="MyHomeWorks.ServiceModel.UserProfile.IUserProfileService"/>
    </service>
  </services>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true">
    <serviceActivations>
      <clear />
      <add service="MyHomeWorks.ServiceModel.UserProfile.UserProfileService" relativeAddress="~/Services/UserProfileService.svc"/>
    </serviceActivations>
  </serviceHostingEnvironment>
  <extensions>
    <behaviorExtensions>
      <!-- Microsoft Connect Issue ID 216431: The full assembly qualified typename including version, culture and key must be specified.-->
      <!-- The following endpoint behavior extension element sets the endpoint's address filter mode to any.  This allows the service
        to operate behind an SSL load balancer where externally https is used and internally http is used.-->
      <add name="addressFilterModeAny" type="Hsb.ServiceModel.Configuration.AddressFilterModeAnyElement, Hsb.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <add name="httpCachePolicy" type="Hsb.ServiceModel.Configuration.HttpCachePolicyElement, Hsb.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

RockfordLhotka replied on Wednesday, August 01, 2012

Well that's a whole other ball game.

CSLA objects (and most .NET objects in general) can't be reliably serialized by things like the XmlSerializer, JsonSerializer, etc. Those serializers can only reliably serialize objects with public read/write properties, public default constructors, no private fields, and no non-public properties.

This is why the data portal uses the BinaryFormatter, NetDataContractSerializer, or MobileFormatter - all of which are able to serialize objects with full fidelity, because they operate at the field level, not the property level.

In short, CSLA objects don't support the concept of being serialized by anything other than those three serializers.

RockfordLhotka replied on Wednesday, August 01, 2012

http://www.lhotka.net/cslanet/faq/XmlServicesFaq.ashx

rcollette replied on Wednesday, August 01, 2012

You skipped looking at the code.  I am using data transfer objects and not sending the business object over the wire.   However, the cache is being utilized during a WCF call which seems at least in some aspect similar to the MS Connect issue in which the cache was being used in a remoting scenario.  On the other hand I am running in ASP.NET compatibility mode so it should be similar to a standard asp.net pipeline call.   This is a common architecture so I can't imagine why I'm seeing an issue that hasn't previously been seen.  

I didn't get a chance to add the fix code today but should get to it tomorrow.

Copyright (c) Marimer LLC