locked
ConfigManager not retrieving my connection string RRS feed

  • Question

  • User-1699898665 posted

    Hi folks,

    This is a follow on from this thread

    Jon did whatever he did and simply moved on without explanation.  I'd like to understand those actions better, otherwise the thread is useless as a source of information....and lets face it, Microsoft aren't helping us any with this kind of stuff.

    So, I have the same scenario and the same web.config in Views folder.  My root web.config is :

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      For more information on how to configure your ASP.NET application, please visit
      https://go.microsoft.com/fwlink/?LinkId=301880
      -->
    <configuration>
      <appSettings>
        <add key="webpages:Version" value="3.0.0.0" />
        <add key="webpages:Enabled" value="false" />
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
      </appSettings>
      <system.web>
        <compilation debug="true" targetFramework="4.7.2" />
        <httpRuntime targetFramework="4.7.2" />
      </system.web>
      <connectionStrings>
        <add name="defaultConnection" connectionString="Data Source=Z400\SQLEXPRESS;Initial Catalog=RelstionshipManagement;Trusted_Connection=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
      </connectionStrings>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />
            <bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
            <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
      <system.codedom>
        <compilers>
          <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
          <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
        </compilers>
      </system.codedom>
    </configuration>

    and the one in the Views folder is:

    <?xml version="1.0"?>

    <configuration>
      <configSections>
        <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
          <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
          <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
        </sectionGroup>
      </configSections>
      
      <system.web.webPages.razor>
        <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <pages pageBaseType="System.Web.Mvc.WebViewPage">
          <namespaces>
            <add namespace="System.Web.Mvc" />
            <add namespace="System.Web.Mvc.Ajax" />
            <add namespace="System.Web.Mvc.Html" />
            <add namespace="System.Web.Optimization"/>
            <add namespace="System.Web.Routing" />
            <add namespace="RelationshipServiceWebProject" />
          </namespaces>
        </pages>
      </system.web.webPages.razor>

      <appSettings>
        <add key="webpages:Enabled" value="false" />
      </appSettings>

      <system.webServer>
        <handlers>
          <remove name="BlockViewHandler"/>
          <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
        </handlers>
      </system.webServer>

      <system.web>
        <compilation>
          <assemblies>
            <add assembly="System.Web.Mvc, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
          </assemblies>
        </compilation>
      </system.web>
    </configuration>

    Looking at this thread its clear that both files are required.

    And according to the MS documentation my web.config should overide the machine.config setting.  But it isn't.  Any ideas why?

    Kind regards, Paul

    Monday, November 23, 2020 10:45 AM

Answers

  • User475983607 posted

    Typically a unit test exercises business logic that operates on data.  The data is usually mocked which allows generate very specific tests.

    I would simply pass the connection string directly to the data service constructor.  

        public class DataService
        {
            public string DefaultConnectionString { get; }
    
            public DataService() : this (ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString)
            {
            }
            public DataService(string ConnectionString)
            {
                DefaultConnectionString = ConnectionString;
            }
        }
    public ActionResult Index()
    {
        DataService service = new DataService();
        return Content(service.DefaultConnectionString);
    }
    public ActionResult Index()
    {
        DataService service = new DataService(ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString);
        return Content(service.DefaultConnectionString);
    }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, November 23, 2020 2:24 PM

All replies

  • User-1699898665 posted

    P.S.  I've just tried adding <Clear/> before my connection string to force it to ignore the machine.config and that didn't work.

    Monday, November 23, 2020 10:48 AM
  • User475983607 posted

    It seems Jon in the other thread placed the connection string in the web.config file within the Views folder and not the application root.

    Example.

    <configuration>
      <configSections>
        <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
        <section name="entityFramework"
          type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
          requirePermission="false"/>
      </configSections>
      <connectionStrings>
        <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-MvcIdentity.mdf;Initial Catalog=aspnet-MvcIdentity;Integrated Security=True"
          providerName="System.Data.SqlClient" />
      </connectionStrings>
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                string conn = ConfigurationManager.ConnectionStrings["DefaultConnection"].ToString();
                return Content(conn);
            }

    In the future, please share the actual C# code that returns null.

    Monday, November 23, 2020 11:01 AM
  • User-1699898665 posted

    Thanks.  I forgot to mention I had tried that as well, with no success.  Have just tried it again with the <clear/> added and that's still not working.

    The usage is

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Configuration;
    using Microsoft.Data.SqlClient;
    using System.Data;
    
    namespace RelationshipServiceWebProject.Data
    {
        public abstract class DataService
        {
            protected string DefaultConnectionString { get; set; }
            protected SqlConnection DefaultConnection { get; set; }
    
            public DataService()
            {
                DefaultConnectionString = ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString;
    
                DefaultConnection = new SqlConnection(DefaultConnectionString);
            }

    ?

    Monday, November 23, 2020 11:16 AM
  • User475983607 posted

    So the plot thickens.  You shared an abstract class (partially) but not the implementation.  I created a working test below.  I'm guessing there is something wrong with your design that we cannot see.

    Keep in mind the configuration manager has worked for many many years.  It's not the configuration manager or the web.config.  

    namespace MvcIdentity.Services
    {
        public abstract class DataService
        {
            protected string DefaultConnectionString { get; set; }
            protected SqlConnection DefaultConnection { get; set; }
    
            public DataService()
            {
                DefaultConnectionString = ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString;
    
                DefaultConnection = new SqlConnection(DefaultConnectionString);
            }
        }
    
        public class MyDataService : DataService
        {
            public string GetConnectionString()
            {
                return DefaultConnectionString;
            }
            
        }
    }
            public ActionResult Index()
            {
                MyDataService service = new MyDataService();
                string conn = service.GetConnectionString();
                return Content(conn);
            }

    Monday, November 23, 2020 11:44 AM
  • User-1699898665 posted

    So, the context that matters is that in the first instance the failure was happening as a result of a unit test:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using System.Collections.Generic;
    using RelationshipServiceWebProject.Data;
    using RelationshipServiceWebProject.Models.Context;
    
    namespace RelationshipServiceWebProject.Tests.Data
    {
        [TestClass]
        public class ContextDataServiceTests
        {
            [TestMethod]
            public void Constructor()
            {
                ContextDataService dataService = new ContextDataService();
    
                Assert.IsNotNull(dataService);
            }
        }
    }

    I now appreciate that when this unit test calls the data service it doesn't set up the http context and therefore web.config doesn't exist.

    But when I ran the application with the connection string section in it's original location I got an error saying that section was incorrect.  Having looked at a very old application from when I last used MVC5 (5 years ago) I moved the section to the start of web.config:

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      For more information on how to configure your ASP.NET application, please visit
      https://go.microsoft.com/fwlink/?LinkId=301880
      -->
    <configuration>
      <connectionStrings>
        <clear/>
        <add name="defaultConnection" connectionString="Data Source=Z400\SQLEXPRESS;Initial Catalog=RelstionshipManagement;Trusted_Connection=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
      </connectionStrings>
      <appSettings>
        <add key="webpages:Version" value="3.0.0.0" />
        <add key="webpages:Enabled" value="false" />
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
      </appSettings>

    and added a little test instantiation in a controller...and that works fine.  I have no idea why the position matters.

    I'm back to the drawing board with the unit test though....or the design of the data service.  I'm trying out test driven development with this project.

    Kind regards, Paul

    Monday, November 23, 2020 12:10 PM
  • User475983607 posted

    And the plot thickens further.  Now there are unit tests in the mix.   A unit test is it's own application and has it's own app.config file.  Or you can create a short cut to the web.config file in the unit test.

    I have no idea why the position matters.

    Position does not matter.  The connections node just needs to be within the configuration node.

        <connectionStrings>
        <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-MvcIdentity.mdf;Initial Catalog=aspnet-MvcIdentity;Integrated Security=True"
          providerName="System.Data.SqlClient" />
      </connectionStrings>
    </configuration>

    Monday, November 23, 2020 12:18 PM
  • User-1699898665 posted

    What the...I moved the connection string section about again and it's working now, regardless of where I put it.  Which is what I expected, but not what originally happened.  Oh well, it's working now.

    Not sure if it's a unit test or a sort of integration test.  Anyway, I've refactored the test and the design of the data service.  Its going to take a while to get used to all this SOLID business and for completeness sake I referenced this article and this thread to come up with these:

        public abstract class DataService
        {
            protected string DefaultConnectionString { get; set; }
            protected SqlConnection DefaultConnection { get; set; }
    
            public DataService(IConnectionStringService connectionStringService)
            {
                DefaultConnectionString = connectionStringService.DefaultConnectionString;
    
                DefaultConnection = new SqlConnection(DefaultConnectionString);
            }

    which uses

    using System.Configuration;
    
    namespace RelationshipServiceWebProject.Data
    {
        public interface IConnectionStringService
        {
            string DefaultConnectionString { get; }
        }
        public class ConnectionStringService : IConnectionStringService
        {
            public string DefaultConnectionString => ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString;
        }
    }

    It does mean I have to instantiate the interface every time I use the data service:

        public class BusinessAreaController : Controller
        {
            // GET: BusinessArea
            public ActionResult BusinessAreaList()
            {
                ContextDataService contextDataService = new ContextDataService(new ConnectionStringService());
    
                return View();
            }
        }

    which I was hoping to avoid...but the test works fine now

        [TestClass]
        public class ContextDataServiceTests
        {
            [TestMethod]
            public void Constructor()
            {
                ContextDataService dataService = new ContextDataService(new ConnectionStringTestService());
    
                Assert.IsNotNull(dataService);
            }

    with ConnectionStringTestService picking up the connection string from the test project app.config.

    It's an expensive business this SOLID programming....if indeed this is SOLID...that's a lot more code than the original for the sake of a test.  I guess I'll get used to it...it'll be interesting to see if my new paymasters are willing to accept this cost.

    Kind regards, Paul

    Monday, November 23, 2020 1:54 PM
  • User475983607 posted

    Typically a unit test exercises business logic that operates on data.  The data is usually mocked which allows generate very specific tests.

    I would simply pass the connection string directly to the data service constructor.  

        public class DataService
        {
            public string DefaultConnectionString { get; }
    
            public DataService() : this (ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString)
            {
            }
            public DataService(string ConnectionString)
            {
                DefaultConnectionString = ConnectionString;
            }
        }
    public ActionResult Index()
    {
        DataService service = new DataService();
        return Content(service.DefaultConnectionString);
    }
    public ActionResult Index()
    {
        DataService service = new DataService(ConfigurationManager.ConnectionStrings["defaultConnection"].ConnectionString);
        return Content(service.DefaultConnectionString);
    }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, November 23, 2020 2:24 PM