First, obviously, find the two types for which the cast failed and verify that they are the same type or otherwise castable.

Next, if the type was just deserialized, also verify that its assembly successfully loaded in the target appdomain.

If everything seems fine, check to see if the assemblies for those two types are loaded from different locations and in the same appdomain. (The actual cast is done in just one appdomain, even if the exception happens when passing a type between two appdomains.) Even if the bits of those assemblies are totally identical, if they are loaded from different paths, they will be considered different, so their types will be considered different. (See Comparing Already-Loaded Assemblies.)

A quick way to check for that is to examine the loaded module window of a debugger to see if that assembly was loaded multiple times. If it was, break on module loads to get the callstack for the unexpected load. If that's inconvenient, try getting the Fusion log.

Usually, the problem is that:

  1. The assembly is available in the GAC (or the ApplicationBase) and loaded there by static reference (something was compiled against that assembly).
  2. It has also been loaded dynamically by path, from another path (LoadFrom(), LoadFile(), etc.).
  3. Then, the code tries to cast the type from (2) to the corresponding type from (1).

To fix this, once you find the offending caller, you will need to either cause the two types to be loaded from the same assembly from the exact same path, or avoid doing the cast. To decide between the assemblies at paths (1) and (2), see Choosing a Binding Context. Usually, I recommend using (1) - see Switching to the Load Context for help with implementing that.