I have a small ASP.NET web application hosted in an integration test (executing within NUnit). My product code can usually find configuration data from the web.config or app.config file, but for some reason when hosting ASP.NET I seem to get an ArgumentException when executing the first of these commands:
var configuration = ConfigurationManager.OpenExeConfiguration(null);
return configuration.GetSectionGroup(SectionGroupName);
exePath must be specified when not running inside a stand alone exe.
I don't know what to put here. There isn't a sensible exePath for my product to ever pass into this method as a parameter as it usually runs within a web server. Also, ordinary Sections (not SectionGroups) can typically be opened using:
ConfigurationManager.GetSection(SectionName)
even within unit tests this works, where an App.config file somehow magically gets read. That's what I'd like when reading SectionGroups.
Any ideas?
Inside the web application, try using WebConfigurationManager. You will need a mechanism to detect if you are in a web context or exe context and use some design pattern to switch between contexts.
A simple way to do this is check if HttpContext.Current is null (not null indicates web context and a null value indicates a exe context).
IMO, something like this should work,
Configuration configuration;
if (HttpContext.Current == null)
configuration = ConfigurationManager.OpenExeConfiguration(null); // whatever you are doing currently
else
configuration = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath); //this should do the trick
configuration.GetSectionGroup(sectionGroupName);
It will be more complex if you don't want the dependency on the System.web dll
I haven't tested it.
ConfigurationManager.GetSection("SectionGroupName/GroupName")
i.e. e.g.
<configSections>
<sectionGroup name="RFERL.Mvc" type="System.Configuration.ConfigurationSectionGroup, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<section name="routes" type="RFERL.Mvc.Configuration.RoutesConfigurationSection, RFERL.Mvc"/>
</sectionGroup>
</configSections>
&
var config = ConfigurationManager.GetSection("RFERL.Mvc/routes") as RoutesConfigurationSection;
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
return config.GetSectionGroup(SectionGroupName);
should work in asp.net.
Posting in case this helps anyone. I used this answer to be able to read an Umbraco configuration.
private static string CdnUrl()
{
// Unit test vs web environment
Configuration configuration;
if (HttpContext.Current == null)
{
configuration = ConfigurationManager.OpenExeConfiguration(null);
}
else
{
configuration = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
}
// Grab Umbraco config
ConfigurationSectionGroup umbracoConfiguration = configuration.GetSectionGroup("umbracoConfiguration");
FileSystemProvidersSection fileSystemProviders = (FileSystemProvidersSection)umbracoConfiguration.Sections.Get("FileSystemProviders");
// Return the information needed
var cdnUrl = fileSystemProviders.Providers["media"].Parameters["rootUrl"].Value;
return cdnUrl;
}
Related
I have a mvc 3 project and I need to populate sql database with data from xml file. So I added the console app project to the solution and wrote the code that will display all needed data on the screen. Now I want to write data into the database. Here is the chunk of code: (fom the console app)
public static void Main()
{
IKernel ninjectKernel = new StandardKernel();
ninjectKernel.Bind<IItemsRepository>().To<ItemsRepository>();
ninjectKernel.Bind<IProductRepository>().To<ProductRepository>();
ninjectKernel.Bind<IShippingRepository>().To<ShippingRepository>();
var itemsRepository = ninjectKernel.Get<IItemsRepository>(); // redirection to datacontext file
var productRepository = ninjectKernel.Get<IProductRepository>();
var shippingRepository = ninjectKernel.Get<IShippingRepository>();
var doc = XDocument.Load(#"C:\div_kid.xml");
var offers = doc.Descendants("offer");
foreach (var offer in offers)
{ // here I use Linq to XML to get all needed data from xml file:
// name, description, price, category, shippingCost, shippingDetails
Product product = productRepository.CreateProduct(name, description, price, category, "Not Specified", "Not Specified");
Shipping shipping = shippingRepository.CreateShipping(shippingCost, shippingDetails);
// here I think I will just create "admin" user and assign its "UserId" to "userId"
Guid? userId = null;
Item item = itemsRepository.CreateItem(product.ProductId, shipping.ShippingId, (Guid) userId, DateTime.Now);
// Resharper highlights this line like unreachable. Why?
Console.WriteLine("name: {0}", offer.Element("name").Value);
}
}
First of all when I run the console app the NullReferenceException occures in MvcProjectName.Designer.cs file in the following line:
public WebStoreDataContext() :
base(global::System.Configuration.ConfigurationManager.ConnectionStrings["WebStoreConnectionString"].ConnectionString, mappingSource)
NullReferenceException: The reference to the object is not pointing to the object instance.
So, I have lots of questions:
1) How to integrate console app code with mvc 3 app code in one solution?
2) I've also found this post on stackoverflow.
But can't I just add reference to MvcProject in references of ConsoleProject? And this way get access to the "mvc repositories" code?
3) Should I use ninject container in console app?
4) Is there any better implementation of loading data from xml file into slq database? I've never had two projects in one solution before, so mabby there are other ways to beautifully handle this situation?
Thanks for Your help in advance!
Edits:
I added app.config file with the connection string:
<add
name="WebStoreConnectionString" connectionString="Data Source=(LocalDB)\v11.0;
AttachDbFilename=|DataDirectory|\WebStore.mdf;Integrated Security=True;Connect Timeout=30"
providerName="System.Data.SqlClient"
/>
Now when I run console app I get the following SqlException when the Linq to SQL ".submitChanges()" method is called:
An attempt to attach an auto-named database for file C:\Users\Aleksey\repos\working_copy\WebStore\LoadDataTool\bin\Debug\WebStore.mdf failed. A database with the same name exists, or specified file cannot be opened, or it is located on UNC share.
Also in the directory LoadDataTool\bin\Debug "WebStore" file with extension "Program Debug Database" appeared.
It's hard to make an accurate assumption on your solution architecture, but from what I'm reading, it doesn't sound like you've separated your Data Access Layer (DAL) from the presentation layer (MVC) - which seems to be why you're trying to referencing it in a console application.
With that assumption, here's how I would answer your questions.
I wouldn't... But if I was going to I was 1) make sure that I have all the required references, 2) validate that the app.config file is setup correctly pointing to the correct database and server.
I would separate your repositories in a different project and use a dependency resolver to inject them into your MVC Application. With that, the console application will only need to reference the DAL assembly - thus not needed all the references in your console app.
If your think that you're going to be pulling out the DAL in the future, then yes. (**See #2 suggestion).
Unless you can't run your solution without the XML file and database created, a better solution is to simply make an administration Controller and Action that allows you to upload your XML file and complete the tasks.
I hope that helps.
Update
For your issue
An attempt to attach an auto-named database for file
Change your connection string so that it looks something like;
Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\WebStore.mdf;
A good source for connection strings is:
http://www.connectionstrings.com/
I guess that you need to define a connection string in the App.config file that is used by your console application (the same way you have it in your web.config):
<connectionStrings>
<add
name="WebStoreConnectionString"
connectionString="YOUR CONNECTION STRING COMES HERE"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
I have a Web Role that is using the ASP.NET SQL Membership provider. Currently the configuration is in the Web.Config file. I would like to configure the connection string as a Web Role setting instead of having it in the Web.Config. The main reason that I wan this is so that I can set up configurations on the azure project to publish to different hosted services (dev, qa, etc.) Right now, I have to manually edit the web.config each time I want to publish to a different service.
I have a couple ideas on how I might be able to accomplish this.
Write a custom membership provider that wraps the SQL provider and provides custom configuration to it.
Put something in the Web Role OnStart method to change the connection string in the Web.config file.
Has anyone done something like this before, or have recommendations on which option might be best, or have another idea on how to accomplish this?
The Windows Azure SDK allows you to have multiple service configurations per environment. But web.config modifications are a bit harder. In your case I would suggest you write some code (or a startup task) that reads the connection string from the service configuration and writes it to the web.config.
Andy's blog post 'Programmatically modify web.config on WebRole Startup' explains exactly how you can do this:
public override bool OnStart()
{
using (var server = new ServerManager())
{
// get the site's web configuration
var siteNameFromServiceModel = "Web"; // TODO: update this site name for your site.
var siteName =
string.Format("{0}_{1}", RoleEnvironment.CurrentRoleInstance.Id, siteNameFromServiceModel);
var siteConfig = server.Sites[siteName].GetWebConfiguration();
// get the appSettings section
var appSettings = siteConfig.GetSection("appSettings").GetCollection();
AddElement(appSettings, "deploymentId", RoleEnvironment.DeploymentId);
AddElement(appSettings, "internalEndpointPort", RoleEnvironment.CurrentRoleInstance.InstanceEndpoints
.First(t=>t.Key=="InternalEndpoint1").Value
.IPEndpoint.Port.ToString());
server.CommitChanges();
}
return base.OnStart();
}
I just created a cron job like job using Quartz.net. For the test, it execute a simple request to the database. It simply adds a field.
I have a dbcontext:
private TotoContext db = new TotoContext();
In my job I have:
var totos = from u in db.totos where u.name == name select u;
Toto[] totoArray = totos.ToArray();
In my web.config, I have a special field with my specific connectionstring and so on ("TotoContext").
But when I create a new dbContext it seems that it uses doesn't use the good connectionString. In the watch the connectionString is not linked with "TotoContext".
I initialize my job in:
public override bool OnStart()
And I have a specific Web.toto.config file with the connectionString for the build.
Why it doesn't use the good connectionString ?!
Thanks a lot !
Edit: if I set manually the connectionString in my db.Database.Connection.ConnectionString, it works. But why it doesn't use the web.config ConnectionString.
If you use full IIS mode (default configuration for web role), web.config will be ignored in the role entry point. So it is recommended to put all ASP.NET specific initialization tasks in Global.asax's Application_Start method. Role entry point is used to do something before ASP.NET application starts up, for example, modify IIS configuration. Inside Global.asax, web.config (and config transform) is respected.
I just found why it's not using the Web.config: https://stackoverflow.com/a/10153375/1396323
But the next question is how to store different connectionString depending on the build config (Debug, Release etc...) and where ?
I'm creating a class library that will be used in a web application. One of the things that happens in one of the assembly classes is that it hits a database. Normally, when I do this from a service, the connection string gets extracted from the web.config file. With the class library, I'm not so sure how that's going to work the same way. Any suggestions?
Right now, I tried putting my normal config call in the assembly:
protected readonly string _utiConnStr =
System.Web.Configuration.WebConfigurationManager.ConnectionStrings["UTI"].ConnectionString;
It pukes, saying "Object reference not set to an instance of an object."
For instance if I want mail settings:
//open the webconfig
Configuration webConfig = WebConfigurationManager.OpenWebConfiguration(#"~/web.config");
// Get mail settings
mailSettings = webConfig.GetSectionGroup("system.net/mailSettings") as MailSettingsSectionGroup;
I have a WCF service that exposes a method. When a client calls this method, the following occurs:
the method does some processing
it tries to load an assembly if its already there
if the above dll isn't there, it generates C# code, compiles it using CSharpCodeProvider's CompileAssemblyFromSource api
It then loads the just generated assembly
Now the problem. The first time the method it called, it generates the assembly at point 3), and when it tries to return the assembly reference via CompilerResults.CompiledAssembly it throws a file not found exception. However, I can clearly see that the assembly has been generated at the specified location and I can open it with other applications.
If I call the method again via a client, it is able to load the assembly (it was successfully generated as a result of the previous call) and goes ahead to do the remaining set of tasks. Only when the assembly isnt there and it generates it and goes ahead to load it immediately, I get that exception. Any ideas? I have tried playing around with the web.config, changing impersonation to true/false. I have a seperate app pool to run this web application and I tried changing the identity of the app pool from local service to local system, even gave my windows logon credentials which has admin rights but no luck.
Any help would be much appreciated.
Are you sure it's generating the assembly? I have the same problem, except I can't find the generated .dll. I first suspected it was unable to write to the folder, so it now calls CreateDirectory and drops a text file to demonstrate that the folder is writeable.
Anyway, same problem, no success. Is it really the case that nobody else has had this issue??
I'm going to remote debug the server & see if I can step through Microsoft's PDBs...
-- EDIT --
No need to step through Microsoft's code. I had a look at the Errors collection of the CompilerResults and there was 1 item in there: "Metadata file 'c:\Windows\System32\aaclient.dll' could not be opened -- 'An attempt was made to load a program with an incorrect format. '"
When I get Directory.GetCurrentDirectory() to pick up the other DLLs it's usign the Windows System32 directory...
-- EDIT --
Resolved this by adding references from the executing assembly's folder:
CompilerParameters compilerParameters = new CompilerParameters
{
OutputAssembly = Path.Combine(GeneratedAssembliesFolder, string.Format("{0}.Generated.dll", typeName))
};
string executingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string[] dllFiles = Directory.GetFiles(executingDirectory, "*.dll");
compilerParameters.ReferencedAssemblies.AddRange(dllFiles.Select(f => Path.Combine(executingDirectory, f)).ToArray());
IEnumerable<string> exeFiles =Directory.GetFiles(executingDirectory, "*.exe").Where(f => !f.Contains(".vshost."));
compilerParameters.ReferencedAssemblies.AddRange(exeFiles.Select(f => Path.Combine(executingDirectory, f)).ToArray());
For greater robustness one aught to add checks for the binaries being valid managed code assemblies. This code could also be shortened by using a Linq .Union between the two GetFiles calls.
To find a suitable folder to write to:
private static string generatedAssembliesFolder;
private static string GeneratedAssembliesFolder
{
get
{
if (generatedAssembliesFolder == null)
{
string[] candidateFolders = new[]
{
Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Process),
Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.Process),
Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Machine),
Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.Machine),
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
};
foreach (string candidateFolder in candidateFolders)
{
try
{
if (!Directory.Exists(candidateFolder)) Directory.CreateDirectory(candidateFolder);
string testFileName = Path.Combine(candidateFolder, Path.GetRandomFileName());
File.WriteAllBytes(testFileName, new byte[0]);
File.Delete(testFileName);
}
catch (Exception ex)
{
continue;
}
generatedAssembliesFolder = candidateFolder;
break;
}
}
return generatedAssembliesFolder;
}
}
Thanks user1796307 for your input. I had resolved this issue, but forgot to update it. Sharing it for everyone's benefit. It was the .NET Fusion at play. It caches assembly load path and won't even try to load an assembly if a previous attempt to load from same location had failed. In other words:
if (Assembly.Load("C:\xyz.dll") == null)
{
Compile("C:\xyz.dll"); // Now the dll exists
Assembly.Load("C:\xyz.dll"); // this will still fail
}
The solution is to change it as:
if (!File.Exists("C:\xyz.dll")
{
Compile("C:\xyz.dll"); // Now the dll exists
Assembly.Load("C:\xyz.dll"); // this will now work
}