2.1.4 CslaDataSource TypeLoadException

2.1.4 CslaDataSource TypeLoadException

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


Skafa posted on Monday, February 19, 2007

In CslaDataSource.cs I get the following error when trying to do an update:

Kan type MyEyes.SmartMarkets.Library.ProductList niet laden vanuit assembly Csla, Version=2.1.4.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30.

translated:

Could not load type MyEyes.SmartMarkets.Library.ProductList from assembly Csla,
Version=2.1.4.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30.

I tracked it down to this:

    internal static Type GetType(
      string assemblyName, string typeName)
    {
      if (!string.IsNullOrEmpty(assemblyName))
      {
        Assembly asm = Assembly.Load(assemblyName);
        return asm.GetType(typeName, true, true);
      }
      else
        return Type.GetType(typeName, true, true);  <----
    }

since TypeAssemblyName isn't used anymore, assemblyName is null and uses Type.GetType to return the type, but it seems Type.GetType won't return types from other assemblies?

Selecting data from the datasource Works!

Remco Ros

Skafa replied on Monday, February 19, 2007

Ok, it seems GetType expects a AssemblyQualifiedName.

changed TypeName to 'MyEyes.SmartMarkets.Library.ProductList, MyEyes.SmartMarkets.Library' and it works now.

   

RockfordLhotka replied on Monday, February 19, 2007

This is most certainly a bug Sad [:(]  I'll see about getting it fixed soon, sorry about that.

RockfordLhotka replied on Monday, February 19, 2007

Well, let me be more specific. The GetType() method is not correct, because it still uses the now-defunct assemblyname parameter, and I'll clean that up.

The bigger bug is in the CslaDataSourceConfiguration dialog, where it improperly builds the type name. Obviously it should be including the assembly name as part of the result.

Skafa replied on Monday, February 19, 2007

I didn't use the dialog, i use source-view only so i don't know about a possible bug in that dialog.

For those strugling with this issue too: change TypeName to something like this: "<Some.NameSpace.BussinesObject>, <Assembly>"

RockfordLhotka replied on Monday, February 19, 2007

Yeah, this whole thing is a problem I wish I’d caught (or a tester had caught) ealier…

 

One easy answer is to make the TypeAssemblyName property have meaning again. In fact that’s the only answer that won’t break existing aspx pages.

 

Another is to keep TypeAssemblyName obsolete/unused and force everyone to change their TypeName values to conform to the new scheme.

 

Grumble.

 

Rocky

 

From: Skafa [mailto:cslanet@lhotka.net]
Sent: Monday, February 19, 2007 12:03 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] 2.1.4 CslaDataSource TypeLoadException

 

I didn't use the dialog, i use source-view only so i don't know about a possible bug in that dialog.

For those strugling with this issue too: change TypeName to something like this: "<Some.NameSpace.BussinesObject>, <Assembly>"


RockfordLhotka replied on Monday, February 19, 2007

In light of this issue, I've backed 2.1.4 into test mode again. This needs fixing, and the more I think about it the bigger the fix must be. I can't break the compatibility with existing aspx pages, so I have to make TypeAssemblyName have meaning again.

Yet I really don't want it to be required, it is simpler to combine everything into the TypeName property.

So I am trying to make both work as smoothly as possible.

Skafa replied on Monday, February 19, 2007

This should do the trick:

up to you wether you like it or not:

      private static System.Collections.Hashtable _typeCache = new System.Collections.Hashtable();

    /// <summary>
    /// Returns a <see cref="Type">Type</see> object based on the
    /// assembly and type information provided.
    /// </summary>
    /// <param name="assemblyName">(Optional) Assembly name containing the type.</param>
    /// <param name="typeName">Full type name of the class.</param>
    /// <remarks></remarks>
    internal static Type GetType(
      string assemblyName, string typeName)
    {
      if (!string.IsNullOrEmpty(assemblyName))
      {
        Assembly asm = Assembly.Load(assemblyName);
        return asm.GetType(typeName, true, true);
      }
      else if (!typeName.Contains(","))
      {
          if (_typeCache[typeName] != null)
              return (Type)_typeCache[typeName];

          foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
          {
              Type type = asm.GetType(typeName, false, true);
              if (type != null)
              {
                  _typeCache[typeName] = type;
                  return type;
              }
          }
      }
      return Type.GetType(typeName, true, true);
    }

Skafa replied on Monday, February 19, 2007

Or a little bit revised:

      private static System.Collections.Hashtable _typeCache = new System.Collections.Hashtable();

    /// <summary>
    /// Returns a <see cref="Type">Type</see> object based on the
    /// assembly and type information provided.
    /// </summary>
    /// <param name="assemblyName">(Optional) Assembly name containing the type.</param>
    /// <param name="typeName">Full type name of the class.</param>
    /// <remarks></remarks>
    internal static Type GetType(
      string assemblyName, string typeName)
    {
      if (!string.IsNullOrEmpty(assemblyName))
      {
        Assembly asm = Assembly.Load(assemblyName);
        return asm.GetType(typeName, true, true);
      }

      Type type = _typeCache[typeName] as Type;
      if (type != null)
          return type;

      type = Type.GetType(typeName, false, true);
      if (type != null)
      {
          _typeCache[typeName] = type;
          return type;
      }

      foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
      {
          type = asm.GetType(typeName, false, true);
          if (type != null)
          {
              _typeCache[typeName] = type;
              return type;
          }
      }

      throw new TypeLoadException();
    }

RockfordLhotka replied on Tuesday, February 20, 2007

I appreciate the code suggestion, but I ended up going a slightly different direction.

There's a new version of 2.1.4 available for download (070220), so if you could try that one and see if it works for you I would appreciate it.

Skafa replied on Wednesday, February 21, 2007

This seems Ok if you use either the "type, assembly" syntax or specify TypeAssemblyName.

either way, you háve to specify the assembly.

what i was trying to accomplish (and i thought you wanted to accomplish) was that you ónly have to specify the typename (like in ObjectDataSource)

I think i have THE solution. I took a look in how ObjectDataSource solves this.

it seems it uses BuildManager.GetType() to resolve the Type from the TypeName string.

ObjectDataSourceView:

private Type GetType(string typeName)
{
if (this.TypeName.Length == 0)
{
throw new InvalidOperationException(SR.GetString("ObjectDataSourceView_TypeNotSpecified", new object[] { this._owner.ID }));
}
Type type1 = BuildManager.GetType(this.TypeName, false, true);
if (type1 == null)
{
throw new InvalidOperationException(SR.GetString("ObjectDataSourceView_TypeNotFound", new object[] { this._owner.ID }));
}
return type1;
}


How about using that !?

RockfordLhotka replied on Wednesday, February 21, 2007

The problem is that GetType() is used at runtime, potentially many times on each page request. I don’t want to have that method using reflection or other heavy-weight techniques to get this information, because this could be a performance-sensitive area. I’d rather require the assembly information one way or the other – or in this case, either way.

 

Thanks!

 

Rocky

 

From: Skafa [mailto:cslanet@lhotka.net]
Sent: Wednesday, February 21, 2007 7:59 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: 2.1.4 CslaDataSource TypeLoadException

 

This seems Ok if you use either the "type, assembly" syntax or specify TypeAssemblyName.

either way, you háve to specify the assembly.

what i was trying to accomplish (and i thought you wanted to accomplish) was that you ónly have to specify the typename (like in ObjectDataSource)

I think i have THE solution. I took a look in how ObjectDataSource solves this.

it seems it uses BuildManager.GetType() to resolve the Type from the TypeName string.

ObjectDataSourceView:

private Type GetType(string typeName)
{
      if (this.TypeName.Length == 0)
      {
            throw new InvalidOperationException(SR.GetString("ObjectDataSourceView_TypeNotSpecified", new object[] { this._owner.ID }));
      }
      Type type1 = BuildManager.GetType(this.TypeName, false, true);
      i
 f (type1 == null)
      {
            throw new InvalidOperationException(SR.GetString("ObjectDataSourceView_TypeNotFound", new object[] { this._owner.ID }));
      }
      return type1;
}



How about using that !?



Skafa replied on Wednesday, February 21, 2007

I doubt this is an issue as this method is used exactly the same by objectdatasource

you could implement a typeCache, as i did in my earlier example. This would also reduce calls to Type.GetType() (Or BuildManager.GetType())

ofcourse it's your decision, but why would you not look at how objectdatasource does this. After all it's for our convience.


btw: I use Lutz Roeder's .NET reflector (http://www.aisto.com/roeder/dotnet/) to disassemble microsoft's and 3rd party dll's, you can learn a lot about how .net does things by looking at those

Skafa replied on Thursday, February 22, 2007

Look rocky, I inspected some more and what BuildManager.GetType( does is this:

public static Type GetType(string typeName, bool throwOnError, bool ignoreCase)
{
Type type1 = null;
if (Util.TypeNameContainsAssembly(typeName))
{
type1 = Type.GetType(typeName, throwOnError, ignoreCase);
if (type1 != null)
{
return type1;
}
}
if (!BuildManager.InitializeBuildManager())
{
return Type.GetType(typeName, throwOnError, ignoreCase);
}
try
{
type1 = typeof(BuildManager).Assembly.GetType(typeName, false, ignoreCase);
}
catch (ArgumentException exception1)
{
throw new HttpException(SR.GetString("Invalid_type", new object[] { typeName }), exception1);
}
if (type1 == null)
{
BuildManager._theBuildManager.EnsureTopLevelFilesCompiled();
type1 = Util.GetTypeFromAssemblies(BuildManager.TheBuildManager.TopLevelReferencedAssemblies, typeName, ignoreCase);
if (type1 != null)
{
return type1;
}
AssemblyCollection collection1 = CompilationUtil.GetAssembliesForAppLevel();
if (collection1 != null)
{
Type type2 = CompilationUtil.GetTypeFromAssemblies(collection1, typeName, ignoreCase);
if (type2 != null)
{
if ((type1 != null) && (type2 != type1))
{
throw new HttpException(SR.GetString("Ambiguous_type", new object[] { typeName, Util.GetAssemblySafePathFromType(type1), Util.GetAssemblySafePathFromType(type2) }));
}
type1 = type2;
}
}
if ((type1 == null) && throwOnError)
{
throw new HttpException(SR.GetString("Invalid_type", new object[] { typeName }));
}
}
return type1;
}
it doesn't have a cache (you could implement it) but it let's you specify the assemblyname, in which case it just uses Type.GetType.


RockfordLhotka replied on Thursday, February 22, 2007

OK, you've convinced me to make the change - along the lines of your second approach, using the type cache.

However, I'm leaving the configuration dialog alone, so it always sets TypeName to a fully qualified type name (including the assembly), because that should provide better performance overall.

But the assembly name is now entirely optional. The changes are in svn now.

Skafa replied on Thursday, February 22, 2007

Great Rocky,

expect more to come ;-)

Copyright (c) Marimer LLC