Sunday, August 30, 2015

Unit Testing (part 2) - Faking the HttpContext and HttpContextBase

This is the 2nd in a series of posts about unit testing:

Unit Testing (part 1) - Without using a mocking framework

Unit Testing (part 2) - Faking the HttpContext and HttpContextBase

Unit Testing (part 3) - Running Unit Tests & Code Coverage

Unit Testing (part 4) - Faking Entity Framework code first DbContext & DbSet

 

There are a lot of posts on the internet about faking the http context.  And when we have controllers / service functions / MVC routes that all make use of the HttpContext how do you fake it. 

This is my approach which works consistently across all my tests.  I went down a route initially of having different ways of handling a HttpContext vs a HttpContextBase.  And then I found way to have 1 approach so the tests are consistent.

 

First we need a way to fake a normal HttpContext.

    public class FakeHttpContext

    {

        public HttpContext CreateFakeHttpContext()

        {

            var httpRequest = new HttpRequest("", "http://localhost/", "");

            var stringWriter = new StringWriter();

            var httpResponce = new HttpResponse(stringWriter);

            var httpContext = new HttpContext(httpRequest, httpResponce);

            var sessionContainer = new HttpSessionStateContainer("id",

new SessionStateItemCollection(),

new HttpStaticObjectsCollection(),

10,

true,                                                    HttpCookieMode.AutoDetect,                                                    SessionStateMode.InProc, false);

            SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

            return httpContext;

        }

    }

 

Secondly we need a way to fake HttpContextBase. 

MVC already have this covered with their implementation of HttpContextWrapper.  But unfortunately even this doesn’t go quite far enough especially when you want to unit test MVC routes. But we can extend it, enter our FakeHttpContextWrapper.  This inherits from Microsoft’s HttpContextWrapper.

What this is going to do is allow us to create our own fake classes by extending the Microsoft ones (so we don’t have to reinvent the wheel).  And this will now let us over-ride the properties we need for unit testing.

We first create a FakeHttpContextWrapper which contains our FakeHttpRequestWrapper, and could also contain a FakeHttpResponseWrapper if we needed it. I’ve commented out the FakeHttpResponseWrapper as I didn’t need it but it does work if you uncomment it.

    public class FakeHttpContextWrapper : HttpContextWrapper

    {

        FakeHttpRequestWrapper _request;

        //FakeHttpResponseWrapper _response;


       
public FakeHttpContextWrapper(HttpContext httpContext)

            : base(httpContext)

        {

            _request = new FakeHttpRequestWrapper(httpContext.Request);

            //_response = new FakeHttpResponseWrapper(httpContext.Response);

        }

 

public FakeHttpContextWrapper(HttpContext httpContext, string appPath = "/",       

                            string requestUrl = "~/", string clientIP = null)

            : base(httpContext)

        {

            _request = new FakeHttpRequestWrapper(httpContext.Request, appPath, requestUrl, clientIP);

            //_response = new FakeHttpResponseWrapper(httpContext.Response);

        }

 

        /// <summary>

        /// Over-ridden so we can return our FakeHttpRequestWrapper class instead

        /// </summary>

        public override HttpRequestBase Request

        {

            get

            {

                return _request;

            }

        }

 

        public override HttpResponseBase Response

        {

            get

            {

                //return _response;

                return base.Response;

            }

        }

    }

We then create a FakeHttpRequestWrapper which inherits from the Microsoft HttpRequestWrapper.  This is where the magic happens as this allows us to over-ride those getter properties ApplicationPath, AppRelativeCurrentExecutionFilePath and UserHostAddress so we can implement our own logic.

    /// <summary>

    /// This class allows us to set additional properties that are anoyingly null by

    /// default if you just used the HttpRequestWrapper class alone.

    /// We are over-ridding the ApplicationPath & AppRelativeCurrentExecutionFilePath

    /// as these are needed to unit test MVC routes

    /// </summary>

    public class FakeHttpRequestWrapper : HttpRequestWrapper

    {

        string appPath;

        string requestUrl;

        string clientIP;

 

        public FakeHttpRequestWrapper(HttpRequest httpRequest)

            : base(httpRequest)

        {

        }

 

        public FakeHttpRequestWrapper(HttpRequest httpRequest, string appPath = "/",

                                    string requestUrl = "~/", string clientIP = null)

            : base(httpRequest)

        {

            this.appPath = appPath;

            this.requestUrl = requestUrl;

            this.clientIP = clientIP;

        }

 

        public override string ApplicationPath

        {

            get

            {

                return appPath;

            }

        }

 

        public override string AppRelativeCurrentExecutionFilePath

        {

            get

            {

                return requestUrl;

            }

        }

 

        public override string UserHostAddress

        {

            get

            {

                return clientIP;

            }

        }

    }

I didn’t need to fake the response but this is how you’d do it if you need to:

    public class FakeHttpResponseWrapper : HttpResponseWrapper

    {

        public FakeHttpResponseWrapper(HttpResponse httpResponse)

            : base(httpResponse)

        {

        }

    }

In this unit test you can see 2 lines that deal with setting the HttpContext. 

[TestMethod]

public void ProductService_PopulateModel_IPDetectUSA()

{

  var clientIP = "1.1.1.2";

  var productService = unityContainer.Resolve<IProductService>();

  HttpContext.Current = new FakeHttpContext().CreateFakeHttpContext();

 

  var httpContextWrapper = new FakeHttpContextWrapper(httpContext:     

                                        HttpContext.Current,

                                        clientIP: clientIP);

  var model = productService.PopulateModel(httpContextWrapper);

}

The 1st line sets a normal HttpContext.Current using our FakeHttpContext () method. 

The 2nd line uses our FakeHttpContextWrapper method to wrap and extend the HttpContext.Current we set up on the 1st line which is something we can now pass to MVC controllers or routes.

 

And this is how you’d unit test an MVC route (the clientIP and requestUrl parameters are optional).

[TestMethod]

public void Route_Account_Login()

{

  var routes = new RouteCollection();

  RouteConfig.RegisterRoutes(routes);

   HttpContext.Current = new FakeHttpContext().CreateFakeHttpContext();

   var httpContextWrapper = new FakeHttpContextWrapper(httpContext:

       HttpContext.Current,

requestUrl: "~/account/login/");

   var routeData = routes.GetRouteData(httpContextWrapper);

 

   //Expected Results

   AssertAll.Execute(() => Assert.IsNotNull(routeData),

   () => Assert.AreEqual("Account", (string)routeData.Values["controller"], true),

   () => Assert.AreEqual("login", (string)routeData.Values["action"], true),

   () => Assert.IsTrue(string.IsNullOrWhiteSpace(routeData.Values["id"].ToString())));

}


 

And this is how you would test an MVC controller, using the same 2 lines in yellow just without the optional parameter this time.

[TestMethod]

public void ProductController_CheckView()

{

   var productService = unityContainer.Resolve<IProductService>();

   var productController = new ProductController(productService);

   HttpContext.Current = new FakeHttpContext().CreateFakeHttpContext();

   var httpContextWrapper = new FakeHttpContextWrapper(httpContext:

HttpContext.Current);

  // Pass the fake httpContext to the controller context

  productController.ControllerContext = new ControllerContext(httpContextWrapper, new

RouteData(), productController);

 

  // Call the controller method to test

  var controllerResult = productController.Index();

 

  // Check the view name is correct

  Assert.IsTrue(controllerResult is ViewResult, "ViewResult");

  var view = controllerResult as ViewResult;

  Assert.AreEqual("Index", view.ViewName, "ViewName");

}

Unit Testing (part 1) - Without using a mocking framework


In this series of posts we are not using Moq or Microsoft Fakes or any testing framework, instead we are using the test double approach and following the rule “do not test code you do not own”.

Unit Testing (part 1) - Without using a mocking framework

Unit Testing (part 2) - Faking the HttpContext and HttpContextBase

Unit Testing (part 3) - Running Unit Tests & Code Coverage

Unit Testing (part 4) - Faking Entity Framework code first DbContext & DbSet

 

When I asked myself what I needed to test the answer is “my business logic and any classes I wrote that are called by the business logic”. I don’t want to test making actual calls to the database or 3rd party web services etc.

I also took the view that what I wanted to test was not every class in isolation I don’t have the time at my employer to do it the purist way of mocking the in’s/outs around each class. Instead I came up with a compromise and call the controller and let the code run (as it would when running on the web site). I just fake out the 3rd party calls so my focus of testing is on my code.

Let’s see how this works:

  • The MVC 5 web site controllers in my site don’t really contain any code they all reference a service function which populates the MVC model. Which keep our controllers lean.

     public ProductController(IProductService productService) : base()

     {

         this.productService = productService;

     }

     public ActionResult Index()

     {

         var model = productService.PopulateModel(this.HttpContext, ...);

         return View(model.ViewTemplateToRender, model);

     }

  • We make use of Interfaces (which are essential for effective unit testing).

  • Also note that the PopulateModel(this.HttpContext, …) is expecting a HttpContextBase more on that later.

  • We use dependency injection to inject the actual concrete class at run time which matches the interface. We do this so in our unit tests we can over-ride any concrete class and replace it with a test double if we need to. The times when we would do this are when we call code that we do not own (e.g. 3rd party web services, .Net framework code etc.).

This is where the rule “do not test code you do not own” comes into its own.

By not following this rule you open yourself to a whole world of pain. For example why test a call to an external web service. All you need to do is test that your code is calling the correct method, passing the correct data, and getting back the correct response. But without making the actual call we’d fake it instead. This results in tests that run much faster. If you made the call to the web service that’s integration testing.

Why would you test code that sets a cookie, you’ve got to fake the request / response to do it, we know it works Microsoft have tested it, and it’s been in .Net for many years. It’s actually much easier to test if you think differently and use a CookieContainer class with an interface ICookieContainer then I let the site use request.Cookies etc. And have a test double FakeCookieContainer class that doesn’t use the request / response at all.

Let’s say I had an external web service which one of my service functions calls which given the clients IP number returns the country that the IP number is located in. I do not want to make that actual call from a unit test the speed will be terrible and that’s a job for an integration test using Selenium or some other UI testing tool. But I do want to call it from my website. So I create an interface:

public interface IIpFunctions

{

    Country GetCountry(HttpRequestBase request);

}

My concrete class used by the site is going to call the GetCountry method and do the actual calling of the external web service, which would look like this (some code has been removed for simpler reading):

public class IpFunctions : IIpFunctions

{

    public Country GetCountry(HttpRequestBase request)

    {

        String ip = GetClientIP(request);

        using (HttpClient client = new HttpClient())

        {

            HttpResponseMessage response = client.GetAsync(countryRequest).Result;

            response.EnsureSuccessStatusCode();

            countryResult = response.Content.ReadAsStringAsync().Result;

        }

        return countryRepository.GetByIso(countryResult);

    }

}

For unit testing I just need to create a fake concrete class, both inheriting the same interface. So it passes in the same request object and gets a Country object back. But the code internally is very different.

public class FakeIpFunctions : IIpFunctions

{

    public Country GetCountry(HttpRequestBase request)

    {

        var clientIP = request.UserHostAddress;

        if (clientIP == "1.1.1.1")

        {

            // Fake UK IP address

            return countryRepository.GetByIso("GBR");

        }

        else if (clientIP == "1.1.1.2")

        {

             // Fake USA IP address

            return countryRepository.GetByIso("USA");

        }

        return null;

    }

}

Now when I’m writing a unit test I can just pass in the IP number 1.1.1.2 as part of the request object and I know the country will be USA.

My unit test would look like this:

[TestMethod]

public void ProductService_PopulateModel_IPDetectUSA()

{

    var clientIP = "1.1.1.2";

    var productService = unityContainer.Resolve<IProductService>();

    HttpContext.Current = new FakeHttpContext().CreateFakeHttpContext();

    var httpContextWrapper = new FakeHttpContextWrapper(httpContext: HttpContext.Current, clientIP: clientIP);

    var model = productService.PopulateModel(httpContextWrapper);

}

My dependency injection is setup so:

  • It uses the same IProductService concrete class for unit tests and for the MVC web site. But within the ProductService class’s constructor it uses the IIpFunctions interface.

public ProductService(IIpFunctions ipFunctions, …)

  • For the IIpFunctions interface the dependency injection is very different, the site calls IpFunctions and the unit tests call FakeIpFunctions.

So I’ve followed the rule “don’t test what you don’t own”. I can do the same with cookies, cache managers like Redis Cache etc. I don’t want to test third party code, so I use interfaces and create a fake test double class for unit testing with.

Note: in the above unit test example I’m starting my unit test at my ProductService not the MVC controller, there is no reason this could not be the controller but because my controllers don’t really contain any code they simply call the service layer I decided that my unit tests for the most part should start at the service layer. I’d then need just a handful of unit tests to test the controller (which would really be to just check the correct view is being returned or any viewbag values are set correctly etc.).

 

Is this the purist way of doing unit testing – No.

Is this truly a unit test where the definition of a unit = a class (e.g. you test each class in isolation and any dependencies for it are mocked / faked) – No.

Isn’t this integration testing – That’s a fine line and debatable but no because it’s very fast, all in memory and we don’t call 3rd party code/services like an integration test would.

Why didn’t we do the above?

  • Because companies don’t like to invest the time in developing unit tests especially the purist way. So it’s better to have something rather than nothing.

  • Because it would take a lot longer to write and more tests are needed to get decent code coverage.

  • It gives the same benefit; the code is tested very quickly while it’s still in Visual Studio. And we’d always follow up with integration tests once the code has been deployed to a test environment.

Saturday, July 2, 2011

Http to Https Flipper for Asp.Net Web Forms

This article shows how to automatically make Asp.Net Web Form sites open certain pages on the site as https by using a 301 moved permanently redirect.  It works with standard web forms .aspx pages and web forms using .Net Routing.

This example will cover the following scenarios:

1. If a user navigates to http://www.mydomain.com/default.aspx then we can automatically have the URL change to be https://www.mydomain.com/default.aspx for the default.aspx page only, other pages will remain non-secure.

2. You want all your sites web pages to always go secure (https).

3. Allow you to control which pages go secure by using a ‘RequireSSL’ attribute (much like mvc now does).

4. Allow you to control which pages go secure by listing them in the web.config.

5. If you have non standard ports for http and https e.g. http://www.mydomain.com:8054/default.aspx can change the URL to https://www.mydomain.com:4054/default.aspx.

Note the 8054 = http and 4054 = https.

6. You have non standard ports as point 5 above mentions, but in your live environment the ports are masked from the user by the firewall (or some other clever piece of hardware).  But internally the ports are still being used by the firewall to route traffic to your site.

e.g. Your web site on IIS is setup to use ports 8054(http) & 4054(https) but the user in their browser will see http://www.mydomain.com/default.aspx and https://www.mydomain.com/default.aspx.  In this scenario you would want to remove the port when you flip from http to https.

 

Lets see some code

First create a new project, lets call it ‘JL.Web.Security’. 

SolutionStructure

As you add the classes below the structure of the project should end up looking like the above.

Add the following 2 classes (RequireSSLAttribute.cs and RequireSSLModule.cs).

RequireSSLAttribute.cs

namespace JL.Web.Security
{
    /// <summary>
    /// Attribute to place on classes that you want to go secure (SSL / HTTPS)
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    sealed public class RequireSSL : Attribute
    {
    }
}

With the ‘RequireSSLAttribute’' you can now place this attribute on any web form code behind page and that page will always go secure e.g.

namespace WebApplication
{
    [RequireSSL]
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

The ‘RequireSSLModule.cs’ class below is the one that does the real work.

But in summary all this class really does is create a new IHttpModule that listens for page requests. Then checks if every page should be secure and if we are not already secure then go secure (https).  Otherwise check if the RequireSSL attribute is on the page if it is go secure (if we are not secure already), if the attribute is not in use then check the web.config file to see if the page is listed there, if it is go secure (if we are not secure already). Otherwise go non secure (if we are currently secure).

RequireSSLModule.cs

namespace JL.Web.Security
{
    /// <summary>
    /// HttpModule for switching between HTTP and HTTPS (HTTP <=> HTTPS)
    /// </summary>
    public class RequireSSLModule : IHttpModule
    {
        SecureWebPageSettings settings = null;

        public void Init(HttpApplication context)
        {
            settings = WebConfigurationManager.GetSection("secureWebPageSettings") as SecureWebPageSettings;
            if (settings != null && settings.Mode != SecureWebPageMode.Off)
            {
                // The PreRequestHandlerExecute event occurs just before ASP.NET begins executing
                // a handler such as a Page.
                // In here we can acquire a reference to the currently executing ASPX Page
                context.PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
            }
        }

        /// <summary>
        /// Empty, but necessary when implementing IHttpModule
        /// </summary>
        public void Dispose() { }

        /// <summary>
        /// Switch between HTTP and HTTPS as and when necessary.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
            bool requireSSL = false;

            // obtain a reference to the ASPX Page
            System.Web.UI.Page Page = HttpContext.Current.Handler as System.Web.UI.Page;

            // if a valid Page was not found, exit
            if (Page == null)
            {
                return;
            }

            if (settings.MakeAllPagesSecure)    // All pages must be secure
                requireSSL = true;
            else
            {
                // Check if the Page is decorated with the RequireSSL attribute
                requireSSL = (Page.GetType().GetCustomAttributes(typeof(RequireSSL), true).Length > 0);
                if (requireSSL == false)
                {
                    // Check the pages in the web.config to see if it is mentioned here as must be secure
                    var pageName = Page.GetType().BaseType.Name;
                    if (settings.Files.IndexOf(pageName) >= 0)
                        requireSSL = true;
                }
            }


            // check if the Page is currently using the HTTPS scheme
            bool isSecureConnection = HttpContext.Current.ApplicationInstance.Request.IsSecureConnection;

            // acquire the URI (e.g. http://localhost/default.aspx)
            Uri baseUri = HttpContext.Current.ApplicationInstance.Request.Url;

            String portToUse = String.Empty;


            if (requireSSL && !isSecureConnection)
            {
                // The page requires SSL and it is currently using the HTTP scheme.
                // Switch the HTTP scheme to the HTTPS scheme
                string url = baseUri.ToString().Replace(baseUri.Scheme, Uri.UriSchemeHttps);

                ChangePort(baseUri, true, ref url);

                // perform a 301 redirect to the secure url
                PermanentRedirect(url);
            }
            else if (!requireSSL && isSecureConnection)
            {
                // The page does not require SSL and it is currently using the HTTPS scheme.
                // Switch the HTTPS scheme to the HTTP scheme
                string url = baseUri.ToString().Replace(baseUri.Scheme, Uri.UriSchemeHttp);

                ChangePort(baseUri, false, ref url);

                // perform a 301 redirect to the non-secure url
                PermanentRedirect(url);
            }
        }

        /// <summary>
        /// Peforms a 301 redirect to the given url. This is the most search engine friendly
        /// way of redirecting to another Page. The 301 status code means that a Page has
        /// permanently moved to a new location.
        /// </summary>
        /// <param name="url"></param>
        private void PermanentRedirect(string url)
        {
            //throw new Exception("url:" + url);
            HttpContext.Current.Response.Status = "301 Moved Permanently";
            HttpContext.Current.Response.AddHeader("Location", url);
        }


        /// <summary>
        ///  Change or remove the port from the url
        /// </summary>
        private void ChangePort(Uri baseUri, Boolean useSecurePort, ref String url)
        {
            String portToUse = String.Empty;

            // Get the current port
            String portToFind = String.Format(":{0}", baseUri.Port.ToString());

            if (settings != null && settings.SecurityRemovePort)
            {
                // Config setting says to remove the port from the url so lets do that  
                url = url.Replace(portToFind, "");
            }
            else
            {
                // Security setting says to leave the port in the url so we need to change it
                if (useSecurePort)
                {
                    // If we are not on the standard port 80 we need to lookup the secure port from iis
                    if (baseUri.Port != 80)
                    {
                        portToUse = LookupSecurePortFromIIS();
                        String portToRepaceWith = String.Format(":{0}", portToUse);
                        url = url.Replace(portToFind, portToRepaceWith);
                    }
                }
                else
                {
                    // If we are not on the standard secure port 443 we need to lookup the non secure port from iis
                    if (baseUri.Port != 443)
                    {
                        portToUse = LookupNonSecurePortFromIIS();
                        String portToRepaceWith = String.Format(":{0}", portToUse);
                        url = url.Replace(portToFind, portToRepaceWith);
                    }
                }
            }
        }

        private String LookupSecurePortFromIIS()
        {
            return LookupPortFromIIS(true);
        }

        private String LookupNonSecurePortFromIIS()
        {
            return LookupPortFromIIS(false);
        }

        /// <summary>
        /// Using DirectoryServices look up in IIS the port that should be used
        /// when swapping from http to https or https to http
        /// </summary>
        private String LookupPortFromIIS(bool findHttpsPort)
        {
            String port = String.Empty;
            System.DirectoryServices.PropertyValueCollection serverBindings;

            // Get the current website server instance
            String websiteInstanceID = HttpContext.Current.ApplicationInstance.Request["INSTANCE_ID"];  // e.g. 1646904159

            // Connect to the IIS directory service for the website instance id
            System.DirectoryServices.DirectoryEntry path = null;
            if (!String.IsNullOrEmpty(settings.IISServerUsername) && !String.IsNullOrEmpty(settings.IISServerPassword))
                path = new System.DirectoryServices.DirectoryEntry("IIS://localhost/W3SVC/" + websiteInstanceID, settings.IISServerUsername, settings.IISServerPassword);
            else
                path = new System.DirectoryServices.DirectoryEntry("IIS://localhost/W3SVC/" + websiteInstanceID);

            // Find the port number
            if (findHttpsPort)  // Get https port
                serverBindings = path.Properties["SecureBindings"];
            else                // Get http port
                serverBindings = path.Properties["serverbindings"];

            if (serverBindings.Value != null)
            {
                String bindingValue = serverBindings.Value.ToString();

                // Just get the port number (remove everything else)
                bindingValue = bindingValue.Substring(bindingValue.IndexOf(":") + 1);

                port = bindingValue.Replace(":", "");
            }

            return port;
        }
    }
}

The LookupPortFromIIS method is the one that needs the most explaining.  In the case when you are using non standard ports e.g. not 80 for http and not 443 for https, then in order to flip from http://mydomain.com:8054 to https://mydomain.com:4054 this method looks up from the IIS server what port number should be used.  So if you were going from http on port 8054 we would look up in IIS that we need port 4054 in order to go secure.  And if we were already secure we would look up that we need port 8054 to go non-secure.  To do this lookup into IIS we use DirectoryServices which has been tested on IIS6 and IIS7.

 

And finally to support controlling which pages go secure via the web.config we need to add some configuration code. Add to this project a ‘Configuration’ directory.  Under the configuration directory add the following 3 classes (enums.cs, SecureWebPageFileSetting.cs and SecureWebPageSettings.cs).

enums.cs

namespace JL.Web.Security.Configuration
{
    public enum SecureWebPageMode
    {
        /// <summary>
        /// Web page security is on
        /// </summary>
        On,

        /// <summary>
        /// Web page security is off
        /// </summary>
        Off
    }
}


SecureWebPageFileSetting.cs

namespace JL.Web.Security.Configuration
{
    /// <summary>
    /// Represents an file entry in the <secureWebPageSettings>
    /// configuration section.
    /// </summary>
    public class SecureWebPageFileSetting : ConfigurationElement
    {
        #region Constructors
        /// <summary>
        /// Creates an instance of SecureWebPageFileSetting.
        /// </summary>
        public SecureWebPageFileSetting()
            : base()
        {
        }

        /// <summary>
        /// Creates an instance with an initial page name.
        /// </summary>
        /// <param name="pagename">The page class name e.g. login (not login.aspx).</param>
        public SecureWebPageFileSetting(string pagename)
            : this()
        {
            Pagename = pagename;
        }
        #endregion

        /// <summary>
        /// Gets or sets the path of this file setting.
        /// </summary>
        [ConfigurationProperty("pagename", IsRequired = true, IsKey = true)] 
        public string Pagename
        {
            get { return (string)this["pagename"]; }
            set { this["pagename"] = value; }
        }
    }

    /// <summary>
    /// Represents a collection of SecureWebPageFileSetting objects.
    /// </summary>
    public class SecureWebPageFileSettingCollection : ConfigurationElementCollection
    {
        /// <summary>
        /// Gets the element name for this collection.
        /// </summary>
        protected override string ElementName
        {
            get { return "files"; }
        }

        /// <summary>
        /// Gets a flag indicating an exception should be thrown if a duplicate element
        /// is added to the collection.
        /// </summary>
        protected override bool ThrowOnDuplicate
        {
            get { return true; }
        }

        /// <summary>
        /// Gets the element at the specified index.
        /// </summary>
        /// <param name="index">The index to retrieve the element from.</param>
        /// <returns>The SecureWebPageFileSetting located at the specified index.</returns>
        public SecureWebPageFileSetting this[int index]
        {
            get { return (SecureWebPageFileSetting)BaseGet(index); }
        }

        /// <summary>
        /// Gets the element with the specified pagename.
        /// </summary>
        /// <param name="pagename">The pagename of the element to retrieve.</param>
        /// <returns>The SecureWebPageFileSetting with the specified pagename.</returns>
        public new SecureWebPageFileSetting this[string pagename]
        {
            get
            {
                if (pagename == null)
                    throw new ArgumentNullException("pagename");
                else
                    return (SecureWebPageFileSetting)BaseGet(pagename.ToLower(CultureInfo.InvariantCulture));
            }
        }

        #region Collection Methods
        /// <summary>
        /// Adds a SecureWebPageFileSetting to the collection.
        /// </summary>
        /// <param name="fileSetting">An initialized SecureWebPageFileSetting instance.</param>
        public void Add(SecureWebPageFileSetting fileSetting)
        {
            BaseAdd(fileSetting);
        }

        /// <summary>
        /// Clears all file entries from the collection.
        /// </summary>
        public void Clear()
        {
            BaseClear();
        }

        /// <summary>
        /// Removes a SecureWebPageFileSetting from the collection with a matching pagename as specified.
        /// </summary>
        /// <param name="pagename">The pagename of a SecureWebPageFileSetting to remove.</param>
        public void Remove(string pagename)
        {
            int Index = IndexOf(pagename);
            if (Index >= 0)
                BaseRemoveAt(Index);
        }

        /// <summary>
        /// Removes the SecureWebPageFileSetting from the collection at the specified index.
        /// </summary>
        /// <param name="index">The index of the SecureWebPageFileSetting to remove.</param>
        public void RemoveAt(int index)
        {
            BaseRemoveAt(index);
        }
        #endregion

        /// <summary>
        /// Creates a new element for this collection.
        /// </summary>
        /// <returns>A new instance of SecureWebPageFileSetting.</returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new SecureWebPageFileSetting();
        }

        /// <summary>
        /// Gets the key for the specified element.
        /// </summary>
        /// <param name="element">An element to get a key for.</param>
        /// <returns>A string containing the pagename of the SecureWebPageFileSetting.</returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            if (element != null)
                return ((SecureWebPageFileSetting)element).Pagename.ToLower(CultureInfo.InvariantCulture);
            else
                return null;
        }

        /// <summary>
        /// Returns the index of an item with the specified pagename in the collection.
        /// </summary>
        /// <param name="pagename">The pagename of the item to find.</param>
        /// <returns>Returns the index of the item with the pagename.</returns>
        public int IndexOf(string pagename)
        {
            if (pagename == null)
                throw new ArgumentNullException("pagename");
            else
                return this.IndexOf((SecureWebPageFileSetting)BaseGet(pagename.ToLower(CultureInfo.InvariantCulture)));
        }

        public int IndexOf(SecureWebPageFileSetting item)
        {
            if (item != null)
                return BaseIndexOf(item);
            else
                return -1;
        }
    }
}


SecureWebPageSettings.cs

namespace JL.Web.Security.Configuration
{
    /// <summary>
    /// Contains the settings of a secureWebPageSettings configuration section.
    /// </summary>
    public class SecureWebPageSettings : ConfigurationSection
    {
        /// <summary>
        /// Creates an instance of SecureWebPageSettings.
        /// </summary>
        public SecureWebPageSettings()
            : base()
        {
        }

        #region Properties
        /// <summary>
        /// Gets or sets the mode indicating how the secure web page settings handled.
        /// </summary>
        [ConfigurationProperty("mode", DefaultValue = SecureWebPageMode.On)]
        public SecureWebPageMode Mode
        {
            get { return (SecureWebPageMode)this["mode"]; }
            set { this["mode"] = value; }
        }

        /// <summary>
        /// Gets the collection of file settings read from the configuration section.
        /// </summary>
        [ConfigurationProperty("files")]
        public SecureWebPageFileSettingCollection Files
        {
            get { return (SecureWebPageFileSettingCollection)this["files"]; }
        }

        /// <summary>
        /// Gets or sets the remove port mode.  This should be set to true when SSL is on a non standard port
        /// </summary>
        [ConfigurationProperty("securityRemovePort", DefaultValue = false)]
        public bool SecurityRemovePort
        {
            get { return (bool)this["securityRemovePort"]; }
            set { this["securityRemovePort"] = value; }
        }

        /// <summary>
        /// Makes all pages on the site secure, regardless of what is specified in the files section.
        /// </summary>
        [ConfigurationProperty("makeAllPagesSecure", DefaultValue = false)]
        public bool MakeAllPagesSecure
        {
            get { return (bool)this["makeAllPagesSecure"]; }
            set { this["makeAllPagesSecure"] = value; }
        }

        /// <summary>
        /// IIS Server user name use to login to directory services
        /// </summary>
        [ConfigurationProperty("iisServerUsername", DefaultValue = "")]
        public String IISServerUsername
        {
            get { return (String)this["iisServerUsername"]; }
            set { this["iisServerUsername"] = value; }
        }

        /// <summary>
        /// IIS Server password use to login to directory services
        /// </summary>
        [ConfigurationProperty("iisServerPassword", DefaultValue = "")]
        public String IISServerPassword
        {
            get { return (String)this["iisServerPassword"]; }
            set { this["iisServerPassword"] = value; }
        }
        #endregion
    }
}

 

In our web site we configure the Http to Https flipper:

Add the following line to the web.config configSections.

<section name="secureWebPageSettings" type="JL.Web.Security.Configuration.SecureWebPageSettings, JL.Web.Security" />

Add the following line to the web.config modules section under system.webServer.

<add name="JL.Web.Security.RequireSSLModule" type="JL.Web.Security.RequireSSLModule" preCondition="managedHandler" />

 

You can now use the ‘RequireSSLAttribute’' by placing this attribute on any web form code behind page and that page will always go secure e.g.

namespace WebApplication
{
    [RequireSSL]
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

And in the web.config add the following to control the http to https flipper settings:

<!-- SSL Configuration -->
    <secureWebPageSettings mode="On" securityRemovePort="False" makeAllPagesSecure="False">
        <files>
            <add pagename="WebForm1" />
        </files>
    </secureWebPageSettings>

secureWebPageSettings mode=”Off”   - Disables the http to https flipper, which you will want to do if you are using Visual Studio’s built in web server as that does not support https.

secureWebPageSettings mode =”On”   - Enable the flipper, use this when the code is running on an IIS server.

secureWebPageSettings securityRemovePort = “True”   - This will remove the port from the URL (to be used in scenario 6 I mentioned at the top of this article).

secureWebPageSettings makeAllPagesSecure = “True”   - All pages on your site will go secure.

secureWebPageSettings makeAllPagesSecure = “False”   - Only pages using the RequireSSL attribute or those listed in the <files> section in the web.config will go secure.  All other pages will flip back to non-secure.

In the add files section you should add a line for each page that you want to go secure.  The pagename is the class name of the page (as in the code behind page e.g. WebForm1)

namespace WebApplication
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

To test the code you will need an SSL, you can create your own on IIS6 using the SelfSSL tool that comes with the IIS6 Resource Kit that can be downloaded here: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=17275.

Or on IIS7 here is a good post on setting up a self certificate: http://weblogs.asp.net/scottgu/archive/2007/04/06/tip-trick-enabling-ssl-on-iis7-using-self-signed-certificates.aspx

And finally on IIS7 when the app pool is running in Managed Pipeline Mode = Integrated you will need to add the JL.Web.Security.RequireSSLModule to the ‘Modules’ section in IIS Manager for the website.

Modules

AddModule

Here is the source code