In my experience, web.config files are widely reviled. In particular, I have found them difficult to manage when you have multiple environments to support, and fiddly to update due to the lack of validation at update-time and the verbosity of XML.
What are the alternatives?
I personally don't mind Web.Config for small one-off applications, but for anything substantial I avoid using them for application configuration.
Here's what I do...
I define one or more interfaces for my configuration depending on the complexity.
I create different implementations per environment (dev, stage, prod, etc.)
I use an abstract base class to define common configuration.
I then use Ninject for dependency injection so the appropriate implementation is provided depending on which environment I am targeting.
I always code to the configuration interfaces and benefit from compile time checks.
Here's an example...
// Config Contract
public interface IWebAppConfig
{
string SmtpHost { get; }
string RootUrl { get; }
}
// Define Common Config Values (values that don't change per environment)
public abstract class AbstractWebAppConfig : IWebAppConfig
{
public string SmtpHost { get { return "smtp.google.com"; } }
public abstract RootUrl { get; }
}
// Dev Config Settings
public class DevWebAppConfig : AbstractWebAppConfig
{
public override string RootUrl { get { return "http://localhost:1322"; } }
}
// Stage Config Settings
public class StageWebAppConfig : AbstractWebAppConfig
{
public override string RootUrl { get { return "http://stage.mysite.com"; } }
}
// Prod Config Settings
public class ProdWebAppConfig : AbstractWebAppConfig
{
public override string RootUrl { get { return "http://www.mysite.com"; } }
}
Advantages of this approach:
Type Safe
Configuration is represented as objects not key value pairs (useful for passing logical groupings of config values instead of multiple values)
Easier to Unit Test classes that are dependent on configuration values
Sharing configuration across multiple apps is trivial
Deploying the assembly that contains the configuration implementations will trigger a recycle of the application pool, much like re-deploying a web.config.
You may still use the web.config to define the environment, which is what I usually do by adding the following to the appSettings:
<appSettings>
<!-- accepts: dev|stage|prod -->
<add key="Env" value="dev" />
</appSettings>
Alternatively, it could be machine based by using Envrionment Variables, or some other construct.
How long has ASP.NET been in existence; how many production web sites are using it?
They're almost all using web.config, as it comes, out of the box. They can't be "reviling" it too much.
That said, look at the new features of ASP.NET in .NET 4.0, including configuration-specific web.config files, and xml-based transformations of web.config that permit environment-specific web.config versions to be generated at deployment time.
You may revile that a little less.
I strongly disagree with your assertion that web.config files are "widely reviled." In my experience, they're easy to maintain and manage, and in some cases are the only place you can put configuration data.
It's worth noting that VS2010 supports per-build configuration web.config transforms. I have a web.config, web.debug.config, and web.release.config. The debug and release files override the connection strings specified in web.config and replace them with the correct strings, for my debug and production SQL Servers, specifically. I'm also using this to make some AppSettings values config-specific.
Since you can add a build config for as many different targets or configurations as you require, I don't see why you'd feel the need to reinvent the wheel by devising another repository.
That said, you can use just about any repository for whatever data you want to store. A database, text config file of the format of your choice, encrypted image, or what have you.
Related
I have a code library I have written which can be utilized in both desktop applications and on a web server. That library sometimes needs to know which environment it's running in.
In the past I have relied on System.Web.Hosting.HostingEnvironment.IsHosted to tell if the code is running on a web server. Unfortunately asp.net core mvc does not have access to the System.Web namespace so I need another mechanism.
How can the code tell if it's running on a web server if one of those possibilities is asp.net core mvc?
Answering my own question in case it helps others.
It's been mentioned that one way to determine whether the code is running on a web server or desktop app is to look at the name of the process it's running in. This is definitely possible, but I have little control over what the process name is for a web application and the name is likely to change in the future if history is any indication.
So instead, I chose to make the determination based on the application's config file name. This file name is different for web apps and desktop apps and it's something that's more under my control as a developer.
The method I wrote to do this is:
public bool IsWebServer {
get {
string file = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile.ToLower();
//web.config used by asp.net 4.X,
//app.config used by MVC Core
//NameOfTheApp.exe.config used by desktop applications
if(file.Contains("web.config") || file.Contains("app.config")) {
return true;
}
return false;
}
}
Unfortunately, there is no currently equivalent API. When you look at how that property works, you can easily do something equivalent yourself--without making assumptions about which server is hosting your application.
You'll have to set up your API with a static public property:
namespace My.Project
{
public static HostingEnvironment
{
public static bool IsHosted { get; private set; }
public static void SetIsHosted(this IServicesCollection services)
{
// you can grab any other info from your services collection
// if you want. This is an extension method that you call
// from your Startup.ConfigureServices method
IsHosted = true;
}
}
}
So now you have something that works with both ASP.Net MVC 5 and 4.5. You would integrate it in your Startup.ConfigureServices() method.
public void ConfigureServices(IServiceCollection services)
{
// Set up whatever services you want here.
// Make sure you have your My.Project namespace
// in your using statements so you can use the IsHosted()
// extension method
services.SetIsHosted();
}
The only difference between this solution and the legacy System.Web.Hosting.HostingEnvironment.IsHosted solution is that the flag was set by the framework when the application was started by IIS automatically. This is probably as close of an equivalent solution as you are going to get, while still allowing the ability to host anywhere.
I have a working robolectric and want to test a component of my application that does HTTP request. Since I don't want these requests to go to my live server but instead to a local test server I want to override a string resources (that contains the servers hostname) during testing.
However, I'm not capable of finding anything in the robolectric documentation that goes remotely in the direction I want :(
I've faced a similar issue in Robolectric 3; you can override a resource at application level using Mockito partial mocks.
First, you tell Robolectric to used a partially mocked Application and to return that when the application context is used: (thanks to this answer: https://stackoverflow.com/a/31386831/327648)
RuntimeEnvironment.application = spy(RuntimeEnvironment.application);
when(RuntimeEnvironment.application.getApplicationContext())
.thenReturn(RuntimeEnvironment.application);
Then you partially mock the Resources object:
Resources spiedResources = spy(app.getResources());
when(app.getResources())
.thenReturn(spiedResources);
Then you can do the real override:
when(spiedResources.getString(R.string.server_address))
.thenReturn("local server address");
I hope this helps.
You can use the technique mentioned at http://robolectric.blogspot.com/2013/04/the-test-lifecycle-in-20.html
This will allow you to override getResources() and use spying to return a hardcoded String or (by default) the String loaded from res/values:
#Override
public Resources getResources() {
Resources resources = spy(super.getResources());
when(resources.getString(R.string.server_address)).thenReturn("local test server address");
return resources;
}
I have a class library used in windows (WPF) and web (ASP.NET) context. The library has settings that are rather user-scoped, if used in windows context and that have to be application-scoped, if used in web context. I would like to declare them as user-scoped and use the default values as “pseudo-application-scoped”. My problem is, that web applications ban user-scoped settings by throwing a configuration error.
What is the best way to handle this, preferably within the frameworks configuration system?
Ok, facing the unavoidable. There are no user-scoped settings in ASP.NET. Class libraries used in mixed client / server environments shouldn’t use them!
Here is the solution I ended with:
For the usage in a server environment (ASP.NET) all settings of class libraries have to be application-scoped.
I introduce a second set of settings for the usage in client environments (WPF). These settings have property names and types identical to the class libraries property names and types, but they can differ in scope and they are located in my main assembly.
With a small piece of code I extend the libraries setting class to inject the user-scoped settings.
namespace ClassLibrary.Properties
{
using System.Configuration;
public sealed partial class Settings {
private ApplicationSettingsBase injectedSettings;
public void InjectSettings(ApplicationSettingsBase settings)
{
injectedSettings = settings;
}
public override object this[string propertyName]
{
get
{
if (injectedSettings != null)
return injectedSettings[propertyName];
return base[propertyName];
}
set
{
base[propertyName] = value;
}
}
}
}
With this extension I can inject the second settings class into the library settings:
ClassLibrary.Properties.Default.InjectSettings(Application.Properties.Settings.Default);
Have a look at the SettingsProvider class by sub classing it you could create a compatible provider for a ASP.NET environment.
It might make changing the current code quite easy because you can keep the current code but have to add a different provider when in ASP.NET.
This gives a nice overview as well
I have a Global.asx file that needs to do custom authentication, auditing and profiling stuff. This is needed because it supports a SAML based SSO system and needs to override the normal .Net authentication (which doesn't support either SAML or mixed authentication)
I don't want to fire it for static files, such as .js, .css, .png, etc
In Cassini/WebDev and IIS7 it does.
What I want to have is some simple check, like a this.Request.IsStaticFile (which doesn't exist, unfortunately) to identify the static files.
I realise that this would be fairly simple to write, but it feels like something that must already exist - IIS has already applied caching policy stuff for the static files and so on.
I need a code solution, rather than an IIS config change one.
Update
This is my current workaround:
/// <summary>Hold all the extensions we treat as static</summary>
static HashSet<string> allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".js", ".css", ".png", ...
};
/// <summary>Is this a request for a static file?</summary>
/// <param name="request">The HTTP request instance to extend.</param>
/// <returns>True if the request is for a static file on disk, false otherwise.</returns>
public static bool IsStaticFile(this HttpRequest request)
{
string fileOnDisk = request.PhysicalPath;
if (string.IsNullOrEmpty(fileOnDisk))
{
return false;
}
string extension = Path.GetExtension(fileOnDisk);
return allowedExtensions.Contains(extension);
}
This works and is quick enough, but feels horribly clunky. In particular relying on extensions is going to be error prone if we add new static files not thought of.
Is there a better way without changing the IIS config?
You might be able to check which handler is dealing with the request.
In IIS6 only .net files, eg aspx are mapped to a handler that does stuff.
In IIS7 with the integrated pipeline, everything routes through .net, which is normally a good thing. Different handlers still deal with different file types though. In particular I believe the staticfilehandler is the one you need to check for. The httpcontext.handler property should allow you to figure it out.
You could create an extension method to add that IsStatic method...
Simon
There are a few options:
Adding authorization element and deny none for those paths that you do not need any authentication and contains your static files
You are using integrated pipeline. Turn it off on your IIS 7.
There is no doubt that you need to create a custom extension method because ASP.NET routing engine uses this code to decide whether a file exist,
if (!this.RouteExistingFiles)
{
string appRelativeCurrentExecutionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (((appRelativeCurrentExecutionFilePath != "~/") && (this._vpp != null)) && (this._vpp.FileExists(appRelativeCurrentExecutionFilePath) || this._vpp.DirectoryExists(appRelativeCurrentExecutionFilePath)))
{
return null;
}
}
You will not able to decide whether the request is static in Application_BeginRequest using context.handler because Routing Module may change the handler and this module always execute after Application_BeginRequest. My suggestion is to use the similar code which ASP.NEt routing engine uses.
I am currently in a dev only phase of development, and am using the VS built-in web server, configured for a fixed port number. I am using the following code, in my MembershipService class, to build an email body with a confirmation link, but obviously this must change when I deploy to our prod host.
var url = string.Format("http://localhost:59927/Account/CompleteRegistration/{0}", newMember.PendingReplyId);
How can I build this URL to always reflect the host that the code is running on, e.g. when deployed to prod the URL should be http://our-live-domain.com/Account/..etc.
MORE INFO: This URL will is included in an email to a new user busy registering an account, so I cannot use a relative URL.
Have a setting for this in your web.config
Like this:
<appSettings>
<add key="BaseURL" value="http://localhost:59927/"/>
</appSettings>
Access the value from the code. If you store multiple values in the appSettings and use them all over your project, I'd avise to use a wrapper class.
public class AppSettingsWrapper
{
public static String BaseURL
{
get { return System.Configuration.ConfigurationManager.AppSettings["BaseURL"].ToString(); }
}
// you can also insert other values here, that need to be cast into a specific datatype
public static int DefaultPageID
{
get { return int.Parse(System.Configuration.ConfigurationManager.AppSettings["DefaultPageID"].ToString()); }
}
}
You can assemble your string like this:
String url = string.Format("{0}{1}", AppSettingsWrapper.BaseURL, ResolveUrl(String.Format("~/Account/CompleteRegistration/{0}", newMember.PendingReplyId)));
Upon deployment, you need to replace the settings from the appSettings section. You can do this by using web config transforms. Have a look at this article http://www.tomot.de/en-us/article/5/asp.net/how-to-use-web.config-transforms-to-replace-appsettings-and-connectionstrings, which shows you how to this. You would create solution configurations for your testserver and your production server
use appSettings section in web.conf it will allow you to configure setting for production server.
and use ConfigurationManager class for acces to appSetting section.
While you can always set the host name and port as a setting which can then be read at run time (Very useful if the machine you have has multiple host headers, which might be in the case of load balancing). You can work out the Url from the following components :
Request["SCRIPT_NAME"] eg "/default.aspx"
Request["SERVER_NAME"] eg "localhost"
Request["SERVER_PORT"] eg "80"
Hope that this helps.
Jonathan
new Uri(
Request.Url, // base URI from current context
"/Account/CompleteRegistration/1234" // address relative to the base URI, use / if needed
).ToString();
This results in http://your.server/Account/CompleteRegistration/1234 .
It works great for relative links, even if our current location is not root:
new Uri(
Request.Url, // we are at http://server/app/subfolder/page.aspx?q=1
"page2.aspx"
).ToString(); //produces http://server/app/subfolder/page2.aspx
BTW, since it's of System.Uri type (unlike Request.RawUrl which is a relative path string), it has tons of useful properties, but typically you will just use .ToString().
Although you can't use ~ (tilde) paths directly, it's very simple to resolve them, and you should do it when in doubt:
new Uri(
Request.Url,
Page.ResolveUrl("~/folder/test") // use this! tilde is your friend!!!
).ToString(); // this will always point to our app even if it's in a virtual folder instead of root