December 2008 - Posts

Using Configuration Sections to Customize Application Settings and Serializing Them to External Config Files
Monday, December 29, 2008 5:39 PM

.NET configuration files provide a very useful mechanism for specification of runtime settings for applications, web sites and web pages. Probably, the biggest benefit of configuration files is that settings can be changed dynamically without having to recompile the application. Every time the application is loaded for the first time settings within the configuration files are cached. Thereafter at the event of any change the settings are re-cached. If there are any custom settings that users might want to add, the AppSettings configuration section can be used to add key value pairs and then later read or changed from within the application programmatically. However, by creating custom Configuration Sections settings can be organized in a more ordered fashion. Also, you can have greater control over the structure and types of the properties of your configuration section rather than having simple key and value pairs.

In this blog post, we'll focus on creating a custom Configuration Section by deriving a class from ConfigurationSection, setting desired properties programmatically and then serializing it to a configuration file. To avoid re-caching of the entire web.config file (or App.config in case it is not a web application) we will serialize our section to an external file so that changes made to our section do not cause re-caching of the all the settings within web.config, leading to better performance.

Let’s say we need to specify as part of the configuration settings the URLs of two images that would form the header and footer of all the web pages in our application. Of course, this can be achieved without using configuration at all but for the sake of learning we will go ahead and use this example.

So let’s begin.

Start Visual Studio and create a new Website. For our specific example we’ll call it CustomConfigSectionExample.

Add the following to default.aspx.

<asp:Label ID="Label1" runat="server" Text="Header URL"></asp:Label>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Label ID="Label2" runat="server" Text="Footer URL"></asp:Label>
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" />

Next, we shall create our custom configuration class. And call it WebPageConfiguration. The structure of the class is relatively simple. It has two properties HeaderImageURL and FooterImageURL, both of type string. Notice that the class derives from System.Configuration.ConfigurationSection. Also, notice the ConfigurationProperty tags on each of the properties.

public class WebPageConfiguration : ConfigurationSection
{
    public WebPageConfiguration()
    {
    }

    [ConfigurationProperty("headerImageURL", IsRequired = true)]
    public string HeaderImageURL
    {
        get
        {
            return (string)this["headerImageURL"];
        }
        set
        {
            this["headerImageURL"] = value;
        }

    }

    [ConfigurationProperty("footerImageURL")]
    public string FooterImageURL
    {
        get
        {
            return (string)this["footerImageURL"];
        }
        set
        {
            this["footerImageURL"] = value;
        }
    }
}

Let us now add a definition for our custom configuration section in the web.config file. In case, we wish to new-up an object of the configuration section and then serialize it to the configuration ile we would not need to add this definition. However, for our example since we would be reading a section and THEN changing its properties this definition is required.

<section name="MyWebPageSettings" type="WebPageConfiguration, App_Code" />

Next would be creation of an external config file to which the settings would be serialized. As explained above, using external files leads to better performance if the settings within it are to be changed very frequently. The external file MUST have a root element else the compiler will complain. Even if you wish to new-up an object of the configuration section the external file cannot be empty. Our file called “external.config” would contain the following root element. “MyWebPageSettings” would be the name of our configuration section.

<MyWebPageSettings/>

Finally we’ll add some code to the Button1_Click method associated with the Button on our default.aspx page. This can be done either in the .cs file associated with the .aspx page or the .aspx page itself. The purpose of this method is to take as input the strings entered on the two text boxes on our page and set them as the values of the properties within our configuration section.

First we get a handle to the configuration object and then extract the section named “MyWebPageSettings”. Next we set the properties to some values and save the configuration object. Invoking the Save() method causes the new values to be serialized to file. Notice how the ConfigSoure property is used. Its purpose it to specify the name of the external config file.

Configuration config = WebConfigurationManager.OpenWebConfiguration("/CustomConfigSectionExample");
WebPageConfiguration webPageConfigurationSection = 
    (WebPageConfiguration)config.GetSection("MyWebPageSettings");
webPageConfigurationSection.HeaderImageURL = TextBox1.Text;
webPageConfigurationSection.FooterImageURL = TextBox2.Text;
webPageConfigurationSection.SectionInformation.ConfigSource = "external.config";
config.Save();

Build and browse the page default.aspx. You should be able to enter strings in the textboxes, push the button and see changes made in the config files.

Let’s first take a look at web.config. You should be able to see the following:

<MyWebPageSettings configSource="external.config" />

This merely tells where the section can actually be found.

The external.config file should be changed to the following:

<MyWebPageSettings headerImageURL="string1" footerImageURL=" string2" />

If you change the values and push the button again you should be able to see the changes immediately without the need for any recompilation.

If you need an overview of the ASP.NET Configuration API http://msdn.microsoft.com is an excellent resource and if you have any specific questions about Configuration please let me know. Hope this information was helpful!

Assad Safiullah

Software Development Engineer in Test

ASP.NET QA Team

Sharing Authentication, Profile and Role data between Web Applications and Windows Forms applications using the ASP.NET Application Services
Thursday, December 11, 2008 1:16 AM

Some of us might have used the ASP.NET Authentication, Profile or Roles services that shipped with the .Net Framework 3.5. Another advantage of these services is their integration with windows forms applications and the fact that user authentication, role and profile data can be shared between windows forms and web applications.

Scenario: Lets say a company “Contoso” has a few in-house applications, some built as web applications and some as Windows Forms applications. Contoso wants to have user information along with their roles stored at one location and wants all of its applications to use the same store to authenticate users as well as authorize requests for various resources based on their roles. Contoso also wants the user settings / preferences to be shared between all these applications. This can quite easily be accomplished by using the inbuilt ASP.NET Application Services. The Web Applications can be setup to use the ASP.NET Authentication, Roles and Profile services . The Windows Forms applications can be setup to use the ASP.NET Application Services from one of those web applications. Here's a walk-through.

Step A- Create the online ASP.NET Application Services

1. Open Visual Studio 2008 and create a website targeting the .Net Framework version 3.5. Lets call this website “AppServices”

2. Let’s turn on the Authentication, Profile and Roles services –

To do this all we simply need to do is add the following to the web.config file of the AppServices website. "prop1" and "prop2" are the profile properties we'll expose further below.

<system.web.extensions>
        <scripting>
            <webServices>
                <authenticationService enabled="true"/>
                <roleService enabled="true"/>
                <profileService enabled="true" readAccessProperties="prop1,prop2" 
                        writeAccessProperties="prop1,prop2"/>
            </webServices>
        </scripting>
    </system.web.extensions>

By default, this causes the default Membership, Profile and Role providers to be used, which in turn use SqlExpress (by default) to create and access the database. SQL server can also be used. These providers can be changed to go against any database and are also explained in other articles.

3. Let’s use Forms Authentication for the purposes of this sample. So set the following into the web.config file:

<authentication mode="Forms" />

4. We’ll need to enable the RoleManager, so lets add the following to the web.config file under the “<system.web>” section:

<roleManager enabled="true"></roleManager>

5. Lets enable some profile properties for use in a windows forms application, so lets add the following to the web.config file under the “<system.web>” section:

<profile enabled="true">
    <properties>
        <add name="prop1" type="System.String"/>
        <add name="prop2" type="System.String"/>
    </properties>
</profile>

6. Let’s now setup some users and Roles. This can be done through a fancier UI, but for now, let’s just do it through code. We’ll create a page called “CreateUsersAndRoles.aspx” inside this website and let’s copy the following code into its Page_Load method.

protected void Page_Load(object o, EventArgs e)
    {
        //Lets create two roles
        Roles.CreateRole("Admin");
        Roles.CreateRole("User");

        //Lets also create two users
        MembershipCreateStatus status;
        MembershipUser user1 = Membership.CreateUser("user1", "Password@1", 
            "someemail@email.com", "question", "answer", true, out status);
        MembershipUser user2 = Membership.CreateUser("user2", "Password@1", 
            "someemail@email.com", "question", "answer", true, out status);

        // Now let's assign these users to their roles
        Roles.AddUserToRole("user1", "Admin");
        Roles.AddUserToRole("user2", "User");
    }

The Application Services are now setup.

Step B - Create the Windows Forms Application

1. Through Visual Studio 2008, create a new Windows Forms Application (Lets call it MyWinFormsApp) targeting the .Net Framework version 3.5

2. Go to the project properties, and in the services tab, check the "Enable client application services" checkbox. Select "Use Forms Authentication". Also set the service locations as below ("http://localhost/AppServices/" in this case).

set_services 

3. On  the Settings tab, click the "Load Web Settings" button. Authenticate as a user in the database ("user1", "Password@1" in this case). Click the "login" button. You should see something like:

load_web_settings

Step C. And we're done!

To use authentication, profiles and roles, a small example is:

if (!Membership.ValidateUser("user1", "Password@1"))
{
    MessageBox.Show("Unable to authenticate.", "Not logged in",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
    Application.Exit();
}
else
{
    IIdentity user = System.Threading.Thread.CurrentPrincipal.Identity;
    MessageBox.Show("Logged in user is " + 
    user.Name);
    MessageBox.Show("Is user in role: " + 
        Roles.IsUserInRole(user.Name,"Admin"));
    Properties.Settings.Default.prop1 = "foo";
    Properties.Settings.Default.prop2 = "bar";
    MessageBox.Show(Properties.Settings.Default.prop1 + 
        Properties.Settings.Default.prop2);
}

Note: There are also tutorials on the internet that explain how to use "Client Application Services" in Windows Forms Applications even in offline / disconnected mode. Note that the users are being authenticated, or profile data being accessed through calls to the application services hosted through the web application.

Carl Dacosta
ASP.NET QA Team

Working with the ASP.NET AJAX Authentication Service
Tuesday, December 09, 2008 7:20 AM

Believe it or not, but there's more to ASP.NET AJAX than the UpdatePanel control. If you're already a fan of AJAX and your ASP.NET website is using Forms based authentication with the ASP.NET Membership feature to validate users, you might want to consider using the ASP.NET AJAX Authentication Service available in ASP.NET v3.5. It's a great way to seamlessly and efficiently integrate user authentication into your web site that's driven from the client.

Let's first talk a bit about the architecture of this feature. Whenever you request an ASP.NET page which has AJAX functionality enabled, a series a JavaScript files is downloaded (and cached) to the client. In one of these files is a JavaScript proxy for calling into the server side Authentication Service. Your client side JavaScript code can use this proxy to perform basic authentication operations like validating users, checking their logged-in status and logging them out of your site. When making the call to the service via the client side proxy, a call is made to a special HTTP Handler which routes the request to the Authentication Service. The service in turn calls down into the Membership system to make the appropriate Membership API call, and then the result is sent back over the wire to the client.

Okay, enough background. Let's put this feature into action. I'll be using Visual Studio 2008 as I walk through this example, but it's not required to use the ASP.NET AJAX Authentication Service. It just makes creating the web site a bit easier.

First, let's create a new web site and switch the authentication mode from the default of Windows to Forms in the site's web.config file:

<authentication mode="Forms" />

Next, we'll need to enable the Authentication Service. It's disabled by default for security reasons. Add the following section to your site's web.config:

<system.web.extensions>
    <scripting>
        <webServices>
            <authenticationService enabled="true" requireSSL="false"/>
        </webServices> 
    </scripting>
</system.web.extensions>

Now calls to the JavaScript proxy can execute on the client.

To demonstrate the service in action, we'll want to create a test user. I'm going to assume your familiar with the Membership feature introduced in ASP.NET v2.0. It's enabled by default and uses SQL Server Express as the default backing data store. Go ahead and create a test user. You can do this by either creating a new web page which invokes the relevant Membership API's to create the user or you can launch the ASP.NET Configuration tool from within Visual Studio (Website -> ASP.NET Configuration) to do this.

At this point, we've configured our site to use Forms based authentication, a test user has been created and we've enabled the Authentication Service. Let's start building a web page which will bring everything together.

I'm going to create a new web page called "UseAuthService.aspx", and then I'll add the blob of code listed below. It looks like a lot, but that's because I've added a bunch of UI logic to better demonstrate how you might plug this in to a real website. The code for the Authentication Service integration is really minimal.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UseAuthService.aspx.cs" 
    Inherits="UseAuthService" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" 
TagPrefix="ajaxToolkit" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>AJAX Authentication Service Sample</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server"></asp:ScriptManager>
    <div>
        <span id="welcomeLabel">Welcome, please</span>
        <asp:HyperLink ID="loginLink" runat="server" ForeColor="Blue" 
            Font-Underline="true">login.</asp:HyperLink>
        <asp:Panel ID="Panel1" runat="server">
            <br />
            <table >
                <tr> <td> Username </td>
                    <td> <input type="text" id="usernameTextBox" /> </td>
                </tr>
                <tr> <td> Password </td>
                    <td> <input type="password" id="passwordTextBox" /> </td>
                </tr>
                <tr> <td> </td>
                    <td> <input type="button" value="Log In" onclick="loginUser()" /> </td>
                </tr>
            </table>
            <span id="loginErrorLabel" style="color: Red; font-weight: bold; 
                font-size: large; visibility: hidden;"><b>Log in failed.</b> </span>
        </asp:Panel>
        <ajaxToolkit:PopupControlExtender ID="PopupControlExtender1" runat="server" 
            TargetControlID="loginLink" PopupControlID="Panel1" Position="Left" />
        <br />
        <span id="logoutLink" onclick="logoutUser()" style="visibility: hidden; 
            color: Blue; text-decoration: underline">Logout</span>    
    </div>
    </form>

    <script language="javascript" type="text/javascript">
        var usernameTextBox;
        var passwordTextBox;
        var username;
        var password;
        var welcomeLabel;
        var loginErrorLabel;
        var loginLink;
        var panel1;
        var logoutButton;

        function pageLoad() {
            usernameTextBox = $get("usernameTextBox");
            passwordTextBox = $get("passwordTextBox");
            welcomeLabel = $get("welcomeLabel");
            loginErrorLabel = $get("loginErrorLabel");
            loginLink = $get("loginLink");
            panel1 = $get("panel1");
            logoutButton = $get("logoutButton");
        }

        function loginUser() {
            username = usernameTextBox.value;
            password = passwordTextBox.value;
            Sys.Services.AuthenticationService.login(username, password, false, 
                null, null, onLoginCallCompleted, null, username);
        }

        function onLoginCallCompleted(result, context, methodName) {
            if (result == true) {
                welcomeLabel.innerHTML = "Welcome " + context + "!";
                loginErrorLabel.style.visibility = "hidden";
                loginLink.style.visibility = "hidden";
                panel1.style.visibility = "hidden";
                logoutLink.style.visibility = "visible";
            } else {
                loginErrorLabel.style.visibility = "visible";
                loginLink.style.visibility = "visible";
                panel1.style.visibility = "visible";
                logoutLink.style.visibility = "hidden";
            }
        }

        function logoutUser() {
            Sys.Services.AuthenticationService.logout(null, null, null, null); 
        }
    </script>
</body>
</html>

Let's dissect each section to see what's going on.  Walking the code from top to bottom, you'll first notice I'm registering the AJAX Control Toolkit assembly. If you're not already familiar with this set of AJAX controls, I recommend you take a few minutes to learn about (link provided below). Anyway, I'm just using the PopupControlExtender control to create a nicer UI for gathering the user's credentials.

Next, you'll see the mandatory ScriptManager control on the page. After that, you'll see various HTML and ASP.NET controls which I'm using to display some basic UI. Out of all of this goop, the key things to notice are:

  • The <input> control which has it's "onclick" value set to the "loginUser()" JavaScript function.
  • The <span> control which has it's "onclick" value set to the "logoutUser()" JavaScript function.

These two target JavaScript functions are our hooks into the Authentication Service's login and logout functionality via the JavaScript proxy.

The next block of code is all of the JavaScript which drives the UI of the page and makes the calls into the Authentication Service's login and logout API's.

Looking at the JavaScript function named loginUser(), you'll see we're pulling the username and password values entered in the page and passing them to the following API:

Sys.Services.AuthenticationService.login(username, password, false, 
                null, null, onLoginCallCompleted, null, username);

Looking at the Javascript function named logoutUser(), it's a very simple call to the Authentication web services logout API:

Sys.Services.AuthenticationService.logout(null, null, null, null); 

If you want to see the full details of the parameters being passed to the API's, it's well documented on MSDN. For the login call, we're just passing the username and password along with the name of a callback function we want to execute once the call to the Authentication Service returns. The callback function simply tweaks the UI appropriately depending on the success or failure of the authentication.

Now before we run the code, I'm going to start Fiddler, which is an HTTP traffic monitoring tool, so I can see the bits flowing over the wire.

I'll make a request to "UseAuthService.aspx". Peeking at Fiddler, besides seeing the initial request for the page, we can see additional requests coming from the client to the special "ScriptResource.axd" handler. This downloads the JavaScript files which form the core of the ASP.NET AJAX framework, and one of these files contains the JavaScript proxy for the Authentication Service. If you want to hunt for it, I recommend you set debugging to "true" in your site's web.config so you get the more verbose version of the JavaScript files.

So here's the UI for the page at the point of a user logging in:

loginScreen

If we click on the "login" link, we get a pop-up where we can enter credentials.

credentials

We'll enter the credentials of the user previously created and look at the HTTP traffic in Fiddler. You'll notice the content sent back and forth between the client and the proxy using the JSON format.

In Fiddler, we'll see the following URL being requested: 

  • …/Authentication_JSON_AppService.axd/Login

Looking at the text sent in the request, we see:

  • {"userName":"Mark","password":"abc123!","createPersistentCookie":false}

That's a tiny amount of data. No view state information is being sent and no page life cycle is being executed on the server. It's a quick and efficient call.

Since the credentials passed were valid, the Authentication Service responds with a value of "true" and an authentication cookie is set:

  • Set-Cookie: .ASPXAUTH=...; path=/; HttpOnly

The UI now changes to reflect the fact that a user has logged in and we now see a "Logout" link.


logout

Let's click on the "Logout" link and take a look at the HTTP traffic again.

We'll see the following URL being called:

  • .../Authentication_JSON_AppService.axd/Logout

In the response, we can see the HTTP header being set which wipes out the authentication cookie so the user is now "logged out" of the site:

  • Set-Cookie: .ASPXAUTH=; expires=Tue, 12-Oct-1999 07:00:00 GMT; path=/; HttpOnly

 

That's it. You now see how easy it is to integrate client-side driven authentication using the ASP.NET Authentication Service.

Some additional notes and resources:

  • If this were a production site, you would probably have configured the Authentication Service to require SSL.
  • In the intro, I mentioned there's a special HTTP Handler which routes the request to the Authentication Service. Looking at the web.config for the site, you'll see this defined as:
      <add verb="*" path="*_AppService.axd" ... >  


Mark Berryman
ASP.NET QA Team

by aspnetqa | 14 comment(s)
Filed under: ,
Evolution of the ASP.NET Test Process (Part 2)
Sunday, December 07, 2008 10:44 PM

This is the second part of a short narration that describes how the testing process has evolved in the ASP.NET QA Team. First part here.

 

The Feature Crew Model

After the release of .NET 2.0, all of Developer Division underwent a general change in the way we built software. This change came from top-down, and it was called the "Feature Crew Model'. I remember that all DevDiv made a lot of preparations for this new methodology, taking months out of development time to build a lot of infrastructure and tools to make it happen. Soma must be recognized for taking this bold step across the division. The basics are:

 

  1. Each Product Unit (PU) inside DevDiv would divide their release into features. A "feature" meant a logical sub component of the product, that in theory could be developed independently.
  2. A feature crew would be created from resources from the PM, Dev and QA disciplines and they would be given some degree of autonomy to shape the feature and manage their milestones.
  3. A feature crew would develop everything on a separate independent branch.
  4. A series of Quality Gates were put in place and only after a feature passed all of them, would the code be permitted to integrate into the main product branch.

The key part of this process was that it enforced a high level of quality before each individual piece made it into the build, and that each piece was finished. This was a big improvement because in the past there was a lot code churn happening on the main branch from all over the division that generated a lot of regressions. The second benefit was that this way of working encouraged better communication between Dev and Test, and allowed for teams that wanted to experiment with SCRUM or other agile methodologies.

 

Our First Feature Crews (ASP.NET Ajax 1.0 Release)

Our team's first encounter with this process was during a project code named Atlas (later to be known as ASP.NET AJAX). Back then, nobody was really sure how this would work out (and there wasn't a lot of guidelines coming from the division either), so it was understood that we would experiment and modify as we went.

Atlas was divided into the following feature crews (they were kicked off in this order, since subsequent crews relied on the previous):

  • Core: which included the type system and BCL.
  • Networking: everything under Sys.Net
  • Partial Rendering: UpdatePanel (this is the feature crew that I worked on, along side Eilon Lipton)
  • UpdateProgress/Timer: targeted crews for only these 2 controls.
  • Services: script callable Membership, Profile and Roles services.

 

ASP.NET QA Process in a Feature Crew

Looking back, the QA team didn't made any substantial changes from our old process, we were too busy preparing for having our test sources in different branches. So the QA process ended up being:

  1. QA resources would be assigned to a feature crew.
  2. Dev/PM/Test would start working on the feature at the same time, on an independent branch.
  3. Dev was now responsible for writing UnitTests
  4. Each feature crew would meet at least 3 times a week to design the feature and provide status updates. (The crew was in charge of costing all work, and arranging it in milestones. At the end of each milestone, the crew would integrate the progress into main).
  5. As the design was being developed, the PM worked on the spec, Dev on prototypes and QA on the test plan.
  6. QA would have a test plan review as before.
  7. QA would start testing the feature as soon as code was checked in by Dev, and would start writing automation.
  8. Progress was still tracked by automation complete numbers.
  9. QA was responsible of signing off a number of items on the Quality Gates check list. Of importance, is the fact a quality gate was to be automation complete, and to have run all test in a number of configuration runs.
  10. After all quality gates were met, feature was integrated to Main.

 

How did it Work?

  • Benefits
    • Better collaboration between disciplines (we were all on the same boat to get our stuff integrated).
    • QA was involved in the design.
    • Code that entered the build was of high quality already.
    • Dev's wrote unit tests which caught bugs earlier.
  • Disadvantages
    • Probably the biggest problem was that QA still tried to do everything that we used to do on a 2-3 year release, but now on a compressed 3-4 months iterations. Because we didn't change anything in our process, we now really became the bottle neck.
    • QA was still creating full featured test plans and focused heavily on automation, for features that now changed even more often.
    • Overlap between dev unit tests and QA functional tests, the old mentality of "test everything" did not go away.
    • Dependencies between crews created version problems that sometimes got crews blocked.

 

In the end, the team pushed quality up stream, but the QA team bled and struggled a lot. And some of the problems that we had identified before began happening again, in particular, finding bugs to late. My opinion is that after Atlas was shipped, the team had a better development process that put quality first and helped the QA team, but our own QA processes still needed to adapt to this new way of doing things.

In Part 3 I'll narrate how the QA team reinvented its methodology for the next ASP.NET release, and how it did it from the bottom up.

 

Federico Silva Armas
SDET, ASP.NET QA Team

by farmas | with no comments
Filed under: ,
Sharing User Controls Between IIS Applications
Thursday, December 04, 2008 4:36 PM

I know a lot of you have developed User Controls over the years, and that many of you have large web sites that re-use these controls.  I appreciate the scope of the issues many of you face in maintaining very large sites and the issues you face when making seemingly small changes in them.

A training session I attended this week reminded me of a bugbear many of you face:  when a large web site's been divided into multiple applications in IIS, there isn't an obvious way to re-use User Controls across those applications.  The instructor was a little bit more adamant; he said there wasn't so much as a good way to do it. 

That isn't the same as saying there's NO way to do it, however, and in this post I want to talk about a way to make it happen.  Whether it's good is something I'll leave up to you. :)

Screenshot of the Date control

To make this easy to explain, let's say that you have a simple ASP.NET control, like the Date.ascx control shown here, which simply displays the date in a few different calendars.  Let's say that you want this on all of the pages in your site, and let's also say that due to reasons beyond your control your site is divided into a couple applications in IIS, which I'll call WebSite1 and WebSite2, organized side-by-side.

If you want the same ASCX file to be used by both IIS applications, you might falsely reach the conclusion that you're stuck.  After all, pages in one application can't use an ASCX file from another application.  However, you can do something extra-sneaky to make IIS treat a folder's contents as part of two different applications at the same time, permitting you to maintain only one physical copy of the ASCX file on the server.

To do this:

  1. Create a folder outside your site's web root (like, say, D:\UserControls) and copy the User Control and its code file there.
  2. Open Internet Information Services Manager (Inetman.exe) and find the root node for one of the applications that you want to share the control.
  3. Right-click the node, select "Add Virtual Directory...", and add the directory from step 1 as a virtual directory under the IIS application node.
    Screenshot of Step 3
  4. Repeat steps 2 and 3 for the other applications you want to be able to use the control.

When you're done, each application will have a virtual directory underneath it which contains the User Control's ASCX file and code file.  When ASP.NET compiles the two sites, the ASCX files will be compiled as a part of -- and be usable in -- both applications.

This solution, of course, isn't perfect.  In addition to the added setup cost up front, your applications will still separately compile and build the assembly for the user control, which is redundant effort.  If you care about saving compilation time, you won't gain any advantage from doing this.  However, if maintainability is your goal, the above does eliminate the need to maintain separate copies of your user controls on the server.

Let me know if you find some value from this and can simplify your site's management.

Clay Compton
ASP.NET QA Team

 

by aspnetqa | 3 comment(s)
Filed under: ,
Evolution of the ASP.NET Test Process (part 1)
Tuesday, December 02, 2008 9:02 PM

For those of you interested in testing methodologies, in these series of posts I'll go over how the QA team has changed the way we test ASP.NET over the years. I have been in the team for over 6 years now and have witnessed several transitions (most of which I think are for the better).

 

The Early Days (.NET 1.0, 1.1 and 2.0)

I joined the team when .NET 1.1 was about to be released and the team was getting ready to work on 2.0 (which turned out to be a major and lengthy release). Back then the general QA process was:

  1. By the time the QA team finished testing and singing off on a release, the next release was well under development. There is usually a "stabilization" time at the end of a release where the QA team is busy writing automated tests, running them under all sorts of configurations and going through a list of checks called "Exit Criteria". This meant that Dev/PM were off prototyping and designing the features of the next release while we were fully engaged testing the current release.
  2. The QA team divided the features between its members, taking into account interest and affinity. This was called "owning" a feature. When I very first started I was the owner of the new GridView and Login controls. At that point, the spec was pretty much finished, and the controls were already checked into the build.
  3. The first thing to do was to write a test plan. What this meant was to define the totality of tests that you planned to verify and automate. These documents were very descriptive and usually amounted to many pages. We entered the tests into an internal tool that DevDiv uses called "Maddog".
  4. This test plan got reviewed by the PM and the Dev of the feature and by fellow testers to try and uncover anything missing or worthy of expansion.
  5. Next came the main testing phase where the tester divided his/her time between performing "ad-hoc" (which meant playing with the features trying out all sorts of scenarios to uncover defects), and writing automation.
  6. Progress was tracked by the number of tests automated that were defined in the test plan. For example, a test plan defined 200 tests (with different priorities) and we would report status of how many tests were left to automate and that's how we scheduled the remaining work.
  7. One thing to note is that the Developers were not writing any UnitTests back then.
  8. At some point, the team would start to kick off runs. This means grabbing all tests in the repository and running them on many different platforms and configurations to uncover any possible defects. In this phase the time of a tester was divided between writing automation and analyzing the inevitable failures of each run.
  9. This is were we entered the "stabilization" time for the release that I mentioned at the beginning. Making lab runs on different SKU's, languages, side by side, x32 and x64, stress, perf, etc.
  10. After many months, at last we would have finished automating all the tests and have them executing on all the runs. Any bugs found were logged, and either fixed or postponed. At this point each feature owner would sign off, and the product would be shipped. (A similar process was repeated for every beta release as well).

 

Problems with this model

  • Over-Automation
    • By placing too much focus on automation, the tester spent more time writing and debugging a huge amount of regression tests rather than trying scenarios on the product.
    • There was a philosophy of leaving no stone unturned and to automate everything that you could think of.
  • High cost of change
    • ASP.NET is a framework, and frameworks go through many revisions and changes before they are released. However, our QA process was not designed to accommodate this. The QA methodology consistently tried to lock down details too early.
    • We spent a lot of time writing fully detailed test plans based on the spec for things that were bound to change a lot, and immediately after test plan creation we launched ourselves into an automation frenzy.
    • What ended up happening is that changes in the design of a feature where disastrous, the cost of updating all test plans and all the previously written automation was very high.
  • No UnitTests
    • The QA team was responsible for testing everything, from passing every possible value to each public method of a class to end to end integration tests. This amounted to a TON of tests written by QA.
    • Since the only automated mechanism to find regressions was the QA tests it was common for the builds to be broken regularly.
  • QA team found bugs to late
    • A side effect of our automation story was that scenarios were not verified until it came time to automate the test, which usually meant months after the feature was coded.
    • All this miss placed effort meant that bugs where found later rather than sooner.
  • QA team did not start at the same time as Dev/PM.
    • In some ways this fact set us up to be the long pole of the team. There were occasions where the Dev had completed all his/her work, but the QA team still had months and months of work to go through.
    • It was also common practice for the QA members to have little input on the design of the product. By the time we became fully engaged, most of the features were already designed and implemented.

 

How we survived

By reading this post you may be disappointed by our team, but keep in mind that I only focused on the "bad" things of the way we used to do things 6 years ago. I believe that regardless of the limitations of our approach the release had a high quality bar. The problems that I mention here, plus the problems with the Dev's process and with our automation infrastructure, all these together are one of the reasons why the .NET 2.0 release took such a long time. Luckily, most of these have been addressed during subsequent releases, and I'll go through the changes that we made and how well they worked in following posts. Maybe you can learn something from our experience...

 

I am also interested in hearing how you approach testing in your organization, what has worked well and what has not worked so well. I am looking forward to any comments or feedback that you may have. Stay tuned for part #2 :D

 

Federico Silva Armas
ASP.NET QA Team

by farmas | 3 comment(s)
Filed under: ,
Running the Lightweight Test Automation Framework for ASP.NET from a separate application
Tuesday, December 02, 2008 1:09 AM

In some instances it might be best if your tests did not run in the same application as the website being tested.  This is where the “IApplicationPathFinder” interface comes into play.  If you implement this interface you will see that it only has one method “GetApplicationpath.”  What this does is allow you to specify a prefix of sorts for the navigate method.  When you call navigate on a HTMLPage it uses a ApplicationPathFinder to get the location it should look for the website at.  For example, the framework will implement this interface and simply return the request path by default.  Creating your own ApplicationPathFinder class allows you to specify where to look for the website, there is nothing stopping you from telling it to look for “http://foo.com.”  The fallowing code shows a simple implementation of the IApplicationPathFinder interface, that points to a second test application. This code is place in the DrivePage that is located in the “Test” folder.

<script runat="server">
    public class TestApplicationPathFinder : Microsoft.Web.Testing.Light.IApplicationPathFinder
    {
        public string GetApplicationPath()
        {
              return "http://localhost/TestApp2";
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        Microsoft.Web.Testing.Light.ServiceLocator.ApplicationPathFinder = 
		new TestApplicationPathFinder();
    }
</script>

Okay, lets take a look at what is really going on here in the code.  First I choose to include the  implementation of the IApplicationPathFinder interface in the DriverPage, however, this could easily be moved to its own file/assembly.  Other than that the code is pretty straight forward, set the ApplicationPathFinder property on the ServiceLocator to an instance of your implementation.

There are numerous reason you might want to run the the test application separately from the site you are testing.  Here is a simple implementation that allows you to store your test application in a central location and pass the location of the site to be test to it.  You could use a URL like “http://localhost/Test?tag=localhost2&filter=true&path=http%3A%2F%2Flocalhost2&run=true” to the navigate and have the test page look for the site at “http://localhost2,” show only the test with the tag name “localhost2,” and run them once navigation is complete.  You could see how you could use this to set up a regression test bed, by storing all your tests in one location and pointing them to the proper servers for the pages.

<script runat="server">
    public class TestApplicationPathFinder : Microsoft.Web.Testing.Light.IApplicationPathFinder
    {
        public string Path { get; set; }

        public TestApplicationPathFinder(string path)
        {
            Path = path;
        }
        
        public string GetApplicationPath()
        {
            return Path;
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        string path = this.Request.Params["path"];
        if(!string.IsNullOrEmpty(path))
            Microsoft.Web.Testing.Light.ServiceLocator.ApplicationPathFinder = 
		new TestApplicationPathFinder(path);
    }
</script>
Hopefully this gives you some insight into the power that the Lightweight Test Automation Framework for ASP.NET has and how you can leverage that power.  You can download the framework here. Please let me know if you have any questions.
More Posts