views:

179

answers:

9

Question: How to make code behave differently when running locally vs. in QA vs. in production?

Example: In an ASP.NET MVC application I have a controller set up to deliver e-mail notifications. When running locally on a development machine I want the e-mails delivered to the developer, when in QA I don't want any e-mail notifications going out and in Production I want the notifications going out to their intended recipients

Thanks

A: 

I did this on a recent project. My solution is fairly involved, but in a nutshell there are two Web.config keys that control this: EmailTestMode and EmailEnabled. If EmailTestMode is on, messages are generated but sent to a specific address rather than their intended recipient. If EmailEnabled is off, messages are logged but not sent.

I went to the trouble of building a Messenger class that manages these items for me - I just call a method with the various attributes of the message, and it figures out whether and where to send it. I also have a separate configuration block in Web.config that contains all of the system messages. That way, the sender, recipient, subject and body can be easily modified from a configuration file. In most cases the body is either generated by the app or uses String.Format() to fill in values.

GalacticCowboy
+4  A: 

Have three different web.configs and add an AppSetting which tells you where you are so you can determine if you should send an email.

You could also define constants in your web.config using the CompilerOptions attribute:

<system.codedom>
  <compilers>
    <compiler language="c#;cs;csharp" extension=".cs"
      type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0,
      Culture=neutral, PublicKeyToken=b77a5c561934e089"
      compilerOptions="/d:Test"/>
  </compilers>
</system.codedom>

And in your code use

#if !Test
    SendMail();
#endif
Yuriy Faktorovich
I like the compiler options idea. :)
GalacticCowboy
Thanks Yuiry. The only problem I can see with the compilerOptions option is that it won't work in medium trust environments. This isn't a problem with me in my particular application and this proved to most straightforward way to do what I needed. I do think that James' solution below is equally valid and is something I considered as well.
wgpubs
+4  A: 

This sounds like something you should do using an IoC. I typically use StructureMap which would allow me to setup different profiles. Then all I would have to do is have a web.config switch configured to set the appropriate environment profile.

For example, in StructureMap you could do the following:

ObjectFactory.Initialize( x => {
    x.CreateProfile( "Development", p =>
    {
        p.Type<IEmailProvider>().Is.OfConcreteType<DeveloperEmailProvider>();
    } );

    x.CreateProfile( "QA", p =>
    {
        p.Type<IEmailProvider>().Is.OfConcreteType<NullEmailProvider>();
    } );

    x.CreateProfile( "Production", p =>
    {
        p.Type<IEmailProvider>().Is.OfConcreteType<ProductionEmailProvider>();
    } );

} );

ObjectFactory.Profile = ConfigurationSettings.AppSettings["Profile"];
jamesaharvey
Seems like a counterintuitive use of patterns.
Yuriy Faktorovich
Care to explain why you believe this is counterintuitive?
jamesaharvey
IoC is intended for making it easier to drop in alternative implementations of required services, so I think its totally appropriate/intuitive.
Frank Schwieterman
Right, thanks for backing me up Frank!
jamesaharvey
That's BTW exactly how I use IoC - my web.config has a setting that tells my App which NinjectModule to load, and then I have a module for Dev and for Prod: http://www.stum.de/2009/12/30/loading-a-type-specified-in-web-config-for-example-a-ninject-module/
Michael Stum
Thanks James! I think both you and Yuriy present good solutions but was able to only mark one as correct (maybe stackoverflow will change that one day).
wgpubs
+2  A: 

For lightweight projects, I just use an AppSetting value, and then for all emails, pass the recipient address through the following message:

public static MailAddress MailTo(string email)
{
    if (Boolean.Parse(ConfigurationManager.AppSettings["RedirectEmails"]))
    {
        return new MailAddress(ConfigurationManager.AppSettings["DebugMailbox"]);
    }

    return new MailAddress(email);
}

Our larger projects use NAnt build scripts, which use template config files to generate a different configuration for different build targets (so you have a Web.Config.template file, which is merged with either a local.properties, test.properties or release.properties XML file containing the relevant variables).

Keith Williams
+1  A: 

I would configure the web.config differently for the environments of how System.Net.Mail sends out the emails. Take a look at Scott Gu's blog post about it. For Development, I'd have it drop the emails on the server somewhere. For QA, have it not send anything anywhere, and for Production have it configured to use the normal SMTP server.

Agent_9191
A: 

Like others have said, use different Application Settings in your web.config. Your runtime code can then use the correct version of your settings.

Another cool way of doing this is to use Conditional Attributes to build and call debug versions of your methods.

kervin
+1  A: 

I think the IoC answer is a good general solution. For the specific case of emails being sent directly to an SMTP server, you could instead use the config solution here: http://stackoverflow.com/questions/567765/how-can-i-save-an-email-instead-of-sending-when-using-smtpclient. The config solution is quick and cheap, especially if the dev team is not big on IoC.

I've seen projects though where there is an intermediate service on another machine that does the email processing. In this case the config solution does not work.

Frank Schwieterman
A: 

I use my version control software to do this for me. Basically I have multiple web.config files for each of my environments (dev, test, qa, prod). Now in the version control software I tag all the files for the correct environment. So when I need to build the qa evnvironment I get all the files tagged "QA" and so on.

Joe
A: 

The way we do it is to have two config keys in our machine.config

ProductionServers="PROD_SERVER"
TestServers="LOCAL_MACHINE|TEST_SERVER"

Then we have a function that tests machine name(System.Environment.MachineName) vs those values. That way we never have to change any configs on the servers and when we want to point at prod instead of test we just change our local machine.config.

Jeff Keslinke