C# and ASP.net Whidbey

It’s been a while, but now that ZBB is over, and I’ve got a little bit of time, I thought I would write a little about what has been occupying so much of my time lately.

C# and ASP.net Whidbey

One of the things I’ve been working on a lot lately is the interaction between C# and ASP.net for Whidbey, so I thought I would write a little bit about what’s going on in that space.

What’s new?

One of the major differences (at least as far as it’s impact on me goes :) is that in Whidbey, you will be able to create inline blocks of server executed script, just like in the old days of ASP. Well not quite, because in ASP.net Whidbey, you will also get support from the individual language of your script block. This means that colorization, intellisense, and all of those other language specific features should also work in inline script.

What does it look like?

Let’s say you create a web form with a simple button on it. The code for the page might look something like this:

 

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<script runat="server">

    uint _numClicks = 0;

   

    void Button1_Click(object sender, EventArgs e)

    {

        ++this._numClicks;

        this.Button1.Text = this._numClicks.ToString();

    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Untitled Page</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        <asp:Button ID="Button1" Runat="server" Text="Button" OnClick="Button1_Click" />

    </div>

    </form>

</body>

</html>

 

 

How does it work?

What happens behind the scenes is that ASP.net generates a complete class which you as the user never see. The C# language service however, sees both of these two things: The actual default.aspx file (what we call the primary buffer), and the C# class containing the inline as well as some other stuff to pull it all together.

This makes it a little difficult for the language service team, because whenever we look at a position in the file, we need to think about which file we’re looking at: it could be either the primary or the secondary buffer depending on what interface we’re talking through. Generally, if we’re talking to either ASP.net team, or the core editor, we need to use primary buffer co-ordinates, but if we’re talking to the C# compiler we need to use secondary buffer co-ordinates.

We’ve pretty much adopted the rule that whenever we talk to the core team or the asp.net team, we’ll translate to/from primary buffer co-ordinates, but the rest of the time we’ll use secondary buffer co-ordinates throughout the language service and compiler. This has been the source of many bugs in our ASP.net integration, because throughout the 7.0 and 7.1 (or RTM and Everett as we call them), cycles, we had an implicit assumption that the two sets of co-ordinate systems would be identical. Thus most of the bugs in C# and ASP.net integration have come from places where we missed the fact that we were interfacing with another component.

Project system issues

Another interesting area when it comes to C# and ASP.net integration is how we interact with the project system. Within the ASP.net world, each individual web form looks to the language service like a separate project. The reason for this is that it’s not possible to reference code from a different page, and the only way to enforce this in the C# compiler is to have them generate different assemblies. The only way to get them to generate different assemblies is to use different projects. This isn’t really a big deal; it just means that opening a web form in the editor is a little bit more expensive than opening a normal C# file in the editor.

What is interesting is that the project system only tells the language service and the compiler about a page when the page is open. There are good reasons for this. Imagine you are working against a remote web server, and the site you are working on contains hundreds of pages. You don’t want VS to have to hit the webserver to retrieve every page when you open the solution. Instead, VS hits the server only on demand, when you actually try to open the page.

“So what?” You might ask. “How will this affect me?”

Well, this affects quite a number of C# Intellisense features, specifically, those that deal with finding things in code. The types of things I’m talking about are the new “Find all references” command, and the new Refactorings. The basic problem is that if a page is closed, we have no idea that it exists, so we can’t look for references to things in it.

Let’s take an example. Imagine you have a class in your Code Directory (the shared repository all pages can access), and you have a reference to it in a bunch of web pages. You decide that you don’t really like the name of the class so you decide to rename it. You can use C#’s handy new Refactor->Rename command to do it. The problem is that if all of the pages that reference it are not open, we don’t know they exist, so we don’t look for references in them, and they don’t get updated. This isn’t a problem for refactorings of things defined in a web form, since they can’t be shared with other pages, but it is a problem for things defined in the Code directory.