System.Data.SqLite DataProvider not found when executing Test using NUnit - c#

I am facing a problem with my UnitTest. I want to Test my Data-Access which is done using a repository based on NPoco. I have therefore written a couple of tests and the test project retrieves NUnit, NPoco, System.Data.SQLite, and some other Stuff via NuGet.
This is the app.config of the TestProject:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="RepositoryTests.Properties.Settings.ConnectionString" connectionString="Data Source=db.sqlite;Version=3" />
</connectionStrings>
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.SQLite"/>
<add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
</DbProviderFactories>
</system.data>
</configuration>
In VS, the Project builds fine. Triggering the tests in Visual Studio works as well.
Building the Projects with MSBUILD works as well. Running the tests via nunit after building them with msbuild.exe raises a Exception though:
Unable to find the requested .Net Framework Data Provider. It may not be installed.
This is only the case, when executing the tests using nunit directly (sth. like nunit-console.exe myproject.csproj /config:Release). Triggering them in VS is no problem.
Does anybody know how to solve this problem?

The problem is caused by the nunit test runner having its own app.config in which your settings are not present.
The simplest way to solve the problem is to move the configuration out of your app.config and into the code itself. Whatever you have in app.config can be done inside code.
Another solution is to move the configuration into a seperate file and then explicitly load that file's configuration using code. Just make sure that the file is copied to the output folder on build.
You could use something like:
public static class TestFactory
{
public static DatabaseFactory DbFactory { get; set; }
public static void Setup()
{
var fluentConfig = FluentMappingConfiguration.Configure(new OurMappings());
//or individual mappings
//var fluentConfig = FluentMappingConfiguration.Configure(new UserMapping(), ....);
DbFactory = DatabaseFactory.Config(x =>
{
// Load the connection string here or just use a constant in code...
x.UsingDatabase(() => new Database("connString");
x.WithFluentConfig(fluentConfig);
x.WithMapper(new Mapper());
});
}
}
See here for more details.
Then in the test fixture:
[TestFixture]
public class DbTestFixture
{
[TestFixtureSetUp]
public void Init()
{
TestFactory.Setup();
}
[Test]
public void YourTestHere()
{
var database = TestFactory.DbFactory.GetDatabase();
...
}
}

I got it working by creating an copy of the app.config-file and give that copy the name of the Test-Project, followed by .config. So if we assume, that the project is named Test.Project the copy must be named Test.Project.config. Nunit doesn't seem to load the automatically generated Test.Project.dll.config. This info can be found somewhat disguised in the NUnit-docs (Configuration Files -> Test Configuration File -> 3rd paragraph, last sentence).
In VS, in the Properties-Section of that copied file, i set the copy-to-output-directory-property to always.
Executing the tests with nunit-console.exe then lead to another exception (Bad-Image), which was caused by NUnit not finding the SQLite.Interop.dll-file. This could be solved kind of hacky by adding that file, which already resides in the x64 or x86 folder, as an existing element to the solution in VS and also setting the copy-to-output-dir-property to always.

Related

Can't read app.config in C# .NET Core unit test project with ConfigurationManager

I've created a simple unit test project to read an app.config file. Target framework is Core 2.0. I also created a Core 2.0 console app, to sanity-check myself to make sure I wasn't doing anything weird (same test passed as expected in a .NET 4.6.1 unit test project).
The console app reads the app.config fine, but the unit test method fails and I cannot figure out why. Both are using a copy of the same app.config (not added as a link) and both have the System.Configuration.ConfigurationManager v4.4.1 NuGet package installed.
The App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Test1" value ="This is test 1."/>
<add key="Test2" value ="42"/>
<add key="Test3" value ="-42"/>
<add key="Test4" value="true"/>
<add key="Test5" value="false"/>
<add key="Test6" value ="101.101"/>
<add key="Test7" value ="-1.2345"/>
</appSettings>
</configuration>
The Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Configuration;
namespace ConfigTest
{
[TestClass]
public class UnitTest1
{
[TestMethod()]
public void ConfigTest()
{
foreach (string s in ConfigurationManager.AppSettings.AllKeys)
{
System.Console.WriteLine(s);
System.Diagnostics.Debug.WriteLine(s);
}
//AllKeys.Length is 0? Should be 7...
Assert.IsTrue(ConfigurationManager.AppSettings.AllKeys.Length == 7);
}
}
}
The Console App
using System;
using System.Configuration;
namespace ConfigTestApp
{
class Program
{
static void Main(string[] args)
{
foreach (string s in ConfigurationManager.AppSettings.AllKeys)
{
Console.WriteLine(s);
System.Diagnostics.Debug.WriteLine(s);
}
//Outputs 7 as expected
Console.WriteLine(ConfigurationManager.AppSettings.AllKeys.Length);
}
}
}
Given that I'm still pretty new to the whole .NET Core world, am I doing something totally incorrect here? I sort of just feel crazy at the moment...
Looking through the github issue's comments, I found a work around that can go in the msbuild file...
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
<Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
</Target>
This makes it easier to verify existing tests under .NET Core before porting the configuration data over to json configuration files.
Edit
If running under Resharper, the previous answer doesn't work as Resharper proxies the assembly, so you need
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
<Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\ReSharperTestRunner64.dll.config" />
</Target>
If you check the result of the call to ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
It should tell you where the required configuration file should be while running unit tests for that assembly.
I found that instead of having an app.config file, ConfigurationManager was looking for a testhost.dll.config file.
This was for a project targeting netcoreapp2.1, with a reference to Microsoft.NET.Test.Sdk,NUnit 3.11 and Nunit3TestAdapter 3.12.0
.CORE 3.1
To find out what dll.config file was being used, I debugged the test by adding this line and looking to see what the value is.
string path = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
Then I found out resharper was using testhost.dll.config and VStest was using testhost.x86.dll.config. I needed to add the following lines to the project file.
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
<Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
<Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.x86.dll.config" />
</Target>
I came across the same issue with my xunit tests and solved it by using the instance of Configuration from ConfigurationManager. I put the static (normal) way it works in core, framework (but not unit tests) before I show the alternative way it works in all three:
var appSettingValFromStatic = ConfigurationManager.AppSettings["mySetting"];
var appSettingValFromInstance = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["mySetting"].Value;
And here is a similar/related issue. In case anyone needs to get a section you can do a similar thing, though the type must change in the app config:
<configSections>
<section name="customAppSettingsSection" type="System.Configuration.AppSettingsSection"/>
<section name="customNameValueSectionHandlerSection" type="System.Configuration.NameValueSectionHandler"/>
</configSections>
<customAppSettingsSection>
<add key="customKey" value="customValue" />
</customAppSettingsSection>
<customNameValueSectionHandlerSection>
<add key="customKey" value="customValue" />
</customNameValueSectionHandlerSection>
Code to grab section:
var valFromStatic = ((NameValueCollection)ConfigurationManager.GetSection("customNameValueSectionHandlerSection"))["customKey"];
var valFromInstance = ((AppSettingsSection)ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).GetSection("customAppSettingsSection")).Settings["customKey"].Value;
I feel like I am also crazy, and I know there are newer ways of doing config in core, but if one wants to do something cross-platform this is the only way I know how. I'd be very interested if anyone has alternatives
For my mixed .NET-Core & .NET-Framework project, I added the following to the unit test global setup:
#if NETCOREAPP
using System.Configuration;
using System.IO;
using System.Reflection;
#endif
...
// In your global setup:
#if NETCOREAPP
string configFile = $"{Assembly.GetExecutingAssembly().Location}.config";
string outputConfigFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
File.Copy(configFile, outputConfigFile, true);
#endif
This copies the config file to the output path testhost.dll.config but should be resilient enough to account for future changes in the testing framework.
Or you can copy to below, which amounts to the same thing:
string outputConfigFile = Path.Combine(Path.GetDirectoryName(configFile), $"{Path.GetFileName(Assembly.GetEntryAssembly().Location)}.config");
Credit to #stop-cran and #PaulHatcher's solutions, this is a combination of those two.
None of the answers given here provide a viable workaround when you're dealing with code accessing directly the static ConfigurationManager properties such as AppSettings or ConnectionStrings.
The truth is, it is not possible at the moment. You can read through the discussion here to understand why:
https://github.com/dotnet/corefx/issues/22101
There is talk to implement the support for it here:
https://github.com/Microsoft/vstest/issues/1758
In my opinion it makes sense to support this scenario since it's been working on the .NET Framework plus System.Configuration.ConfigurationManager is a .NET Standard 2.0 library now.
A hacky, but working way is to copy the config to the same folder as an entry assembly, whatever it is:
[SetUpFixture]
public class ConfigKludge
{
[OneTimeSetUp]
public void Setup() =>
File.Copy(
Assembly.GetExecutingAssembly().Location + ".config",
Assembly.GetEntryAssembly().Location + ".config",
true);
[OneTimeTearDown]
public void Teardown() =>
File.Delete(Assembly.GetEntryAssembly().Location + ".config");
}
Apart from adding this class, the only thing to make it work is to include app.config file in test project (without any copying options). It should be copied to the output folder as <your test project name>.dll.config at the build step, because it's kind of default logic.
Note the documentation for OneTimeSetUpAttribute:
Summary:
Identifies a method that is called once to perform setup before any child tests are run.
Although it should work for parallel test runs for a single project, there could be obvious troubles when running two test projects simultaneously, since the config would get overwritten.
However, it is still suitable for containerized test runs, like in Travis.
When we answer such well-researched and well-articulated question, we should better assume that it is being asked by an informed and intelligent being. And instead of patronizing them with the obvious about new, great ways of writing tonnes of boilerplate code to parse all sort of JSON et al, being forced on us and shoved down our throat by know-betters, we should focus on answering to the point.
Since the OP is already using System.Configuration to access settings, they already know how to arrive at this point. The only thing that is missing is one little touch: adding this line to the post-build event:
copy $(OutDir)<appname>.dll.config $(OutDir)testhost.dll.config
where <appname> is the project being unit-tested.
I applaud everyone who is still using (originally lame but workable) implementation of app.config because doing so protects our and our clients' investment in technology instead of reinventing the wheel. Amen.
Mercifully there is now a way to set the name of the expected configuration file at runtime. You can set the APP_CONFIG_FILE data for the current app domain.
I created the following SetUpFixture to do this automatically:
[SetUpFixture]
public class SetUpFixture
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
var testDllName = Assembly.GetAssembly(GetType())
.GetName()
.Name;
var configName = testDllName + ".dll.config";
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", configName);
}
}
The relevant GitHub discussions are:
ConfigurationManager doesn't find config file with "dotnet test" · Issue #22720 · dotnet/runtime
Provide a way to override the global configuration file path · Issue #931 · dotnet/runtime
Respect AppContext.SetData with APP_CONFIG_FILE key by krwq · Pull Request #56748 · dotnet/runtime
The ConfigurationManager API will only use the configuration of the app that is currently running. In a unit test project, this means the app.config of the test project, not the console application.
.NET Core Applications aren't supposed to use app.config or ConfigurationManager, as it is a legacy "full framework" configuration system.
Consider using Microsoft.Extensions.Configuration instead to read JSON, XML or INI configuration files. See this doc: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration
Usually in .NET Framework projects, any App.config file was copied to the bin folder by Visual Studio, with the name of your executable (myApp.exe.config) so it could be reachable in runtime. Not anymore in .NET Standard or Core Framework. You must manually copy and set the file in the bin/debug or release folder. After that it could be get with something like:
string AssemblyName = System.IO.Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase);
AppConfig = (System.Configuration.Configuration)System.Configuration.ConfigurationManager.OpenExeConfiguration(AssemblyName);
While app.config exists in the root project folder add below string to Post-build event command line
xcopy /Y $(ProjectDir)app.config $(ProjectDir)$(OutDir)testhost.dll.config*
Add the configuration file
First, add a appconfig.json file to the Integration test project
Configure the appconfig.json file to be copied to the output
directory by updating
Add NuGet package
Microsoft.Extensions.Configuration.Json
Use the configuration in your unit tests
[TestClass]
public class IntegrationTests
{
public IntegrationTests()
{
var config = new ConfigurationBuilder().AddJsonFile("appconfig.json").Build();
_numberOfPumps = Convert.ToInt32(config["NumberOfPumps"]);
_numberOfMessages = Convert.ToInt32(config["NumberOfMessages"]);
_databaseUrl = config["DatabaseUrlAddress"];
}
}

Running unit test from command prompt fails

Currently I am struggeling with some unit tests that run fine from within visual studio but fail in Teamcity
I tracked down the problem to mstests.exe
Let's say I do the following steps:
Create a new Test project
Add a new Test class with the following test
[TestMethod]
public void TestCanCreateSqLiteConnection()
{
// Create the DbProviderFactory
var factory = DbProviderFactories.GetFactory("System.Data.SQLite");
// Create the DbConnection.
var connection = factory.CreateConnection();
// Assign connection string
connection.ConnectionString = "Data Source=database.sqlite";
// check the result
Assert.IsTrue(connection.GetType().Name.Equals("SQLiteConnection"));
}
Add an app.config file and add this:
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.SQLite" />
<add name="SQLite Data Provider"
invariant="System.Data.SQLite"
description=".Net Framework Data Provider for SQLite"
type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
</DbProviderFactories>
</system.data>
Install "System.Data.SQLite (x86/x64)" via nuget
Run test from Visual Studio (2010). It should run fine:
Now I want to run the same test via mstest.exe so I:
Open a Visual Studio 2010 command prompt
Navigate to the bin\debug folder
Execute
mstest.exe /testcontainer:TestProject1.dll /detail:errormessage
The test eventually failes with
System.DllNotFoundException: Unable to load DLL 'SQLite.Interop.DLL':
The specified module could not be found. (Exception from HRESULT:0x8007007E)
Now if I extend the call to mstest.exe with testsettings the test runs fine.
mstest.exe /testcontainer:TestProject1.dll /detail:errormessage
testsettings:..\..\..\Local.testsettings
The Local.testsettings contains nothing special, even if I create a new testsettings file and use this, the test passes.
<?xml version="1.0" encoding="UTF-8"?>
<TestSettings id="fc837936-41d1-4987-8526-34f9336569f5" name="TestSettings1" enableDefaultDataCollectors="false" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>default test run</Description>
<Deployment enabled="false"/>
</TestSettings>
So the main question is, why this has an impact on my test run, and how I can run my tests form commandline without specifying a *.testsettings file.
You need to use a DeploymentItem to ensure the file is copied to the deployment directory when testing via the command line. I created a base class for all of my test classes that depend on the SQLite database.
[TestClass]
[DeploymentItem("Resources\\empty-db.sqlite", "Resources")]
[DeploymentItem("x64\\SQLite.Interop.dll", "x64")]
[DeploymentItem("x86\\SQLite.Interop.dll", "x86")]
public class SQLiteTest
{
[TestInitialize()]
public void ClearDatabase()
{
File.Copy("Resources\\empty-db.sqlite", "test-db.sqlite", true);
}
}
I encountered a similar error message a while back. If I remember correctly, the following was the crux of the issue: (it may not be 100% relevant for the OP, but it might be useful to somebody who hits this down the line).
The problem was that my unit tests all failed in Release mode with an exception complaining about the availability (or lack thereof) of the SQLite.Interop.dll. I realised that when built in Debug mode, the bin\Debug folder had 2 sub folders (x64 and x86) each with a copy of SQLite.Interop.dll but, in Release mode these files/folders did not exist.
To resolve it, I created the x64 and x86 folders in my project and added the appropriate version of SQLite.Interop.dll too them, setting the Copy to ouput setting to Copy if newer. (I had originally used 'Copy always' but it seems the MS Test engine doesn't shut down when the test run is complete - which can lock the file. Since the dll shouldn't change regularly the Copy if newer option was a suitable approach).
This enabled my unit tests to pass in release mode - but unfortunately (as in the OP's case) they did not work when run from the command line. Still trying to figure that one out - I think it's because MSTest is 32-bit and SQLite uses native code which is (maybe) 64-bit but the finer detail necessary to resolve this eludes me at present.
Two year later and it is still a pain to get SQLite working in unit tests.
Yesterday I included the current SQLite nuget package into a unit test project with <Deployment enabled="true"/> enabled and could not access sqlite by the dbproviderfactories method.
I included the SQLite interop directories with
<Deployment>
<DeploymentItem filename="packages\System.Data.SQLite.Core.1.0.98.1\build\net40\" />
</Deployment>
But that was not enough. Accessing the provider with
DbProviderFactories.GetFactory("System.Data.SQLite");
still threw an error Failed to find or load the registered .Net Data Provider error unless I made this call var factory = new System.Data.SQLite.SQLiteFactory(); After that I could acces SQLite witht the DbProviderFactories.
I included this in the ClassInitialize method so is is only executed once.
I've encountered a similar issue where the tests run through fine in Visual Studio 2013 but if run directly by MSTest many would fail. No SQL Lite used!
In the end I just added a default .testsettings file to the MSTest call and now the results are consistant.

Enable Migrations with Context in Separate Assembly?

I have one project that I want to run my update-database against but I have my Models and Context in a separate project.
If I run enable-migrations I get this error:
No context type was found in the assembly 'MyProject'.
This is presumably because my Context is in MyProject.MVC.
If I run enable-migrations against MyProject.MVC I have to add an app config file. I don't want to do that as I want to use the code across many projects.
So can I run enable-migrations against MyProject and somehow tell it to look in MyProject.MVC for the Context?
This will only work in EF 6, but there was a release that added the -ContextProjectName parameter to the -enable-migrations command. By using this command you could do the following:
enable-migrations -ContextProjectName MyProject.MVC -StartUpProjectName MyProject.MVC
-ContextTypeName MyProject.MVC.MyContextFolder.MyContextName -ProjectName MyProject
This will add migrations to your MyProject project using the context in the MyProject.MVC.
You need to make sure that the project with the Migrations has a reference to the project with your Context, i.e., MyProject references MyProject.MVC
You may only run "Enable-Migrations" in the project containing the Database Context class.
Your solution will contain 2 projects:
1) MyProject.Models
|- Migrations
|- 201401061557314_InitialCreate.cs
|- Configuration.cs
|- MyContext.cs
|- App.config (no connection string)
App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
</configuration>
2) MyProject.MVC
|- Filters
|- InitializeSimpleMembershipAttribute.cs
InitializeSimpleMembershipAttribute.cs
namespace MyProject.MVC.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext, MyProject.Model.Migrations.Configuration>());
using (var context = new MyContext())
{
context.Database.Initialize(force: true);
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
}
Set MyProject.MVC as startup project
In package manager, select project: MyProject.Models
Then run "Enable-Migrations" to create "Migrations" folder in MyProject.Models
Followed by "Update-Database" -> migrations will use connection string in Web.config from startup project to perform migration
Here is a workaround:
Add a class into MyProject(the project for migrations). Make this class inherit the dbcontext(the one in MyProject.MVC).
Then run EF migration commands on MyProject.
install Microsoft.EntityFrameworkCore.Tools to Enable Using package manager Console
I had the same problem, and I'm using EntityFramework 4.3.1. It seems that EF6 solves this issue (according to the answer by #SOfanatic) but I didn't want to upgrade to EF6 because of some breaking changes (in the DataAnnotations, for example).
So, what I did to solve this (and what I learned in the process):
Create a new solution (empty project) and add the project where you have the model you want to enable migrations for (in your case MyProject.MVC). You may need to install the required NuGet packages for it before you are able to add the existing project.
Add a config file with a connection string (don't worry, this is only to trick the migrations engine). Copy your existing database to the model project output folder (should be MVC\bin\Debug in your case). Make sure the connection string in the config file points to that database:
<connectionStrings>
<add name="MyDB" providerName="System.Data.SqlServerCe.4.0" connectionString="DataSource=|DataDirectory|\MyDB.sdf"/>
</connectionStrings>
Since you are in a new solution, set your model project as a startup project (you can remove the default project).
Run the enable-migrations command in the package manager console. It should create a Migrations folder with two files: a Configuration.cs and a timestamped InitialCreate.cs file. It's nice to have the InitialCreate, that's why you put your existing database in the model project's output folder (but this is optional).
Reload your original solution so that these changes are updated.
What I learned (as far as I understand):
The migrations engine needs something that looks like a valid connection to work. I was creating my connection string in code (in another project) and that didn't work. I just gave the Migrations engine a "valid" connection string to make it work.
Put your database where the migrations engine can find it (aka model project's output folder) so it creates a starting point for migrations. This starting point is basically your database schema written in the migrations API.
You can restore everything to your previous state once the migrations are set in place, and it works ok.
Everytime you want to manually add a migration, you must "trick" the migrations engine again, just like the first time. I haven't tried with automatic migrations, by I guess this approach works as well.
By th way, I am using a SQL Server CE 4.0 database, so some things about the connection string have a little twist compared to a standard SQL Server DB or LocalDB. Besides that, everything's the same.
Hope this is helpful and gives you some insight. Please comment if you know more about the way this migrations work.

MetadataException - Unable to load the specified metadata resource?

I have a solution with two relevant projects. The first builds My.exe, and the second builds a class library MyModel.dll that contains only my EF model.
I'm getting a MetadataException in my Model's VS-generated ObjectContext ctor. I've read through Troubleshooting Entity Framework Connection Strings, but I still haven't been able to narrow down my problem.
The offending constructor code:
public MyEntities() :
base(#"name=MyEntities", "MyEntities") // MetadataException here
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
Metadata Artifact Processing is set to EmbedInOutputAssembly. When I open MyModel.dll in Reflector, I see:
DataAccessLayer.MyModel.csdl
DataAccessLayer.MyModel.msl
DataAccessLayer.MyModel.ssdl
I've tried setting Build Action for my app.config to None and Content, and neither makes a difference. The config file contains:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="MyEntities"
connectionString="metadata=
res://*/DataAccessLayer.MyModel.csdl|
res://*/DataAccessLayer.MyModel.ssdl|
res://*/DataAccessLayer.MyModel.msl;
provider=Devart.Data.PostgreSql;
provider connection string="
User Id=MY_USER;
Password=MY_PASS;
Host=127.0.0.1;
Database=MY_DB;
Persist Security Info=True""
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
I've tried replacing the resource prefix res://*/ with both res://MyModel.dll/ and res://MyModel/, leaving the rest intact (because it matches the resources in MyModel.dll), but neither solved the problem. My class library is strong-named.
Both my executable and class library projects contain references to System.Data.Entity and Devart.Data.PostgreSql, and My.dll is being built to the same location as My.exe.
I stepped into .NET source code, and by examining the arguments & local variables of internal EF calls, found the resource assembly name was still set to *.
It turns out a bug in my application code set the model's default connection string before calling the ObjectContext ctor. It was pulled from a Settings.settings file, which hadn't been recently sync'd with the latest, correct app.config.
Some reflections on our discussion in the comments:
The correct assembly name in app.config is MyModel rather than MyModel.dll (as shown in Craig's article.) Using MyModel.dll produces an exception in the EF code.
Calling the ctor as base(#"name=MyEntities") instead of base(#"name=MyEntities", "MyEntities") actually produced a new exception in ObjectContext.GetEntitySetName()
I updated Settings.settings to be in sync with app.config, and everything is now working.

.NET Configuration (app.config/web.config/settings.settings)

I have a .NET application which has different configuration files for Debug and Release builds. E.g. the debug app.config file points to a development SQL Server which has debugging enabled and the release target points to the live SQL Server. There are also other settings, some of which are different in debug/release.
I currently use two separate configuration files (debug.app.config and release.app.config). I have a build event on the project which says if this is a release build then copy release.app.config to app.config, else copy debug.app.config to app.config.
The problem is that the application seems to get its settings from the settings.settings file, so I have to open settings.settings in Visual Studio which then prompts me that the settings have changed so I accept the changes, save settings.settings and have to rebuild to make it use the correct settings.
Is there a better/recommended/preferred method for achieving a similar effect? Or equally, have I approached this completely wrong and is there a better approach?
Any configuration that might differ across environments should be stored at the machine level, not the application level. (More info on configuration levels.)
These are the kinds of configuration elements that I typically store at the machine level:
Application settings
Connection strings
retail=true
Smtp settings
Health monitoring
Hosting environment
Machine key
When each environment (developer, integration, test, stage, live) has its own unique settings in the c:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG directory, then you can promote your application code between environments without any post-build modifications.
And obviously, the contents of the machine-level CONFIG directory get version-controlled in a different repository or a different folder structure from your app. You can make your .config files more source-control friendly through intelligent use of configSource.
I've been doing this for 7 years, on over 200 ASP.NET applications at 25+ different companies. (Not trying to brag, just want to let you know that I've never seen a situation where this approach doesn't work.)
This might help some people dealing with Settings.settings and App.config: Watch out for GenerateDefaultValueInCode attribute in the Properties pane while editing any of the values in the Settings.settings grid in Visual Studio (Visual Studio 2008 in my case).
If you set GenerateDefaultValueInCode to True (True is the default here!), the default value is compiled into the EXE (or DLL), you can find it embedded in the file when you open it in a plain text editor.
I was working on a console application and if I had defaulted in the EXE, the application always ignored the configuration file placed in the same directory! Quite a nightmare and no information about this on the whole Internet.
There is a related question here:
Improving Your Build Process
Config files come with a way to override the settings:
<appSettings file="Local.config">
Instead of checking in two files (or more), you only check in the default config file, and then on each target machine, you put a Local.config, with just the appSettings section that has the overrides for that particular machine.
If you are using config sections, the equivalent is:
configSource="Local.config"
Of course, it's a good idea to make backup copies of all the Local.config files from other machines and check them in somewhere, but not as a part of the actual solutions. Each developer puts an "ignore" on the Local.config file so it doesn't get checked in, which would overwrite everyone else's file.
(You don't actually have to call it "Local.config", that's just what I use)
From what I am reading, it sounds like you are using Visual Studio for your build process. Have you thought about using MSBuild and Nant instead?
Nant's xml syntax is a little weird but once you understand it, doing what you mentioned becomes pretty trivial.
<target name="build">
<property name="config.type" value="Release" />
<msbuild project="${filename}" target="Build" verbose="true" failonerror="true">
<property name="Configuration" value="${config.type}" />
</msbuild>
<if test="${config.type == 'Debug'}">
<copy file=${debug.app.config}" tofile="${app.config}" />
</if>
<if test="${config.type == 'Release'}">
<copy file=${release.app.config}" tofile="${app.config}" />
</if>
</target>
To me it seems that you can benefit from the Visual Studio 2005 Web Deployment Projects.
With that, you can tell it to update/modify sections of your web.config file depending on the build configuration.
Take a look at this blog entry from Scott Gu for a quick overview/sample.
We used to use Web Deployment projects but have since migrated to NAnt. Instead of branching and copying different setting files we currently embed the configuration values directly in the build script and inject them into our config files via xmlpoke tasks:
<xmlpoke
file="${stagingTarget}/web.config"
xpath="/configuration/system.web/compilation/#debug"
value="true"
/>
In either case, your config files can have whatever developer values you want and they'll work fine from within your dev environment without breaking your production systems. We've found that developers are less likely to arbitrarily change the build script variables when testing things out, so accidental misconfigurations have been rarer than with other techniques we've tried, though it's still necessary to add each var early in the process so that the dev value doesn't get pushed to prod by default.
My current employer solved this issue by first putting the dev level (debug, stage, live, etc) in the machine.config file. Then they wrote code to pick that up and use the right config file. That solved the issue with the wrong connection string after the app gets deployed.
They just recently wrote a central webservice that sends back the correct connection string from the value in the machine.config value.
Is this the best solution? Probably not, but it works for them.
One of the solutions that worked me fine was using a WebDeploymentProject.
I had 2/3 different web.config files in my site, and on publish, depending on the selected configuration mode (release/staging/etc...) I would copy over the Web.Release.config and rename it to web.config in the AfterBuild event, and delete the ones I don't need (Web.Staging.config for example).
<Target Name="AfterBuild">
<!--Web.config -->
<Copy Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " SourceFiles="$(SourceWebPhysicalPath)\Web.Release.config" DestinationFiles="$(OutputPath)\Web.config" />
<Copy Condition=" '$(Configuration)|$(Platform)' == 'Staging|AnyCPU' " SourceFiles="$(SourceWebPhysicalPath)\Web.Staging.config" DestinationFiles="$(OutputPath)\Web.config" />
<!--Delete extra files -->
<Delete Files="$(OutputPath)\Web.Release.config" />
<Delete Files="$(OutputPath)\Web.Staging.config" />
<Delete Files="#(ProjFiles)" />
</Target>
You'll find another solution here: Best way to switch configuration between Development/UAT/Prod environments in ASP.NET? which uses XSLT to transfor the web.config.
There are also some good examples on using NAnt.
Our project has the same issue where we had to maintain configs for dev, qa, uat and prod. Here is what we followed (only applies if you are familiar with MSBuild):
Use MSBuild with the MSBuild Community tasks extension. It includes the 'XmlMassUpdate' task that can 'mass-update' entries in any XML file once you give it the correct node to start with.
To Implement:
1) You need to have one config file which will have your dev env entries; this is the config file in your solution.
2) You need to have a 'Substitutions.xml' file, that contains only the entries that are DIFFERENT (appSettings and ConnectionStrings mostly) for each environment. Entries that do not change across the environment need not be put in this file. They can live in the web.config file of the solution and will not be touched by the task
3) In your build file, just call the XML mass update task and provide the right environment as a parameter.
See example below:
<!-- Actual Config File -->
<appSettings>
<add key="ApplicationName" value="NameInDev"/>
<add key="ThisDoesNotChange" value="Do not put in substitution file" />
</appSettings>
<!-- Substitutions.xml -->
<configuration xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
<substitutions>
<QA>
<appSettings>
<add xmu:key="key" key="ApplicationName" value="NameInQA"/>
</appSettings>
</QA>
<Prod>
<appSettings>
<add xmu:key="key" key="ApplicationName" value="NameInProd"/>
</appSettings>
</Prod>
</substitutions>
</configuration>
<!-- Build.xml file-->
<Target Name="UpdateConfigSections">
<XmlMassUpdate ContentFile="Path\of\copy\of\latest web.config" SubstitutionsFile="path\of\substitutionFile" ContentRoot="/configuration" SubstitutionsRoot="/configuration/substitutions/$(Environment)" />
</Target>
replace '$Environment' with 'QA' or 'Prod' based on what env. you are building for. Note that you should work on a copy of a config file and not the actual config file itself to avoid any possible non-recoverable mistakes.
Just run the build file and then move the updated config file to your deployment environment and you are done!
For a better overview, read this:
http://blogs.microsoft.co.il/blogs/dorony/archive/2008/01/18/easy-configuration-deployment-with-msbuild-and-the-xmlmassupdate-task.aspx
Like you I've also set up 'multi' app.config - eg app.configDEV, app.configTEST, app.config.LOCAL. I see some of the excellent alternatives suggested, but if you like the way it works for you, I'd add the following:
I have a
<appSettings>
<add key = "Env" value = "[Local] "/>
for each app I add this to the UI in the titlebar:
from ConfigurationManager.AppSettings.Get("Env");
I just rename the config to the one I'm targetting (I have a project with 8 apps with lots of database/wcf config against 4 evenioments). To deploy with clickonce into each I change 4 seetings in the project and go. (this I'd love to automate)
My only gotcha is to remember to 'clean all' after a change, as the old config is 'stuck' after a manual rename. (Which I think WILL fix you setting.setting issue).
I find this works really well (one day I'll get time to look at MSBuild/NAnt)
Web.config:
Web.config is needed when you want to host your application on IIS. Web.config is a mandatory config file for IIS to configure how it will behave as a reverse proxy in front of Kestrel. You have to maintain a web.config if you want to host it on IIS.
AppSetting.json:
For everything else that does not concern IIS, you use AppSetting.json.
AppSetting.json is used for Asp.Net Core hosting. ASP.NET Core uses the "ASPNETCORE_ENVIRONMENT" environment variable to determine the current environment. By default, if you run your application without setting this value, it will automatically default to the Production environment and uses "AppSetting.production.json" file. When you debug via Visual Studio it sets the environment to Development so it uses "AppSetting.json". See this website to understand how to set the hosting environment variable on Windows.
App.config:
App.config is another configuration file used by .NET which is mainly used for Windows Forms, Windows Services, Console Apps and WPF applications. When you start your Asp.Net Core hosting via console application app.config is also used.
TL;DR
The choice of the configuration file is determined by the hosting environment you choose for the service. If you are using IIS to host your service, use a Web.config file. If you are using any other hosting environment, use an App.config file.
See Configuring Services Using Configuration Files documentation
and also check out Configuration in ASP.NET Core.
It says asp.net above, so why not save your settings in the database and use a custom-cache to retrieve them?
The reason we did it because it's easier (for us) to update the continuously database than it is to get permission to continuously update production files.
Example of a Custom Cache:
public enum ConfigurationSection
{
AppSettings
}
public static class Utility
{
#region "Common.Configuration.Configurations"
private static Cache cache = System.Web.HttpRuntime.Cache;
public static String GetAppSetting(String key)
{
return GetConfigurationValue(ConfigurationSection.AppSettings, key);
}
public static String GetConfigurationValue(ConfigurationSection section, String key)
{
Configurations config = null;
if (!cache.TryGetItemFromCache<Configurations>(out config))
{
config = new Configurations();
config.List(SNCLavalin.US.Common.Enumerations.ConfigurationSection.AppSettings);
cache.AddToCache<Configurations>(config, DateTime.Now.AddMinutes(15));
}
var result = (from record in config
where record.Key == key
select record).FirstOrDefault();
return (result == null) ? null : result.Value;
}
#endregion
}
namespace Common.Configuration
{
public class Configurations : List<Configuration>
{
#region CONSTRUCTORS
public Configurations() : base()
{
initialize();
}
public Configurations(int capacity) : base(capacity)
{
initialize();
}
public Configurations(IEnumerable<Configuration> collection) : base(collection)
{
initialize();
}
#endregion
#region PROPERTIES & FIELDS
private Crud _crud; // Db-Access layer
#endregion
#region EVENTS
#endregion
#region METHODS
private void initialize()
{
_crud = new Crud(Utility.ConnectionName);
}
/// <summary>
/// Lists one-to-many records.
/// </summary>
public Configurations List(ConfigurationSection section)
{
using (DbCommand dbCommand = _crud.Db.GetStoredProcCommand("spa_LIST_MyConfiguration"))
{
_crud.Db.AddInParameter(dbCommand, "#Section", DbType.String, section.ToString());
_crud.List(dbCommand, PopulateFrom);
}
return this;
}
public void PopulateFrom(DataTable table)
{
this.Clear();
foreach (DataRow row in table.Rows)
{
Configuration instance = new Configuration();
instance.PopulateFrom(row);
this.Add(instance);
}
}
#endregion
}
public class Configuration
{
#region CONSTRUCTORS
public Configuration()
{
initialize();
}
#endregion
#region PROPERTIES & FIELDS
private Crud _crud;
public string Section { get; set; }
public string Key { get; set; }
public string Value { get; set; }
#endregion
#region EVENTS
#endregion
#region METHODS
private void initialize()
{
_crud = new Crud(Utility.ConnectionName);
Clear();
}
public void Clear()
{
this.Section = "";
this.Key = "";
this.Value = "";
}
public void PopulateFrom(DataRow row)
{
Clear();
this.Section = row["Section"].ToString();
this.Key = row["Key"].ToString();
this.Value = row["Value"].ToString();
}
#endregion
}
}

Categories