The next version of BlogEngine.NET has been released and is available for download. This new version features several new upgrades including support for IIS7, a much newer version of the tinyMce WYSIWYG editor, support for WLW 2009, compiled extension support, and nested comments to name a few.
Arguably even more important for BE.NET users since the last release is the growing number of available themes, widgets and extensions for BlogEngine. A few noteworthy places to get themes is from this site that has aggregated themes from several locations (live previews available), from another theme aggregator with live previews, or directly from onesoft who has adapted several themes for BE.NET.
The documentation has also been updated and moved with this release. The new location is now at CodePlex.
If you're already using BE.NET, upgrading should be pretty painless -- especially if you haven't done too many customizations to files outside your theme. If you're looking to start blogging, BlogEngine.NET is a great blogging platform to go with. It runs right out of the box, and since it's open source, you can optionally customize any part of it.
BlogEngine.NET 1.5 can be downloaded here.
When integrating a website with 3rd party services such as search providers or payment processors, it's not an uncommon need to have a submit button that posts to that 3rd party's site.
ASP.NET 2.0 introduced the PostBackUrl property for controls that can initiate a post -- Buttons, ImageButtons, LinkButtons. With this property, you can have your page post to any URL. This is a somewhat decent solution to this problem. The major downside to PostBackUrl is it requires the visitor to have JavaScript enabled in their browser. If JavaScript is disabled, you end up with a normal postback to your own page. To me, this makes PostBackUrl not a very good choice to turn to.
The other day, I needed to send a visitor to a payment processor's site. My site was using master pages. I was collecting some preliminary information from the visitor on a Content page. Once collected, I was going to show them a confirmation of what they would be paying for and give them a button to move onto the payment processor. I decided to use the PostBackUrl property on this button.
The payment processor needed a few hidden fields included in the form submission. I put those hidden input fields including the values into non-server hidden input fields since I didn't want the Name or Ids of the input fields mangled by ASP.NET. The form looked good, and clicking the button took me to the payment processor's website -- but as soon as I got there, the only thing on the page was some error message complaining about incoming data (a very non-specific error message).
I knew their site and this particular landing page worked when I tested posting data in a non-ASP.NET environment. I confirmed the correct hidden input fields were being sent in the POST through Fiddler, but still no dice. The only explanation I could come up with is when using PostBackUrl, not only are my custom hidden fields being passed to this other site, but so are all the other standard ASP.NET hidden fields -- ViewState, EventValidation, etc. It's possible the payment processor's site was not expecting other form values to be passed to it.
So I decided to ditch PostBackUrl ... which was fine since I wasn't a big fan of PostBackUrl to begin with. I came up with a pretty simple solution to having multiple forms in this master page environment. The typical master page setup is where a <form runat="server"> tag in the master page surrounds the ContentPlaceHolder.
<body>
<form id="form1" runat="server">
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</form>
</body>
Now if you put your own <form> tag in the Content page, you'll end up with a form nested in another form. Nested forms are not valid. Here's what I did to avoid nested forms, but still end up with multiple forms.
<body>
<form id="form1" runat="server">
<asp:ContentPlaceHolder id="cphForm" runat="server">
</asp:ContentPlaceHolder>
</form>
<asp:ContentPlaceHolder id="cphNoForm" runat="server">
</asp:ContentPlaceHolder>
</body>
The master page now has 2 content place holders. One is wrapped in a <form runat="server"> tag, and the other isn't. The type of controls allowed when not wrapped in a <form runat="server"> tag is limited. For instance, you cannot have ASP.NET Buttons, TextBoxes and a number of other controls outside a <form runat="server"> tag. You can however use Literals, PlaceHolders and basically any HTML controls with a runat="server" tag. Here's the content page markup.
<asp:Content ID="cntForm" ContentPlaceHolderID="cphForm" Runat="Server">
<asp:PlaceHolder ID="phForm" runat="server">
<h2>Select an Item to Purchase</h2>
<asp:RadioButtonList ID="rblMenu" runat="server">
<asp:ListItem Text="Item 1" Value="item1" Selected="True"></asp:ListItem>
<asp:ListItem Text="Item 2" Value="item2"></asp:ListItem>
<asp:ListItem Text="Item 3" Value="item3"></asp:ListItem>
</asp:RadioButtonList>
<asp:Button ID="btnContinue" runat="server"
Text="Continue" OnClick="continuePurchase" />
</asp:PlaceHolder>
</asp:Content>
<asp:Content ID="cntNoForm" ContentPlaceHolderID="cphNoForm" Runat="Server">
<asp:PlaceHolder ID="phConfirmation" runat="server" Visible="false">
<h2>
Your Selected Item:
<asp:Literal ID="litSelectedItem" runat="server"></asp:Literal></h2>
<form method="post" action="http://www.example.com/">
<input type="hidden" name="myId" value="someID" />
<input type="hidden" name="itemCode" value="<%= itemCode %>" />
<input type="hidden" name="itemAmount" value="<%= itemAmt %>" />
<input type="submit" name="payNow" value="Pay Now" />
</form>
</asp:PlaceHolder>
</asp:Content>
The content page is making use of both content place holders. The top Content control contains a place holder, which contains a simple order form. The second Content control contains a place holder with its visibility set to false. So when the page is first pulled up, nothing in the second Content control is yet visible.
Once an item is selected, and the Continue button is clicked, a postback is done. During the postback, the placeholder in the first Content control is made invisible, and the placeholder in the second Content control is made visible. Because the second Content control (and all of its contents) are outside of the <form runat="server"> tag, we've achieved having two different, non-nested <form> tags on the same page. Here's what the rendered HTML looks like after the person has selected their item and is ready to be sent to the payment processor:
<body>
<div>
<form name="aspnetForm" method="post" action="Content1.aspx" id="aspnetForm">
<div>
<input type="hidden" name="__VIEWSTATE"
id="__VIEWSTATE" value="some long value" />
</div>
</form>
<h2>Your Selected Item: Item 2</h2>
<form method="post" action="http://www.example.com/">
<input type="hidden" name="myId" value="someID" />
<input type="hidden" name="itemCode" value="item2" />
<input type="hidden" name="itemAmount" value="30" />
<input type="submit" name="payNow" value="Pay Now" />
</form>
</div>
</body>
With this approach, I was able to post the form to the payment processor, and the form contained hidden input fields only relevant to the processor. This approach will work in most master page situations. It may be difficult to implement if you have server side controls requiring a <form runat="server"> tag in your master page -- outside of the main content place holder. In this case, it may still be possible to implement this two ContentPlaceHolder approach, if you do some juggling around of your controls and/or layout. However, in a lot of situations, this is a practical way to achieve multiple form tags in an ASP.NET site.
Compared to Firefox and Chrome, IE has always had a very plain rendering when viewing the source of an HTML page. The source just shows up as plain text in Notepad. HTML is of course just plain text, but Firefox and Chrome add coloring for matching HTML tags which makes looking at the source a little more pleasant.
IE8 now does what these others browsers have been doing. The HTML source no longer is displayed in Notepad, but in an IE source viewing pop-up window. This IE source viewer now does tag coloring and even includes line numbers. This is a nice little improvement.
I did notice one other interesting feature when doing a View Source in IE8. On the File menu of the source viewer, if you select 'Save', you have a choice to save the HTML Source (nothing special here) or save the 'Formatted HTML View' (screenshot below). This "save formatted HTML view" will create an HTML file of how IE8's source viewer is displaying the source -- including the tag coloring. You can then open up that saved HTML file in any browser to have the source display exactly as it does in IE8's source viewer.
The file size of the "formatted html view" is considerably larger than the size of the plain HTML source without the formatting. For instance, for a particular 45 KB HTML page I tried this in, the formatted html view file is 452 KB.
I'm guessing the new color tags and syntax highlighting in the new source viewer was done via HTML markup. So it probably wasn't a big deal for the IE team to just include this new save as 'Formatted HTML View' option -- since the formatted HTML source was already there. In any event, it's nice it's there for whenever the need of the formatted html source could be used.
Bloggers often have a few words they like to say about themselves. With BlogEngine.NET, there are a few ways to achieve this. In the default BE.NET installation, a TextBox widget titled "About the author" is already in the blog's sidebar. The author can just click 'Edit' and quickly describe themselves using the WYSIWYG editor. BE.NET users will find the TextBox widget is a very powerful widget because it's generic enough to display virtually anything.
Another option for About Me is to create a Page in BE.NET, and add a link on your blog to the About Me page. The main difference with this 'Page' approach is the About Me content is only seen on the About Me page, rather than the widget approach where the About Me content is shown on every blog page in the sidebar. With the Page approach, you also get a lot more room horizontally which is nice if you have some pictures or other space consuming content. I'm currently using the Page approach for the About Me page on this blog.
A third approach which is very similar to using the TextBox widget described above is to use an "About Me" widget. BE.NET allows each editor/administrator to create their own profile on the Profiles tab in the control panel. On the Profile page, each editor/administrator can enter details such as their Name, Phone Number, Address, a Photo image, and last but not least, About Me! The About Me box is a WYSIWYG editor. Currently, profile information entered is hardly used anywhere within BE.NET. The information is available, however, through the AuthorProfile class in BlogEngine.
The AboutMe widget displays the About Me text entered for a user on the Profiles tab in the control panel.
If multiple editors/administrators have been created on the Users tab in the control panel, a separate profile can be created for each one of these users on the Profiles tab. Because multiple profiles may exist, the AboutMe widget needs to know which profile's About Me content it should display. The edit control in the AboutMe widget allows the user to choose the profile to display the About Me content for. Because the settings for each widget are stored independently of one another, multiple AboutMe widgets may be added to the blog -- one AboutMe widget for each profile.
The AboutMe widget consists of the 2 required files you find for widgets -- widget.ascx, widget.ascx.cs. The edit control is made up of edit.ascx and edit.ascx.cs. A download link for the entire widget is available at the bottom of this post if you are interesting in trying it out in your own BlogEngine.NET installation.
widget.ascx (view code)
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="widget.ascx.cs"
Inherits="widgets_AboutMe_widget" %>
<asp:PlaceHolder runat="Server" ID="phAboutMe" />
widget.ascx.cs (view code)
#region Using
using System;
using System.Web;
using System.Web.UI;
using System.Collections.Specialized;
using BlogEngine.Core;
#endregion
public partial class widgets_AboutMe_widget : WidgetBase
{
public override void LoadWidget()
{
string CacheKey = "widget_aboutme_" + this.WidgetID.ToString();
string ProfileUserName = (string)HttpRuntime.Cache[CacheKey];
if (ProfileUserName == null)
{
StringDictionary settings = GetSettings();
if (settings.ContainsKey("UserName"))
ProfileUserName = settings["UserName"];
if (ProfileUserName == null)
ProfileUserName = string.Empty;
HttpRuntime.Cache[CacheKey] = ProfileUserName;
}
if (string.IsNullOrEmpty(ProfileUserName))
{
// Find the first profile with About Me content.
foreach (AuthorProfile profile in AuthorProfile.Profiles)
{
if (!string.IsNullOrEmpty(profile.AboutMe))
{
ProfileUserName = profile.UserName;
HttpRuntime.Cache[CacheKey] = ProfileUserName;
break;
}
}
}
if (!string.IsNullOrEmpty(ProfileUserName))
{
AuthorProfile profile = AuthorProfile.GetProfile(ProfileUserName);
if (profile != null && !string.IsNullOrEmpty(profile.AboutMe))
{
phAboutMe.Controls.Add(new LiteralControl(profile.AboutMe));
}
}
}
public override string Name
{
get { return "AboutMe"; }
}
public override bool IsEditable
{
get { return true; }
}
}
edit.ascx (view code)
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="edit.ascx.cs"
Inherits="widgets_AboutMe_edit" %>
<div>
<h3>Select the Profile to show About Me for</h3>
<div id="noProfilesAvailable" runat="server">
No profiles have yet been created.
</div>
<asp:RadioButtonList ID="rblProfileToDisplay"
runat="server"
DataTextField="UserName"
DataValueField="UserName">
</asp:RadioButtonList>
<br />
<div>
Note: Make sure you have entered About Me content for
the selected user above. This content should be
entered on the Profiles tab in the Control Panel.
</div>
</div>
edit.ascx.cs (view code)
#region Using
using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using BlogEngine.Core;
#endregion
public partial class widgets_AboutMe_edit : WidgetEditBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string SelectedUserName = null;
StringDictionary settings = GetSettings();
if (settings.ContainsKey("UserName"))
SelectedUserName = settings["UserName"];
if (!string.IsNullOrEmpty(SelectedUserName))
{
if (AuthorProfile.GetProfile(SelectedUserName) != null)
rblProfileToDisplay.SelectedValue = SelectedUserName;
}
rblProfileToDisplay.DataSource = AuthorProfile.Profiles;
rblProfileToDisplay.DataBind();
noProfilesAvailable.Visible = rblProfileToDisplay.Items.Count == 0;
}
}
public override void Save()
{
StringDictionary settings = GetSettings();
settings["UserName"] = rblProfileToDisplay.SelectedValue;
SaveSettings(settings);
HttpRuntime.Cache.Remove("widget_aboutme_" + this.WidgetID.ToString());
}
}
AboutMe Widget Download (2.4 kb)
Thought I would pass on a small issue I ran into recently when redirecting a user from an HTTPS page to an HTTP page. When the person reached the HTTP page, Request.UrlReferrer was null. There are browsers, add-ons, proxies, security suites and other entities that will strip the url referrer sent to a web server, but that was not the case in this instance as this was happening to me when testing a new site and didn't happen when I was redirected to the same page from an HTTP page.
It turned out this is a pretty standard security feature implemented by browsers to omit the referrer when a user is redirected from an HTTPS page to an HTTP page, or when a user clicks on a hyperlink taking them from an HTTPS page to an HTTP page.
This behavior does make sense considering sensitive information may be stored in query string parameters of the HTTPS page url. I found this MS KB article explaining this behavior. In the article, MS suggests some sites may even store credit card data in a url. Credit card numbers in a url ... really?? I was thinking more along the lines of private session ids in the url. I don't think I'd feel too comfortable shopping at a site if I saw my credit card number in the address bar ;-)
February 25, 2009 5:10 PM
Ever since setting up this blog which is powered by BlogEngine.NET, I spend a small amount of time everyday at the BE.NET code hosting area at CodePlex. Mostly I participate in the discussions there and have even submitted some bug reports in the issue tracker. It's been a rewarding experience to be able to contribute to an open-source application used by probably thousands of bloggers out there.
However, my time spent at CodePlex has been anything but pleasant. CodePlex is a website built, maintained and run by Microsoft. It's a place anyone can store their open source projects for free. There's other code hosting services out there such as SourceForge and Google Code.
Allow me to vent a bit and list the problems I find with CodePlex.
- SLOW, SLOW, SLOW! CodePlex has to be the slowest website on the face of the internet. Just about anything you do on CodePlex takes 10 times longer than it takes to do the same type of action on other websites. Just pulling up a simple discussion takes 3 seconds for a simple GET request. That 3 seconds is a best case scenario. Almost on a daily basis, CodePlex will start slowing up. Posting a message can take 10 to 20 seconds or even minutes at times. Searching the Discussions takes too much time. Even worse is searching the Issue Tracker. There's been times it takes over 2 or 3 minutes for search results to come back.
- Server side errors. I don't think a day has gone by that I haven't received at least one server side error while on CodePlex. The typical error is an XML parsing error. Sometimes the response from the server takes so long that I just get a general timeout error or page cannot be displayed error.
- Issue Tracker editor. When creating an issue in the tracker or adding a comment to an issue, instead of getting a WYSIWYG editor like you get in the Discussions, all you get here is a plain old textarea. And a pretty small textarea at that. It's a common need to paste some code into the issue tracker -- after all, that's what CodePlex is for! But all leading spaces are lost when saving your post. This means properly indented code is no longer indented, looks crappy and is difficult to follow. You also can't edit anything you posted in the Issue Tracker. I'm not sure why you can edit messages and get a WYSIWYG editor in the Discussions area, but not in the Issue Tracker.
- Spam. For a while, various spammers kept posting spam messages in the Discussions area. CodePlex doesn't seem to have any mechanism to report spammers. The spammers would post messages with some relevance to technology, but the messages had nothing to do with the Discussion at hand. Throw in CodePlex's speed woes, and trying to find the real messages while sifting past the spam equates to a waste of my time.
- Team Foundation Server unavailable. The source code for projects at CodePlex is stored in a TFS database. Every now and then, I get messages stating TFS is unavailable for short to long periods of time. While TFS is unavailable, the Issue Tracker and Source Code areas are completely unavailable.
- RSS Feed Lags. CodePlex uses caching for its RSS feed which is a good idea. New items to be added to the feed seem to normally show up within an hour. There are times, however, that certain items may take several hours before they appear in the feed.
I wouldn't recommend CodePlex to anyone looking for a place to host their code. I've not yet spent any time at Google Code, but I do visit SourceForge on rare occasion. I recall no slowness or errors while browsing through the messages in the discussions at SourceForge.
I'm definitely not the first and certainly won't be the last one to bring up some of these CodePlex problems. Dave Ward had this great blog post where he broke down some of the massive performance inefficiencies in CodePlex with its voting system. It's disturbing too since this isn't the only Microsoft website to suffer from a performance / reliability standpoint. A few years ago when I was spending some time on the www.asp.net forums, forum searches were very slow. Apparently the asp.net website was completely down about a week ago. The Microsoft blogs website is another site where blog posts and paging through the posts often results in long wait times.
I'd love to see BE.NET move and host its codebase elsewhere. I unfortunately haven't seen any sign of Microsoft planning to fix CodePlex and they seem perfectly content with the way CodePlex is now. If they do fix CodePlex, great. I'm just constantly frustrated everytime I'm at CodePlex, and don't think a code hosting website (or any website) should be a hindrance or distraction from the real reason I'm at the site -- to participate in and have fun with a growing open-source project.
February 22, 2009 4:45 PM
In today's tabbed browsing world, maybe you find yourself opening and closing tabs left and right. If you're into the keyboard shortcuts, that would be Ctrl-T and Ctrl-W respectively
What if you accidentally close a tab? This happens to me most often when either I thought I was done with the website I was on, or I thought the tab I was on was a new tab with nothing to go 'back' to.
Fortunately, the good browsers out there offer a Ctrl-Shift-T keyboard command to open up the last tab you closed. I like to think of it as the "ctrl-z of browsers". This keyboard command is very handy because you can actually keep pressing Ctrl-Shift-T to open up all the tabs you previously closed. Each tab you re-open not only opens up with the page you were on when the tab was closed, but the browser history for that tab is also preserved. So once you re-open a closed tab, the entire 'Back' button browser history is there.
Ctrl-Shift-T works in Firefox and Chrome. It doesn't appear to work in Safari/Windows and doesn't work in IE7. That's what I meant by this keyboard shortcut working in 'good' browsers
It does appear to work in IE8.
Since discovering this keyboard command it's something I use probably at least once a day. Sometimes many times a day!
February 21, 2009 9:04 PM
Yesterday I was working on a C# web site project in VS 2008. After making some modifications to a class in the App_Code folder, when I later went back to the codefile for an ASPX webpage, intellisense no longer recognized the class or methods of the class I was earlier changing. I wasn't getting the normal coloring for the class name and no intellisense listing of the class's methods. Right-clicking on the class or its methods and trying to 'Go to Definition' resulted in a message stating it couldn't find the definition (or something to that effect).
I could build and run the site with no errors. I tried closing and re-opening the solution, tried closing and re-opening VS, tried deleting all the files in the Temporary ASP.NET Files directory, tried restarting IIS and tried rebooting the PC. Nothing worked. After rebooting the PC, intellisense even stopped recognizing another App_Code class. Life without intellisense sucks!
What finally did work was to right click on these classes in the solution explorer, select the 'Exclude from Project' option which excludes the files from the project by giving the files .exclude extensions, and then I right-clicked on the files again to Include them in the project. That cured intellisense.
February 8, 2009 11:03 AM
I was recently buying a SSL certificate and remembered being on a website where some HTTPS pages had a WWW host in the url and other HTTPS pages didn't have the WWW prefix. I vaguely remember from when I last bought a SSL certificate, you would normally indicate the host name you are buying the SSL certificate for -- whether it be www.example.com, secure.example.com, members.example.com. And the SSL certificate would only be valid for that exact host.
There's also wildcard SSL certificates that cover all hosts. These certificates are a lot more expensive. Wildcard certs cover secure.example.com, www.example.com, anything.example.com. Interestingly, it appears SSL certificates do not necessarily cover cases where you have no host -- i.e. example.com. An example of this is the customer login page at the hosting company where I have this blog hosted. If you examine the certificate, it looks like it uses a wildcard certificate as the common name is *.webcontrolcenter.com. However, if you remove the WWW from the url, my browser (Firefox) warns of an invalid security certificate. What a sour deal ... you spend the extra money for a wildcard certificate and it doesn't even work when there's no host name! I'd suspect that may not be the case with all wildcard certificates ... it probably depends on who you buy the certificate from.
In my case, I didn't need a wildcard security certificate, but I was hoping I could cover both www.example.com and example.com. One option would be to buy two separate SSL certs, one for www.example.com and one for example.com. Fortunately a few SSL certificate sellers cover both WWW and no host in a single certificate as a standard feature. GoDaddy was one of the two places I found that offered this. I didn't search around that much though. GoDaddy's SSL certificate is dirt cheap at just $30. What a great deal considering how expensive SSL certs once were.
GoDaddy briefly explains this feature in this help topic. What I wasn't sure about was when creating the CSR that you submit to the SSL certificate seller, I wasn't sure if I should put www.example.com or example.com for the common name (CN) field in the CSR. The website I had previously seen where HTTPS worked with and without WWW was using a GoDaddy SSL cert and the common name on the cert was just example.com without the WWW. And then I ran across this post where the blogger discussed this feature of the GoDaddy SSL certificates, and towards the end of his post, he says,
Therefore, to summarize the solutions, I can say that you can use a wildcard certificate or issue two separate certificates that both cost money and may not be a good option for many cases. You can also look for a SSL certificate issuer that automatically includes the base domain name when you generate one for the domain name with "WWW."
This last part sounds like you would generate a SSL certificate with www.example.com as the common name -- not example.com. But, when I examined his SSL certificate, the common name doesn't have the WWW! Anyhow, I decided to include WWW in my common name field, sent the CSR to GoDaddy, received the certificate, installed it, and the good news is my site works both with and without the WWW. Looking at the certificate fields when examining these SSL certificates from GoDaddy, of the ones I've seen, they all have a field named "Certificate Subject Alt Name" where the field value is:
Not Critical
DNS Name: example.com
DNS Name: www.example.com
This may be the field that tells the browser the certificate is valid with or without the WWW. In summary, it appears you can probably put either www.example.com or example.com in the common name (CN) field of the CSR when purchasing the SSL certificate from GoDaddy. I included the WWW in my common name, and the certificate is valid with and without the WWW host.
February 1, 2009 12:14 PM
Beta 1 for Windows 7 was just released last week. I haven't downloaded it, but happened to catch this blog post from the Windows team on Windows 7. The official name for the next version of Windows will be Windows 7. This name obviously came from the fact that the internal version number of this release of Windows would be version 7.0. Windows 2000 is 5.0, XP is 5.1, Vista is 6.0, and this next one will be 7.0, hence the name Windows 7. Apparently not! The RTM version of Windows 7 will have an internal version number of 6.1.
Microsoft's reasoning for this boils down to maximizing application compatibility. Programs and drivers that require themselves to be run on a build of Windows with a major version of 6 (or sometimes less) would continue to run on Windows 7 which will still have a major version of 6. Yet, I thought Microsoft's been saying Windows 7 is a brand new operating system, and not just a more stable or improved version of Vista?! Many have suggested the name for the upcoming OS should just be Vista SE, as in second edition. Not a bad idea, actually!
I'm perplexed to understand the situation. I can already see this versioning issue causing confusion by users trying to troubleshoot OS and application software issues on forums and other support channels. The reason a major build number exists is to differentiate between major builds of software. Although Windows 7 is looking a lot like a less resource hungry version of Vista, there's still the whole new touch screen functionality being added and early reports of Windows 7 Beta 1 show that some people are experiencing crashes in Beta 1. Being just Beta 1, crashes are expected, but if Windows 7 was just a more solid Vista with some minor enhancements, then I wouldn't expect any crashes, nor would I expect it to take 10+ months from this Beta 1 release until Windows 7 RTM's at the end of 2009 or early 2010.
From my point of view, it's quite evident there's enough changes going on in this upcoming release of Windows that an increment in the major build number should be made. Although Microsoft is trying to maximize application compatibility by keeping Windows 7's major build number at 6, I wouldn't be surprised if there will be some applications that allow themselves to run or be installed on Windows 7 because the major build number is 6, but crash or operate incorrectly because of differences in Windows 7 and Vista. I can only assume the version of Windows that will have an internal build number of 7.0 is going to be the next major release after Windows 7. What a needlessly confusing situation!