Entity Framework Core Connection String - Environment Variable - c#

I have a .net core app using entity framework core. When running entity framework migrations or updates via the command line, I get a "value cannot be null. Parameter name: connectionString"
The connection string is kept as an environment variable:
ConnectionStrings__SomeContextConnection ...(localdb)\\MSSQLLocalDB...
However, when I move that exact same connection string into a .json config file:
"ConnectionStrings": {
"SomeContextConnection": "...(localdb)\\MSSQLLocalDB..."
}
Then the entity framework tools recognize the connection string without issue.
When debugging the code, in Startup.cs:
var connectionString = _config.GetConnectionString("SomeContextConnection");
the connectionString variable is set to the correct string when the string is stored in either of the two locations, but it crashes when trying to connect to the database when using the environment var.
(note: in the environment variable case, the connection string is escaped so goes from
(localdb)\\MSSQLLocalDB...
to
(localdb)\\\\MSSQLLocalDB...
but the issue persists even after removing the extra back-slashes)
UPDATE:
When the connection string is moved into a Windows level environment variable, it works fine. Seems to only be an issue when using Visual Studio environment variables.

It works if you remove the double backslash after (localdb) so that there's only one backslash.
So once you've corrected that in the environment variable it should look something like this:
Server=(localdb)\MSSQLLocalDB...

I suggest you use a DesignTimeContextFactory class for the migrations:
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyContext>
{
AmpContext IDesignTimeDbContextFactory<MyContext>.CreateDbContext(string[] args)
{
var connectionString = ConfigHelper.GetConnectionString();
var optionsBuilder = new DbContextOptionsBuilder<MyContext>();
optionsBuilder.UseSqlServer(connectionString);
return new AmpContext(optionsBuilder.Options);
}
}
the GetConnectionstring for me is like this and I use it throughout my application (that is for my Web API project and for integration tests):
public class ConfigHelper
{
/// <summary>
/// Gets the connectionstring from the appsettings.databasestring.json file in the solution root if there is no environment variable to be found
/// </summary>
/// <param name="solutionBasePath">Optional to not auto resolve the solution base path</param>
/// <returns></returns>
public static string GetConnectionString(string solutionBasePath = null)
{
//how to set it on IIS on the server: https://stackoverflow.com/a/36836533/1343595
var environmentString = Environment.GetEnvironmentVariable("CUSTOMCONNSTR_MyContextDb");
if (!string.IsNullOrEmpty(environmentString))
return environmentString;
if(!string.IsNullOrEmpty(solutionBasePath))
return GetStringFromConfig(Path.Combine(solutionBasePath, "appsettings.databasestring.json"));
var filePath = Path.Combine(GetSolutionBasePath(), "appsettings.databasestring.json");
return GetStringFromConfig(filePath);
}
private static string GetStringFromConfig(string filePath)
{
IConfigurationRoot config = new ConfigurationBuilder()
.AddJsonFile(filePath) //you can change the value of the connectionstring in the appsettings file and add it to gitignore so the change will not effect others
.Build();
var connectionString = config.GetConnectionString("MyContextDb");
return connectionString;
}
/// <summary>
/// Gets the current soution base path
/// </summary>
/// <returns></returns>
public static string GetSolutionBasePath()
{
var appPath = PlatformServices.Default.Application.ApplicationBasePath;
var binPosition = appPath.IndexOf("\\bin", StringComparison.Ordinal);
var basePath = appPath.Remove(binPosition);
var backslashPosition = basePath.LastIndexOf("\\", StringComparison.Ordinal);
basePath = basePath.Remove(backslashPosition);
return basePath;
}
}

Related

C# Path not recognized or utilized when invoking File.AppendAllText

I’m trying to use the File class to work with a text file in a console and winforms desktop app and getting the following exception:
The type initializer for '_Library.Logging' threw an exception
From what I’ve read here this error is typically caused by a problem in App.config for Winfoms apps but the Exception details seem to point elsewhere:
System.ArgumentNullException: Value cannot be null. Parameter name: path
at System.IO.File.AppendAllText(String path, String contents)
The MSDN examples for file manipulation all hard code the path parameter without any reference to using an App.confiig file so my presumption was it is possible to do this without involving ConfigurationManager.
This is the code I'm trying to use
// in calling method
class Program_Console
{
private static StringBuilder SB4log = new StringBuilder();
public static void Main(string[] tsArgs)
{
// Conditionals dealing with argumentts from Task Scheduler
Save2Log("Calling _UI.exe via \"Process.Start()\"", true);
// try-catch-finally for Process.Start
}
private static void Save2Log(string msgTxt, bool noTS)
{
SB4log.AppendLine($"{msgTxt}");
if (noTS) Logging.SaveLog(SB4log);
else Logging.SaveLog_TimeStamp(SB4log);
SB4log.Clear();
}
}
// saving app progression messages to a single log txt file
public static class Logging
{
private static String filePath = Connections.LogPath();
private static StringBuilder SB4log = new StringBuilder();
public static void SaveLog(StringBuilder logTxt)
{
File.AppendAllText(filePath, logTxt.ToString());
logTxt.Clear();
}
}
// class for DB connection and file paths
public static class Connections
{
private static StringBuilder SB4log = new StringBuilder();
public static string AppPath()
{
string appRoot;
try
{
string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
SaveLog($"->App Path: {appDir}", true); // culprit
int loc = appDir.IndexOf("BURS");
appRoot = appDir.Substring(0, loc + 5);
}
catch (Exception ex)
{
// Exception response and MessageBox
}
return appRoot;
}
public static string ConsolePath() {}
public static string UIPath() {}
public static string LogPath()
{
string appRoot = AppPath();
string wrkDir = #"_Library\Data\BURS_Log.Txt";
string fullDir = $"{appRoot}{wrkDir}";
SaveLog($"->Log Path: {fullDir}\n", true); // culprit
return fullDir;
}
}
In stepping through the code the code the variable containing the path -- filePath -- has the expected value: "D:\BURS_Library\Data\BURS_Log.Txt" (quotes used to show there re no unintended spaces needing to be trimmed). Acordinng to MSDN if it's a malformed path an exception will be thrown but the path looks valid to me.
Why isn’t the Path variable utilized?
Any help will be appreciated.
Edit: expanded code to show start-to-finish flow since the original abridged version seemed to be confusing. Have added the text "//culprit" to the two lines which caused the error as pointed out by the responders.
It's not clear what Connections is, but given Connections.LogPath(); it seems that you're calling LogPath(); to set the value for filePath which is a problem because that calls AppPath which has the following statement SaveLog($"->App Path: {appDir}", true);.
You haven't included a version of SaveLog that has 2 parameters, but assuming it's similar to the one you've posted, you're attempting to use filePath when the value hasn't been set yet - which causes an issue.

Is there a way to programmatically check pending model changes in Entity Framework Core?

I am currently in the progress of setting up a team environment for ASP.NET Core WebAPI development, using xUnit for unit tests in combination with GitLab CI. For database communication, we use EF Core.
For EF Core we are going to use Code First Migrations and we are worried that a developer might only update the model and not also create a migration for their model change.
Thus, we want our CI to run all migrations that exist in the codebase, compare them with the current state of the code first model and fail when the code first model state is not equal to the state that results from running all the migrations.
Is there a way to do this? I cannot find anything about this in the EF Core documentation.
For EF Core 6, from #ErikEJ's excellent EF Core Power Tools:
var migrationsAssembly = _ctx.GetService<IMigrationsAssembly>();
var hasDifferences = false;
if (migrationsAssembly.ModelSnapshot != null) {
var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;
if (snapshotModel is IMutableModel mutableModel) {
snapshotModel = mutableModel.FinalizeModel();
}
snapshotModel = _ctx.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
hasDifferences = _ctx.GetService<IMigrationsModelDiffer>().HasDifferences(
snapshotModel.GetRelationalModel(),
_ctx.GetService<IDesignTimeModel>().Model.GetRelationalModel());
}
https://github.com/ErikEJ/EFCorePowerTools/blob/5a16c37c59be854605f3e81d3131011d96c96704/src/GUI/efpt30.core/EFCoreMigrationsBuilder.cs#L98
If you are using EF (core) 5 you'll need a slightly different version (also adapted from #ErikEJ sample code)
[Fact]
public void ModelDoesNotContainPendingChanges()
{
// Do not use the test database, the SQL Server model provider must be
// used as that is the model provider that is used for scaffolding migrations.
using var ctx = new DataContext(
new DbContextOptionsBuilder<DataContext>()
.UseNpgsql(DummyConnectionString)
.Options);
var modelDiffer = ctx.GetService<IMigrationsModelDiffer>();
var migrationsAssembly = ctx.GetService<IMigrationsAssembly>();
var dependencies = ctx.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = ctx.GetService<RelationalConventionSetBuilderDependencies>();
var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)migrationsAssembly.ModelSnapshot.Model).Builder, null);
var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(migrationsAssembly.ModelSnapshot.Model);
var finalSourceModel = ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel();
var finalTargetModel = ctx.Model.GetRelationalModel();
var hasDifferences = modelDiffer.HasDifferences(finalSourceModel, finalTargetModel);
if(hasDifferences)
{
var changes = modelDiffer.GetDifferences(finalSourceModel, finalTargetModel);
Assert.True(false, $"{changes.Count} changes between migrations and model. Debug this test for more details");
}
Assert.False( hasDifferences );
}
Thanks to the example code from #ErikEJ, I was able to write the following test that does exactly what I want:
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Xunit;
/// <summary>
/// Contains a test that verifies that the
/// model does not contain any changes that are not included
/// in the migrations.
/// </summary>
public class NoPendingModelChangesTest
{
private static readonly string DummyConnectionString = #"Server=localhost;Database=DoesNotExist;Trusted_Connection=True;";
/// <summary>
/// Tests that the current model does not contain any changes
/// that are not contained in the database migrators.
/// In other words: tests that the current model state equals the
/// state that results from all the migrations combined.
/// </summary>
[Fact]
public void ModelDoesNotContainPendingChanges()
{
// Do not use the test database, the SQL Server model provider must be
// used as that is the model provider that is used for scaffolding migrations.
using var ctx = new MyDatabase(
new DbContextOptionsBuilder<MyDatabase>()
.UseSqlServer(DummyConnectionString)
.Options);
var modelDiffer = ctx.GetService<IMigrationsModelDiffer>();
var migrationsAssembly = ctx.GetService<IMigrationsAssembly>();
var pendingModelChanges = modelDiffer
.GetDifferences(
migrationsAssembly.ModelSnapshot?.Model,
ctx.Model);
pendingModelChanges
.Should()
.BeEmpty(
because:
"the current model state should be equal to the state that results from all the migrations combined (try scaffolding a migration)");
}
}

How to get value from applicationSettings?

I am trying to get value of service in my application from app.config. I have to send it to the application which shows the URL. A web service which I am consuming in this aplication also using it so can not move it to appSettings.
I want to get this value 'http://192.168.4.22:82/Service.asmx' through c# code.
<applicationSettings>
<SDHSServer.Properties.Settings>
<setting name="DOServer_WebReference1_Service" serializeAs="String">
<value>http://192.168.4.22:82/Service.asmx</value>
</setting>
</SDHSServer.Properties.Settings>
</applicationSettings>
Not sure i get the question,
string s = SDHSServer.Properties.Settings.DOServer_WebReference1_Service;
will get you it
If I understand you correctly you have two Visual Studio C# projects. The first (project A) has a setting you want to access in the second (project B). To do that you have to perform the following steps:
Add a reference from project B to project A
Change the access modifier of the settings i project A to public (default is internal)
Now you can access the setting in project B, in your case using the fully qualified name SDHSServer.Properties.Settings.Default.DOServer_WebReference1_Service
Note that in the settings editor you can set a value for the setting. This is the default value for the setting and this value is also stored in the App.config file for the project. However, you can override this value by providing another value in the App.config file for the application executing.
In this example, the App.config file for project A will contain the value for the setting which is http://192.168.4.22:82/Service.asmx. However, you can override this in the App.config file for project B to get another value. That is probably not what you want to do but you should be aware of this.
I use this code in a ASP.Net 4.0 site to pull section data out of the 'applicationsetting' section:
public sealed class SiteSupport {
/// <summary>
/// Retrieve specific section value from the web.config
/// </summary>
/// <param name="configSection">Main Web.config section</param>
/// <param name="subSection">Child Section{One layer down}</param>
/// <param name="innersection">Keyed on Section Name</param>
/// <param name="propertyName">Element property name</param>
/// <returns></returns>
/// <example>string setting = NoordWorld.Common.Utilities.SiteSupport.RetrieveApplicationSetting("applicationSettings", "NoordWorld.ServiceSite.Properties.Settings", "ServiceWS_SrvWebReference_Service", "value")</example>
public static string RetrieveApplicationSetting(string configSection, string subSection, string innersection, string propertyName) {
string result = string.Empty;
HttpWorkerRequest fakeWorkerRequest = null;
try {
using (TextWriter textWriter = new StringWriter()) {
fakeWorkerRequest = new SimpleWorkerRequest("default.aspx", "", textWriter);
var fakeHTTPContext = new HttpContext(fakeWorkerRequest);
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap() { ExeConfigFilename = fakeHTTPContext.Server.MapPath(#"~/Web.config") }, ConfigurationUserLevel.None);
ConfigurationSectionGroup group = config.SectionGroups[configSection];
if (group != null) {
ClientSettingsSection clientSection = group.Sections[subSection] as ClientSettingsSection;
if (clientSection != null) {
SettingElement settingElement = clientSection.Settings.Get(innersection);
if (settingElement != null) {
result = (((SettingValueElement)(settingElement.ElementInformation.Properties[propertyName].Value)).ValueXml).InnerText;
}
}
}
}
} catch (Exception ex) {
throw ex;
} finally {
fakeWorkerRequest.CloseConnection();
}
return result;
}
}
https://www.ServiceWS.com/webservices/Golf
Depends something like this.
var s = SDHSServer.Properties.Settings.Default.DOServer_WebReference1_Service;
or
var s = SDHSServer.Properties.Settings.DOServer_WebReference1_Service;

How To Read UnitTest Project's App.Config From Test With HostType("Moles")

I have the folowing tests:
[TestClass]
public class GeneralTest
{
[TestMethod]
public void VerifyAppDomainHasConfigurationSettings()
{
string value = ConfigurationManager.AppSettings["TestValue"];
Assert.IsFalse(String.IsNullOrEmpty(value), "No App.Config found.");
}
[TestMethod]
[HostType("Moles")]
public void VerifyAppDomainHasConfigurationSettingsMoles()
{
string value = ConfigurationManager.AppSettings["TestValue"];
Assert.IsFalse(String.IsNullOrEmpty(value), "No App.Config found.");
}
}
The only difference between them is [HostType("Moles")]. But the first passes and the second fails. How can I read App.config from the second test?
Or may be I can add some another config file in other place?
Assuming you are trying to access values in appSettings, how about just adding the configuration at the beginning of your test. Something like:
ConfigurationManager.AppSettings["Key"] = "Value";
Then when your test tries to read the AppSettings "Key", "Value" will be returned.
You just add your "App.Config" file to the unit test project . It will read automatically.
See http://social.msdn.microsoft.com/Forums/en/pex/thread/9b4b9ec5-582c-41e8-8b9c-1bb9457ba3f6
In the mean time, as a work around, you could try adding the configuration settings to Microsoft.Moles.VsHost.x86.exe.config
[ClassInitialize]
public static void MyClassInitialize(TestContext testContext)
{
System.Configuration.Moles.MConfigurationManager.GetSectionString =
(string configurationName) =>
{
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
Assembly assembly = Assembly.GetExecutingAssembly();
fileMap.ExeConfigFilename = assembly.Location + ".config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
object section = config.GetSection(configurationName);
if (section is DefaultSection)
{
ConfigurationSection configurationSection = (ConfigurationSection) section;
Type sectionType = Type.GetType(configurationSection.SectionInformation.Type);
if (sectionType != null)
{
IConfigurationSectionHandler sectionHandler =
(IConfigurationSectionHandler)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(sectionType.Assembly.FullName, sectionType.FullName);
section =
sectionHandler.Create(
configurationSection.SectionInformation.GetParentSection(),
null,
XElement.Parse(configurationSection.SectionInformation.GetRawXml()).ToXmlNode());
}
}
return section;
};
}
I ran across this issue at work and didn't like any of these answers. I also have the problem that the configuration file is being read in a static constructor which means I can't Mole ConfigurationManager before the static constructor is executed.
I tried this on my home computer and found that the configuration file was being read correctly. It turns out I was using Pex 0.94.51006.1 at home. This is slightly older than the current one. I was able to find a download for the older academic version:
http://research.microsoft.com/en-us/downloads/d2279651-851f-4d7a-bf05-16fd7eb26559/default.aspx
I installed this on my work computer and everything is working perfectly. At this point, I'm downgrading to the older version until a newer working version is released.
This is what I am using to get the correct AppConfig and ConnectionString sections:
var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(Reflection.Assembly.GetExecutingAssembly().Location);
typeof(Configuration.ConfigurationElementCollection).GetField("bReadOnly", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(System.Configuration.ConfigurationManager.ConnectionStrings, false);
foreach (Configuration.ConnectionStringSettings conn in config.ConnectionStrings.ConnectionStrings)
System.Configuration.ConfigurationManager.ConnectionStrings.Add(conn);
foreach (Configuration.KeyValueConfigurationElement conf in config.AppSettings.Settings)
System.Configuration.ConfigurationManager.AppSettings(conf.Key) = conf.Value;
Saw the ConnectionString part here

Accessing the Settings of another application

I have application 1 and application 2. App2 needs to verify that App1 is installed, and if it is it needs to access a property from the App1 Settings.
What would be the best way to go about this?
UPDATE
First, my apologies for never accepting an answer to this, I know it's over a year old now, but I got sidetracked immediately after asking this and then the project was changed, blah blah blah. Mea culpa...
I'm back on it now and I still need to solve this problem, but now the applications are deployed via ClickOnce, so I don't actually know where they are located. Any suggestions would be appreciated. I promise I'll select an answer this time.
The docs for ConfigurationManager.OpenExeConfiguration have an example of reading the .config file of another exe and accessing the AppSettings. Here it is:
// Get the application path.
string exePath = System.IO.Path.Combine(
Environment.CurrentDirectory, "ConfigurationManager.exe");
// Get the configuration file.
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(exePath);
// Get the AppSetins section.
AppSettingsSection appSettingSection = config.AppSettings;
As far as checking that App1 is installed, you could write a value in the Registry during installation and check it in App2 (and remove the value during uninstall).
This is a pain, I can tell you that much. I've found that the best way to do this is that you serialize the Settingsclass and use XML(code below). But try this page first:
http://cf-bill.blogspot.com/2007/10/visual-studio-sharing-one-file-between.html
public class Settings
{
public static string ConfigFile{get{return "Config.XML";}}
public string Property1 { get; set; }
/// <summary>
/// Saves the settings to the Xml-file
/// </summary>
public void Save()
{
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
using (TextWriter reader = new StreamWriter(ConfigFile))
{
serializer.Serialize(reader, this);
}
}
/// <summary>
/// Reloads the settings from the Xml-file
/// </summary>
/// <returns>Settings loaded from file</returns>
public static Settings Load()
{
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
using (TextReader reader = new StreamReader(ConfigFile))
{
return serializer.Deserialize(reader) as Settings;
}
}
}

Categories