How to identify cause of Assembly issue? - c#

I'm getting an error in a new unfamiliar area to me, SharePoint development. I'm trying to debug my solution via Visual Studio 2012 and I get the following error as recorded in the EventLog:
Exception information:
Exception type: FileLoadException
Exception message: The located assembly's manifest definition does not match the
assembly reference. (Exception from HRESULT: 0x80131040)
The error is seen within the .ascx.g.cs for my control, in a #__BuildControlTree method. Unfortunately it doesn't tell me the name of the assembly, so I'm not sure where the error lies. The stack trace reported is:
Stack trace:
at MI.VWP.Chart.Chart.CreateChildControls() at
System.Web.UI.Control.EnsureChildControls() at
System.Web.UI.WebControls.WebParts.Part.get_Controls() at
System.Web.UI.Control.AddParsedSubObject(Object obj) at
Microsoft.SharePoint.WebPartPages.WebPart.AddParsedSubObject(Object
obj) at
MooDInternational.VisualWebParts.Chart.Chart.__BuildControlTree(Chart
__ctrl) at MooDInternational.VisualWebParts.Chart.Chart.InitializeControl() at
MooDInternational.VisualWebParts.Chart.Chart.OnInit(EventArgs e) at
System.Web.UI.Control.InitRecursive(Control namingContainer) at
System.Web.UI.Control.AddedControl(Control control, Int32 index) at
System.Web.UI.WebControls.WebParts.WebPartManager.WebPartManagerControlCollection.AddWebPartHelper(WebPart
webPart) at
System.Web.UI.WebControls.WebParts.WebPartManager.WebPartManagerControlCollection.AddWebPart(WebPart
webPart) at
System.Web.UI.WebControls.WebParts.WebPartManagerInternals.AddWebPart(WebPart
webPart) at
Microsoft.SharePoint.WebPartPages.SPWebPartManager.AddWebPartWithRetry(WebPart
webPart) at
Microsoft.SharePoint.WebPartPages.SPWebPartManager.CreateWebPartsFromRowSetData(Boolean
onlyInitializeClosedWebParts) at
Microsoft.SharePoint.WebPartPages.SPWebPartManager.LoadWebParts()
at
Microsoft.SharePoint.WebPartPages.SPWebPartManager.OnPageInitComplete(Object
sender, EventArgs e) at System.EventHandler.Invoke(Object sender,
EventArgs e) at System.Web.UI.Page.OnInitComplete(EventArgs e)
at System.Web.UI.Page.ProcessRequestMain(Boolean
includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
I don't however get a call to that method if I attempt to set a breakpoint there. I've tried looking at fusionlog but get the following which I don't think helps...
* Assembly Binder Log Entry (13/09/2012 # 14:16:15) *
The operation was successful. Bind result: hr = 0x0. The operation
completed successfully.
Assembly manager loaded from:
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorwks.dll Running
under executable C:\Program Files (x86)\Microsoft Visual Studio
11.0\Common7\IDE\Extensions\Microsoft\SharePoint\vssphost4.exe
--- A detailed error log follows.
=== Pre-bind state information === LOG: User = **.***** LOG: DisplayName = Microsoft.Build.Framework, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a (Fully-specified)
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio
11.0/Common7/IDE/Extensions/Microsoft/SharePoint/ LOG: Initial PrivatePath = C:\Program Files (x86)\Microsoft Visual Studio
11.0\Common7\IDE\Extensions\Microsoft\SharePoint\VstspCache10724 LOG: Dynamic Base = NULL LOG: Cache Base = NULL LOG: AppName = NULL Calling
assembly : (Unknown). LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\Program Files
(x86)\Microsoft Visual Studio
11.0\Common7\IDE\Extensions\Microsoft\SharePoint\vssphost4.exe.Config LOG: Using machine configuration file from
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\config\machine.config.
LOG: Binding succeeds. Returns assembly from
C:\Windows\assembly\GAC_MSIL\Microsoft.Build.Framework\2.0.0.0__b03f5f7f11d50a3a\Microsoft.Build.Framework.dll.
LOG: Assembly is loaded in default load context.
Can anyone make any suggestions on how I might resolve this issue?
EDIT
Using Reflector I managed to debug into the generated #_BuildControl method a little, and discover a call to AddParsedSubject(object child) which is a virtual method on a control.
Adding the following code:
protected override void AddParsedSubObject(object obj)
{
System.Web.UI.Control child = obj as System.Web.UI.Control;
if (child != null)
{
try
{
base.AddParsedSubObject(obj);
System.Diagnostics.Trace.WriteLine("Added " + child.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("Error with " + child.ToString() + " : " + ex.ToString());
}
}
}
This told me that the control causing the issue was a DevExpress.XtraCharts.Web.WebChartControl, so know I've tracked the assembly down to one of the DevExpress ones, not 100% sure which and not yet sure how to fix it.

In asp.net this kind of errors means that old references are not been replaced in bin or temp asp.net folder. I would clean those folders an rebuild my project if i was you..

Related

Could not find the IHttpModule if I install it to the GAC cache

I have a simple IHttpModule
namespace System.Web.Extensions.Resource
{
public class MyHttpModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.PreSendRequestContent += PreSend_RequestContent;
}
private void PreSend_RequestContent(object sender, EventArgs e)
{
HttpResponse response = ((HttpApplication)sender).Response;
response.AddHeader("MyHttpModule", "Running");
}
}
}
And I installed it to GAC with powershell (No errors at all):
$name = "c:\MyHttpModule.dll";
[System.Reflection.Assembly]::Load('System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a');
$publish = New-Object System.EnterpriseServices.Internal.Publish;
$publish.GacInstall($name);
$type = 'System.Web.Extensions.Resource.MyHttpModule,' + [System.Reflection.AssemblyName]::GetAssemblyName($name).FullName;
C:\Windows\System32\inetsrv\Appcmd.exe add module /name:MyHttpModule /type:"$type"
But when I access the IIS site, I got
Exception Details: System.IO.FileNotFoundException: Could not load file or assembly 'MyHttpModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
So Windows no longer support installation to GAC cache?
EDIT
I've added a strong name and confirmed the assembly is installed to C:\Windows\Microsoft.NET\assembly\GAC_MSIL\MyHttpModule\v4.0_1.0.0.0__4959579d21f18138.
Now IIS has a different error
System.TypeLoadException: Could not load type 'System.Web.Extensions.Resource.MyHttpModule' from assembly 'MyHttpModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4959579d21f18138'.
But from ILSpy I can see this type is available, so what was wrong then?
EDIT again
It suddenly worked, I've seen the header added by the module. Not sure why, but closing it now.
The PublicKeyToken is null, which mean the DLL is not signed with a strong name. You need to sign the Dll and then put it to GAC, please refer the MSDN article here, the article also tells you how to sign it in VS:

DataConnectionDialog crashes in Visual Studio 2017

I'm converting to VS2017 a legacy project which uses a database connection dialog to collect data to format a connection string.
This line below is triggering an error:
var dialog = new DataConnectionDialog();
Could not load file or assembly 'Microsoft.Data.ConnectionUI,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=f4ca07f51760da93' or
one of its dependencies. The system cannot find the file specified.:
System.IO.FileNotFoundException: Could not load file or assembly
'Microsoft.Data.ConnectionUI, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=f4ca07f51760da93' or one of its dependencies. The
system cannot find the file specified. File name:
'Microsoft.Data.ConnectionUI, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=f4ca07f51760da93' at
Microsoft.Data.ConnectionUI.DataConnectionDialog..ctor() at
Wings4CloudPackage.Forms.frmNewSolution.btnDatabase_Click(Object
sender, EventArgs e)
=== Pre-bind state information === LOG: DisplayName = Microsoft.Data.ConnectionUI, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=f4ca07f51760da93 (Fully-specified) LOG: Appbase =
file:///C:/Program Files (x86)/Microsoft Visual
Studio/2017/Enterprise/Common7/IDE/ LOG: Initial PrivatePath = NULL
Calling assembly : Microsoft.Data.ConnectionUI.Dialog,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=f4ca07f51760da93.
=== LOG: This bind starts in LoadFrom load context. WRN: Native image will not be probed in LoadFrom context. Native image will only be
probed in default load context, like with Assembly.Load().
I have checked this path: C:/Program Files (x86)/Microsoft Visual Studio/2017/Enterprise/Common7/IDE/ and the referenced Microsoft.Data.ConnectionUI.dll is there.
This project uses the following nuget package to show the dialog: DataConnectionDialog (https://www.nuget.org/packages/DataConnectionDialog/1.1.0)
This code works in VS2013, but not in VS2017.
For me in VS 2017 in WPF app works this:
DataConnectionDialog dcd = new DataConnectionDialog();
DataProvider dataProviderSql = DataProvider.SqlDataProvider;
DataSource dataSourceSql = new DataSource(dataProviderSql.Name, dataProviderSql.DisplayName);
dataSourceSql.Providers.Add(dataProviderSql);
DataProvider dataProviderOracle = DataProvider.OracleDataProvider;
DataSource dataSourceOracle = new DataSource(dataProviderOracle.Name, dataProviderOracle.DisplayName);
dataSourceOracle.Providers.Add(dataProviderOracle);
DataProvider dataProviderOle = DataProvider.OleDBDataProvider;
DataSource dataSourceOle = new DataSource(dataProviderOle.Name, dataProviderOle.DisplayName);
dataSourceOle.Providers.Add(dataProviderOle);
DataProvider dataProviderOdbc = DataProvider.OdbcDataProvider;
DataSource dataSourceOdbc = new DataSource(dataProviderOdbc.Name, dataProviderOdbc.DisplayName);
dataSourceOdbc.Providers.Add(dataProviderOdbc);
dcd.DataSources.Add(dataSourceSql);
dcd.DataSources.Add(dataSourceOracle);
dcd.DataSources.Add(dataSourceOle);
dcd.DataSources.Add(dataSourceOdbc);
dcd.SelectedDataSource = dataSourceSql;
dcd.SelectedDataProvider = dataProviderSql;
if (DataConnectionDialog.Show(dcd) == System.Windows.Forms.DialogResult.OK)
{
System.Windows.MessageBox.Show("YES");
}

Creating a signalR client HubConnection with Powershell

I am trying to connect to a SignalR hub using a powershell script. I am very new to powershell, so please excuse any rookie mistake.
I have set up a minimal not working example of what I have tried here :
Gist
Relevant code:
Load dlls
$dllFolder = -join((Get-Item -Path ".\" -Verbose).FullName, "\bin\Debug\")
[string[]] $dllPathsToLoad = #("\Newtonsoft.Json.dll", "\Microsoft.AspNet.SignalR.Client.dll")
$token = "insertyourtokenhere"
function LoadDllPaths($dlls)
{
foreach ($dll in $dlls)
{
$dllpath = $dllFolder + $dll
[System.Reflection.Assembly]::LoadFrom($dllpath)
}
}
[...]
LoadDllPaths($dllPathsToLoad)
Create HubConnection:
$server = "https://localhost/rest/"
[...]
$hub = New-Object Microsoft.AspNet.SignalR.Client.HubConnection($server)
Steps:
Create a new Visual Studio project
Add Newtonsoft.Json v10.0.2 Nuget package (latest)
Add Microsoft.AspNet.SignalR.Client v2.2.2 Nuget package (latest)
Add powershell script to the root of the project
With powershell (run as admin), type .\HubConnectionTestsScript.ps1
Result:
View on imgur
Error : System.Management.Automation.MethodInvocationException: Exception calling ".ctor" with "1" argument(s): "Could not load file or assembly 'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified." ---> System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified.
at Microsoft.AspNet.SignalR.Client.Connection..ctor(String url, String queryString)
at Microsoft.AspNet.SignalR.Client.HubConnection..ctor(String url, Boolean useDefaultUrl)
--- End of inner exception stack trace ---
at System.Management.Automation.DotNetAdapter.AuxiliaryConstructorInvoke(MethodInformation methodInformation, Object[] arguments, Object[] originalArguments)
at System.Management.Automation.DotNetAdapter.ConstructorInvokeDotNet(Type type, ConstructorInfo[] constructors, Object[] arguments)
at Microsoft.PowerShell.Commands.NewObjectCommand.CallConstructor(Type type, ConstructorInfo[] constructors, Object[] args)
This signalR source code object seems to be the problem, I just don't see what part of it can be throwing this error.
Question:
Why does the error mention Newtonsoft.Json v6.0.0 when signalR dependencies say >=6.0.4, and I have 10.0.2?
Am I doing anything wrong in my Powershell script which could be causing this?
Thank you very much! Any help is appreciated at this point
I managed to solve this issue with some help from a colleague. Sharing the solution here in case anyone ever struggles on the same problem.
It appears that one of SignalR dependencies tries to load an old version of Newtonsoft.Json. We can force it to redirect him to our own instance of Newtonsoft.Json
Inspired by this gist, here is the idea :
When you load your Json Assembly, store it in a variable
$newtonsoftAssembly = [System.Reflection.Assembly]::LoadFrom($dllFolder + "\Newtonsoft.Json.dll")
Afterwards, setup the redirect bindings. My best guess is that this intercepts any call to load an assembly, giving us the opportunity to return our own Json assembly instead of letting him fail to find the version he wants (6.0.0 in my case).
function RedirectJsonBindings()
{
$onAssemblyResolveEventHandler = [System.ResolveEventHandler] {
param($sender, $e)
# You can make this condition more or less version specific as suits your requirements
if ($e.Name.StartsWith("Newtonsoft.Json")) {
Write-Host "Newtonsoft assembly" $e.Name -ForegroundColor DarkGreen
return $newtonsoftAssembly
}
foreach($assembly in [System.AppDomain]::CurrentDomain.GetAssemblies()) {
if ($assembly.FullName -eq $e.Name) {
return $assembly
}
}
return $null
}
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolveEventHandler)
}
And finally, at the end of your script, unbind
# Detach the event handler (not detaching can lead to stack overflow issues when closing PS)
[System.AppDomain]::CurrentDomain.remove_AssemblyResolve($onAssemblyResolveEventHandler)

C#, Get DLLs referenced by Activator.CreateInstance(assemblyType) object

I have a console app that resides in C:\MyApp.
I have several libraries that are NOT referenced by the app. I use an Activator.CreateInstance() to use them. They reside in C:\MyLibrary\Job001, C:\MyLibrary\Job002, etc. Each of these libraries have multiple dependencies and can be different versions of dependencies already found in the main app.
When I try to run this I am seeing this error: Could not load file or assembly 'Persistence.Database, Version=1.7.2.67, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. This is one of the common dependencies for most jobs. I checked the directory and it does exist with the library.
How do I activate the library AND have it use the references as found in it's own directory?
I am using the following (extension) code to activate a library:
public static IJob ConcreteJob(this JobInfoPayload src)
{
if (src.AssemblyFile.IsNullOrEmpty())
throw new Exception("AssemblyFile cannot be empty or null!");
if (src.AssemblyName.IsNullOrEmpty())
throw new Exception("AssemblyName cannot be empty or null!");
try
{
var assembly = Assembly.LoadFile(src.AssemblyFile);
var assemblyType = assembly.GetType(src.AssemblyName);
var job = Activator.CreateInstance(assemblyType) as IJob;
return job;
}
catch (Exception ex)
{
Serilog.Log.Logger.Fatal(ex, "JOB was not able to be created!!");
throw; // bubble this up to the top...
}
}
I am looking at system.appdomain.assemblyresolve but am not making sense of how to use this in the library project.
Thoughts?
ADDITIONAL INFO (29 NOV 2016)
Server App References:
Library.Infrastructure
QueueApp.Core
Hangfire
OWIN
Job Library References:
Library.Infrastructure
Library.Persistence
Library.SQL.Database01
Library.SQL.Database02
QueueApp.Job.Core
EntityFramework
We have several Jobs that follow the same pattern BUT can be built with different versions of the Job Library References. This is due to a slow creep over time. If a job written last year is still working why would we take the time to open up that solution, update all the references, recompile, then spend a month going back through QA and acceptance when we can just leave it alone?
The challenge I am running into is the JOB cannot find the referenced files, expecting them to be in the Server App directory. Instead, they are in that Job's directory. Using Fuslogvw.exe just confirms that it is NOT looking in the DLL's directory but rather the hosting app's directory.
** I currently get the same behavior whether I use Assembly.LoadFrom() or Assembly.LoadFile().
FUSLOGVW log results:
*** Assembly Binder Log Entry (11/29/2016 # 10:20:21 AM) ***
The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.
Assembly manager loaded from: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable D:\Dev\QueueApp\Source\QueueApp\bin\Debug\QueueApp.exe
--- A detailed error log follows.
=== Pre-bind state information ===
LOG: DisplayName = Job.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (Fully-specified)
LOG: Appbase = file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = QueueApp.exe
Calling assembly : Job.AgileExport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: D:\Dev\QueueApp\Source\QueueApp\bin\Debug\QueueApp.exe.Config
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core.DLL.
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core/Job.Core.DLL.
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core.EXE.
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core/Job.Core.EXE.
LOG: All probing URLs attempted and failed.
The APP is looking for all files in:
D:\Dev\QueueApp\Source\QueueApp\bin\Debug
The JOB exists in:
D:\Dev\QueueApp\Source\Job.AgileExport\bin\Debug
I think there are two solutions available.
One solution is to create a new AppDomain to host your dynamically loaded assembly. When you create a new AppDomain you have the option of providing a settings object for the AppDomain, and in that object you get to provide the paths that AppDomain will use to resolve assemblies. You can't alter the paths in your existing AppDomain because it already exists.
Another solution is to handle your current AppDomain's AssemblyResolve event, which will be raised in the case that the normal assembly resolution fails. You can then take custom steps to help resolve the assembly.
There is a feature/bug in .NET where handling this event is required when .NET is hosted in various containers (such as IE, COM+, and more) and BinaryFormatter is used to deserialize types that should be available, but actually aren't found.
I have an example of hooking and resolving the AssemblyResolve event here:
https://github.com/MarimerLLC/csla/blob/V1-5-x/cslacs10/NetRun/Launcher.cs
In your case you can probably alter my ResolveEventHandler method to look for the "missing" assemblies in the folder where you originally loaded the dynamic assembly.
Using Assembly.LoadFrom, it is not possible to load multiple versions of the same assembly, in the same AppDomain.
Thus, if Job001 needs LibraryA, 1.0.0.0 (and can't use newer version at runtime) and Job002 needs LibraryA, 2.0.0.0, you'll have to load Job001 and Job002 each in its own AppDomain.
Notice that the order in which you dynamically load assemblies is very important:
When you load Job001 it will automatically load LibraryA, 1.0.0.0 if it finds it, and if you load Job002 after that, it won't be able to load LibraryA, 2.0.0.0 and LibraryA, 1.0.0.0 will remain in the domain.
Likewise, When you load Job002 it will automatically load LibraryA, 2.0.0.0 if it finds it, and if you load Job001 after that, it won't be able to load LibraryA, 1.0.0.0 and LibraryA, 2.0.0.0 will remain in the domain.
You best bet is to either use Assembly.LoadFile + AppDomain.AssemblyResolve to load the dependencies yourself (and then you can have multiple versions of the same assembly in the same AppDomain), or you create a separate AppDomain for each JobXXX assembly, and let the dependencies be loaded automatically.
This is what I came up with so far. These classes are in the main server app, not found in any of the JOBs. We have several different types of JOBs, Ad Hoc being one of the types. By placing the code in the base class, all JOB handlers now inherit it.
public class JobAdHocHandler : BaseHandler, IJobHandler
{
public MinimumResultModel Handle(MinimumCommandModel message)
{
var result = new MinimumResultModel {Id = "-1", PayloadAsString = message.FullPayloadString};
try
{
var info = message.MinimumPayload.JobInfo;
SetupInstance(info); // <<-- SOLUTION (in BaseHandler)
var job = JobHandler.GetJob(info); // <<-- SOLUTION (in BaseHandler)
result.Id = BackgroundJob.Enqueue(() => job.Execute(null, message.FullPayloadString, JobCancellationToken.Null));
}
catch (Exception ex)
{
Log.Logger.Fatal(ex, ex.Message);
result.Exception = ex;
}
AppDomain.Unload(JobAppDomain);
return result;
}
public bool AppliesTo(JobType jobType) => jobType == JobType.AdHoc;
}
public class BaseHandler : MarshalByRefObject
{
protected internal AppDomain JobAppDomain;
protected internal BaseHandler JobHandler;
protected internal void SetupInstance(JobInfoPayload info)
{
var ads = new AppDomainSetup
{
ApplicationBase = new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName,
DisallowBindingRedirects = false,
DisallowCodeDownload = true,
PrivateBinPath = info.JobClassName,
ApplicationName = info.JobName,
};
JobAppDomain = AppDomain.CreateDomain(info.JobName, null, ads);
JobHandler = (BaseHandler)JobAppDomain.CreateInstanceAndUnwrap(typeof(BaseHandler).Assembly.FullName, typeof(BaseHandler).FullName);
}
protected internal IJob GetJob(JobInfoPayload info)
{
var assembly = Assembly.LoadFrom(info.JobClassName + #"\" + info.JobClassName + ".dll");
var assemblyType = assembly.GetType(info.AssemblyName);
var job = Activator.CreateInstance(assemblyType) as IJob;
if (job == null)
throw new Exception("Unable to create job: " + info.JobClassName);
return job;
}
}
Seems to work well so far.

.NET Code Contracts - ProjectA.exe cannot find ProjectB.Contracts.dll when both are in same solution

I have a C# solution in VS2013, with the Code Contracts extension installed. In my solution I have an application project (ProjectA) and a class library project (ProjectB). ProjectA references ProjectB, and most of ProjectB's public members have contracts associated with them.
I can access most of ProjectB's members from ProjectA without issue, but on one property I am getting a FileNotFoundException, with the message "Could not load file or assembly 'ProjectB.Contracts, Version=....' or one of its dependencies."
Here is what the fusion log has to say:
=== Pre-bind state information ===
LOG: DisplayName = [SolutionName].ProjectB.Contracts, Version=1.0.5953.23121, Culture=neutral, PublicKeyToken=null (Fully-specified)
LOG: Appbase = [SolutionDir]/[SolutionName].ProjectA/bin/Debug/
LOG: Initial PrivatePath = NULL Calling assembly : [SolutionName].ProjectC, Version=1.0.5953.23122, Culture=neutral, PublicKeyToken=null.
xxx
LOG: This bind starts in default load context.
LOG: Using application configuration file: [MyDocuments][SolutionName][SolutionName].ProjectA\bin\Debug[SolutionName].ProjectA.vshost.exe.config
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL
[SolutionDir]/[SolutionName].ProjectA/bin/Debug/[SolutionName].ProjectB.Contracts.DLL.
LOG: Attempting download of new URL
[SolutionDir]/[SolutionName].ProjectA/bin/Debug/[SolutionName].ProjectB.Contracts/[SolutionName].ProjectB.Contracts.DLL.
LOG: Attempting download of new URL
[SolutionDir]/[SolutionName].ProjectA/bin/Debug/[SolutionName].ProjectB.Contracts.EXE.
LOG: Attempting download of new URL
[SolutionDir]/[SolutionName].ProjectA/bin/Debug/[SolutionName].ProjectB.Contracts/[SolutionName].ProjectB.Contracts.EXE.
The only references in this solution are standard Framework libraries, other projects in this solution, and one other first-party library (which only references System.dll). I've tried cleaning and rebuilding all projects in this solution, as well as the other first-party library.
I am confused for several reasons.
In the output window, I can see that all of ProjectA and ProjectB's references (and their references) have already been loaded.
Other seemingly similar methods and properties with similar contracts are not causing any issues. Also, I've been using Code Contracts for months in several solutions and I haven't run into this yet.
Shouldn't the contract code already be recompiled into ProjectB.dll? Why does ProjectA even need to know that there is a .Contracts library? (Other than for intellisense and static analysis)
Does anyone know what might be going on here?
Edit: All projects in solution are targeting .NET 4.0 and "Any CPU".
Here are a few things I tried that didn't work:
On the project settings for ProjectA, on the Code Contracts tab, I tried adding ...ProjectB\bin\Debug\CodeContracts to the field Extra Contract Library Paths. This did not change anything.
I also tried manually adding a reference to ProjectB.Contacts.dll. This turned my FileNotFoundException into a MissingMethodException. This made me think the issue was perhaps due to an error by the Code Contracts rewriter. Enter ILSpy.
The contract that was causing the issue was basically this in the original source code:
Contract.Requires<ArgumentNullException>(collection.All(x => x != null));
But I had put it in a contract abbreviator method like this:
public static class PreCondition {
[ContractAbbreviator]
public static void NotIsOrHasNull<T>(IEnumerable<T> collection){
Contract.Requires<ArgumentNullException(collection != null,
"Collection cannot be null.");
Contract.Requires<ArgumentNullException>(collection.All(x => x != null),
"Collection cannot contain null.");
}
}
and called it in a class like this:
public class Class1{
public void DoStuff(IEnumerable<T> collection){
PreCondition.NotIsOrHasNull(collection);
foreach (var x in collection){
//Stuff
}
}
}
When viewing the troublesome method call in ILSpy, I saw it was trying to call an instance method named Class1.NotIsOrHasNull, instead of the static method PreCondition.NotIsOrHasNull.
The Solution:
I changed the PreCondition class to move the LINQ expression into its own Pure function, and change the contract abbreviator to call that method.
public static class PreCondition {
[Pure]
public static Boolean CollectionContainsNull(IEnumerable<T> collection){
Contract.Requires<ArgumentNullException(collection != null);
if (typeof(T).IsValueType)
return false;
return collection.Any(x => Object.Equals(x, null));
}
[ContractAbbreviator]
public static void NotIsOrHasNull<T>(IEnumerable<T> collection){
Contract.Requires<ArgumentNullException(collection != null,
"Collection cannot be null.");
Contract.Requires<ArgumentNullException>(!CollectionContainsNull(collection)),
"Collection cannot contain null.");
}
}
Now the method call in Class1 works and the IL looks correct in ILSpy.
+1 to ILSpy!

Categories