Code working unexpectedly after hosting in IIS - c#

Below is the code to build a cache which stores (typeName,type) found in the assemblies loaded in the current app domain.
I am facing this weird issue when trying to load types from one particular assembly say assemblyK. The assemblyK has around 954 types. Logs are in sync and it works all well when locally hosted in Visual Studio. Also, when I deploy the website in IIS it works well in when the buildCache is called for the first time.
However when the application pool is restarted/ after calling it later, it runs the nested loop for only for 5 types and goes on to execute the next function i.e. it does not throw any exception and moves forward.
private static void BuildCache()
{
Assembly[] assemblies= AppDomain.CurrentDomain.GetAssemblies();
foreach(Assembly assembly in assemblies){
if(assembly.GlobalAssemblyCache){
if(assemblly.ToString().Contains("assemblyK"))
Logger.Log("GAC");
continue;
}
try
{
if(assembly.ToString().Contains("AssemblyK"))
{
string allTypes = string.Join(",",assembly.GetTypes().Select(x => x.ToString()).ToArray())
Logger.Log("All types :{0}",allTypes);
}
foreach(Type type in assembly.GetTypes())
{
if(assembly.ToString().Contains("AssemblyK"))
{
Logger.Log("Type parsed :{0}",type);
}
if(!typeCache.ContainsKey(type.FullName))
{
if(assembly.ToString().Contains("AssemblyK"))
{
Logger.Log("Adding to cache :{0}",type);
}
typeCache.Add(type.FullName,type);
}
}
catch(Exception ex)
{
Logger.Log("Exception Caught : {0}",ex.ToString());
}
}
}
Notably, The AssemblyK.dll build in Visual Studio 2008 in .Net 3.5 version works all well and when complied in Visual Studio 2019 causing the issue. Application Pool is 2.0. And the assemblyK is loaded in the "assemblies" always. Please Help.
P.S.: I copied the logs for allTypes received in both the cases(when it works and doesnt) in two files and did a file compare using cmd, with fc file1 file2.
Both contained the identical data i.e.954 types. So my question is how can the foreach run randomly? What could be the issue here?
Update : The logs read:
1) In Working case :
All Types : Type1,Type2,Type3,..Type954
Type Parsed 954 times
Adding to cache 954 times
2) In Not Working case :
All Types : Type1,Type2,Type3,..Type954.
Type Pased 4 Times.
typeCache is a private static readonly Dictionary

Related

Getting InvalidCastException with a Plugin architecture

I have a plugin architecutre which is using a Shared DLL to implement a plugin interface and a base plugin class (eg. IPlugin & BasePlugin). The shared DLL is used by both the Plugin and the main application.
For some reason, when I try to cast an object that was instantiated by the main application to the shared plugin interface (IPlugin), I get an InvalidCastException saying I cannot cast this interface.
This is despite:
The class definitely implementing the plugin interface.
The Visual Studio debugger saying that "(objInstance is IPlugin)" is true when I mouse-over the statement, despite the same 'if' condition evaluating as false when I step-through the code or run the .exe.
The Visual Studio Immediate Window also confirms that the above condition is true and that is it possible to cast the object successfully.
I have cleaned/deleted all bin folders and also tried both VS2019 and VS2022 with exactly the same outcome.
I am going a little crazy here because I assume it is something to do with perhaps with multiple references of the same DLL somehow causing the issue (like this issue). The fact that the debugger tells me everything is okay makes it hard to trouble-shoot. I'm not sure if it's relevant but I have provided example code and the file structure below:
Shared.dll code
public interface IPlugin
{
}
public class BasePlugin : IPlugin
{
}
Plugin.dll code
class MyPlugin : BasePlugin
{
void Init()
{
// The plugin contains references to the main app dlls as it requires
// to call various functions inside the main app such as adding menu items etc
}
}
Main.exe
(Note: This is pseudo-code only and does not show the plugin framework used to load the plugin DLLs via Assembly.Load())
var obj = Activator.CreateInstance(pluginType); // Plugin type will be 'MyPlugin'
if (obj is IPlugin) // <== This executes as false when executed but evaluates to true in the Visual Studio debugger
{
}
var castObj = (IPlugin)obj // <== This will cause an invalid cast exception
Folder structure
|--- MainApp.exe
|--- Shared.dll
|--- Addins
|------- Plugin.dll
|------- Shared.dll
Does anyone know the reasons how I can trouble-shoot this issue and what might be causing it?
My suggestion is that (using the Properties\Signing tab) you sign your shared dll assembly with a Strong Name Key. If your plugin classes reference a signed copy of the dll, there should be no possibility of confusion.
The application has no prior knowledge of what the plugins are going to be, but it does know that it's going to support IPlugin. Therefore, I would reference the PlugInSDK project directly in the app project.
Testbench
Here's what works for me and maybe by comparing notes we can get to the bottom of it.
static void Main(string[] args)
{
Confirm that the shared dll (known here as PlugInSDK.dll) has already been loaded and display the source location. In the Console output, note the that PublicKeyToken is not null for the SDK assembly (this is due to the snk signing).
var sdk =
AppDomain.CurrentDomain.GetAssemblies()
.Single(asm=>(Path.GetFileName(asm.Location) == "PlugInSDK.dll"));
Console.WriteLine(
$"'{sdk.FullName}'\nAlready loaded from:\n{sdk.Location}\n");
The AssemblyLoad event provides the means to examine on-demand loads as they occur:
AppDomain.CurrentDomain.AssemblyLoad += (sender, e) =>
{
var name = e.LoadedAssembly.FullName;
if (name.Split(",").First().Contains("PlugIn"))
{
Console.WriteLine(
$"{name}\nLoaded on-demand from:\n{e.LoadedAssembly.Location}\n");
}
};
It doesn't matter where the plugins are located, but when you go to discover them I suggest SearchOption.AllDirectories because they often end up in subs like netcoreapp3.1.
var pluginPath =
Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"..",
"..",
"..",
"PlugIns"
);
Chances are good that a copy of PlugInSDK.dll is going to sneak into this directory. Make sure that the discovery process doesn't accidentally pick this up. Any other Type that implements IPlugin gets instantiated and put in the list.
List<IPlugin> plugins = new List<IPlugin>();
foreach (
var plugin in
Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories))
{
// Exclude copy of the SDK that gets put here when plugins are built.
if(Path.GetFileName(plugin) == "PlugInSDK.dll") continue;
// Be sure to use 'LoadFrom' not 'Load'
var asm = Assembly.LoadFrom(plugin);
// Check to make sure that any given *.dll
// implements IPlugin before adding to list.
Type plugInType =
asm.ExportedTypes
.Where(type =>
type.GetTypeInfo().ImplementedInterfaces
.Any(intfc => intfc.Name == "IPlugin")
).SingleOrDefault();
if ((plugInType != null) && plugInType.IsClass)
{
plugins.Add((IPlugin)Activator.CreateInstance(plugInType));
}
}
Now just display the result of the plugin discovery.
Console.WriteLine("LIST OF PLUGINS");
Console.WriteLine(
string.Join(
Environment.NewLine,
plugins.Select(plugin => plugin.Name)));
Console.ReadKey();
}
Is this something you're able to repro on your side?

Dictionary and Items protected members of a KeyedCollection in Unity3d 5.3.1?

I've been using Unity 5 for a year and everything worked fine until last week when we had the idea to update our IDE to 5.3.1 version.
Now the compiler shows two errors:
The name `Dictionary' does not exist in the current context.
The name `Items' does not exist in the current context.
both localized in a simple KeyedCollection
public class CommandProcessQueueCollection : KeyedCollection<int, CommandProcessQueue>
{
public bool TryGetQueue(int id, out CommandProcessQueue queue)
{
if (Dictionary != null)
{
return Dictionary.TryGetValue(id, out queue);
}
foreach (var i in Items)
{
var k = GetKeyForItem(i);
if (Comparer.Equals(id, k))
{
queue = i;
return true;
}
}
queue = default(CommandProcessQueue);
return false;
}
protected override int GetKeyForItem(CommandProcessQueue queue)
{
return queue.Id;
}
}
After several attempts things became even more tricky as the exact same code has been compiled in 5 different machines with 5.3.1 installed.
Well, 2 out of 5 proved to compile without any error.
Furthermore, msbuild seems to compile the code without any error as well.
Probably, there are differences, among the machines, in the .NetFramework used by Mono for compilations.
Do you have any idea about this odd issue?
PS: I'm adding using System.Collections.ObjectModel and my current API compatibility option in Unity editor is set to .NET 2.0.
Ok, my team solved.
In those machines Windows Support for Editor 5.3.1f1 was not installed therefore the standard module of webplayer was running and few API of .NET were not available.

Cannot Debug CLR User Defined Type

I'm trying to debug a CLR User Defined Type in SQL Server 2014 / Visual Studio 2013. I can deploy my UDT to the database and execute the script that calls that UDT in the debugger from within visual studio but it throws an error and doesn't hit my breakpoint. The error happens in the static Parse method on the type.
My DBA thought it might be a permissions issue (preventing me from debugging, not the underlying error) but didn't have time to look into it. Any suggestions on what I need to do to debug my UDT?
The SQL Server is a local instance
Per request, the code of Parse:
public static SqlQueryDescriptor Parse(SqlString s)
{
if (s.IsNull)
return Null;
var queryDescriptor = new JsonSerializer().Deserialize<QueryDescriptor>(s.Value);
//var queryDescriptor = new QueryDescriptor();
SortContext sort = new SortContext();
if (queryDescriptor.Sorts != null)
{
foreach (var sortDescriptor in queryDescriptor.Sorts)
{
sort.AddSort(sortDescriptor.Property, sortDescriptor.Direction);
}
}
return new SqlQueryDescriptor(sort, queryDescriptor.Filters, queryDescriptor.Page);
}
The error is a NRE in the Json deserialization library, I need to debug to figure out why that is happening as I didn't write that library and imported it from the web. Although the break point still doesn't hit even if I don't use the deserializer (commented out line)

Executable fails with weird exception

I am using ILMerge and Quartz.NET in a C# .NET 4.0 Windows Service application. The app runs fine without using ILMerge, but now that we're nearing shipping release, I wanted to combine all DLLs into a single executable.
Problem is, that ILMerge seems to work fine, but when I run the combined executable, it throws this exception:
Unhandled Exception: Quartz.SchedulerException: ThreadPool type 'Quartz.Simpl.SimpleThreadPool' could not be instantiated. ---> System.InvalidCastException: Unable to cast object of type 'Quartz.Simpl.SimpleThreadPool' to type 'Quartz.Spi.IThreadPool'.
at Quartz.Util.ObjectUtils.InstantiateType[T](Type type) in :line 0
at Quartz.Impl.StdSchedulerFactory.Instantiate() in :line 0
--- End of inner exception stack trace ---
at Quartz.Impl.StdSchedulerFactory.Instantiate() in :line 0
at Quartz.Impl.StdSchedulerFactory.GetScheduler() in :line 0
Does anyone have any idea why this is? I have been wasting over 4 hours already and I can't figure it out. If I don't combine with ILMerge, then everything runs fine (with the Quartz.dll and Common.Logging.dll in the same directory).
I'm sure someone must have tried packaging Quartz.net up like this before, any ideas?
Disclaimer: I don't know Quartz.NET at all, although I spent some time struggling with ILMerge. When I finally understood its limitations... I stopped using it.
ILMerge'd application tends to have problems with everything which contains the word "reflection".
I can guess (I've never used Quartz.NET) that some classes are resolved using reflection and driven by configuration files.
Class is not only identified by its name (with namespace) but also by assembly it is coming from (unfortunatelly it doesn't get displayed in exception message).
So, let's assume you had (before ILMerging) two assemblies A (for you Application) and Q (for Quartz.NET).
Assembly 'A' was referencing assembly 'Q' and was using a class 'Q:QClass' which was implementing 'Q:QIntf'.
After merging, those classes became 'A:QClass' and 'A:QIntf' (they were moved from assembly Q to A) and all the references in code has been replaced to use those (completely) new classes/interfaces, so "A:QClass" is implementing "A:QIntf" now.
But, it did not change any config files/embedded strings which may still reference "Q:QClass".
So when application is reading those not-updated config files it still loads "Q:QClass" (why it CAN find it is a different question, maybe you left assembly 'Q' in current folder or maybe it is in GAC - see 1).
Anyway, "Q:QClass" DOES NOT implement "A:QIntf", it still implements "Q:QIntf" even if they are binary identical - so you can't cast 'Q:QClass' to 'A:QIntf'.
The not-ideal-but-working solution is to "embed" assemblies instead of "merging" them. I wrote a open-source tool which does it (embedding instead of merging) but it is not related to this question. So if you decide to embed just ask me.
You can test it by removing (hiding, whatever works for you) every single instance of Q.dll on your PC. If I'm right, the exception should say now 'FileNotFound'.
You could try creating your own ISchedulerFactory and avoid using reflection to load all of your types.
The StdSchedulerFactory uses this code to creat a threadpool. It's where your error is happening and would be the place to start looking at making changes:
Type tpType = loadHelper.LoadType(cfg.GetStringProperty(PropertyThreadPoolType)) ?? typeof(SimpleThreadPool);
try
{
tp = ObjectUtils.InstantiateType<IThreadPool>(tpType);
}
catch (Exception e)
{
initException = new SchedulerException("ThreadPool type '{0}' could not be instantiated.".FormatInvariant(tpType), e);
throw initException;
}
The ObjectUtils.InstantiateType method that is called is this one, and the last line is the one throwing your exception:
public static T InstantiateType<T>(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type", "Cannot instantiate null");
}
ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
if (ci == null)
{
throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name);
}
return (T) ci.Invoke(new object[0]);
}
Right after this section in the factory, datasources are loaded using the same pattern and then the jobs themselves are also loaded dynamically which means you'd also have to write your own JobFactory. Since Quartz.Net loads a bunch of bits and pieces dynamically at runtime going down this road means you might end up rewriting a fair amount of things.

unable to deploy sql clr stored procedure (unable to verify metadata)

I am trying to run the CREATE ASSEMBLY command for a SQL CLR (.Net 3.5) assembly on SQL Server 2008. It returns a cryptic error message:
An error occurred while gathering metadata from assembly 'My.Awesome.Assembly' with HRESULT 0x80004005.
Why is it doing this, and how can I fix it without deploying it as UNSAFE?
steps I have done:
Followed all rules in http://msdn.microsoft.com/en-us/library/ms403273.aspx
Used no static fields
Have created 2 other SQL CLR assemblies that deploy just fine
This is what solved the problem for me; for future reference to anyone out there, SQL CLR stuff is very, very picky.
I had a constructor like this:
public MyObject(IEnumerable<T> items)
{
_innerItems = items.ToDictionary(i => i.Key);
}
I changed it to this:
public MyObject(IEnumerable<T> items)
{
_innerItems = new Dictionary<int, T>();
foreach (var item in items)
{
_innerItems.Add(item.Key, item);
}
}
And it was then able to deploy. I then proceeded to pound my head against my desk. Two methods, functionally equivalent; one works, and one has a cryptic deployment error message.

Categories