The Bitland Prince Development Blog

.:: development horror stories ::.

Let me state it : Diavolo is ASP.NET WebForms. I know most people is now fond of MVC and that stuff but let me say I tried to use one of those stars lately, that is Orchard, to gain some knowledge about new kids on the block and, while I find it interesting, Orchard is very convoluted, like probably most of those projects. Don't get me wrong, I'm sure it has lots of potential and lots of innovative things since it's probably one of the most interesting project which grew up lately, however WebForms is a different beast. Nothing currently matches its flexibility and, via WebControls and extensions, its ability to become anything and everything you want. Of course, there's some overhead in it but in my opinion it's worth the pain.

So how does Diavolo compare to new Web 2.0 stuff ? Well, I should say XHTML conformance wasn't my top priority as a start. I'm now focusing on ironing things together and then I will also focus con standards. I consider that an optimization rather than a mandatory things and since we didn't have our first release (which would probably be Diavolo v4.3), I'm not putting too much emphasis on XHTML conformance yet.

So what's in Diavolo now which relates to Web 2.0 ? First of all, I was really happy to be able to create a container which proved itself flexible enough to accomodate many different technologies. Diavolo currely is WebForms (never forget!) but supports new ASP.NET 4 routing for WebForms meaning you can create your stuff eliminating postbacks, if that's what you want, and relying on URL parameters. To make it easier, it has a very basic but flexible parameters engine which will automatically detect your parameters and make them available to your code without relying on parsing query strings. So when in your "news" module, you can rely on postbacks to display selected news or you could redirect to your "XXX/read/167" URL where read is your parameter to tell your module it needs to load a specific news and "167" is your news ID. That's similar to MVC actions / controllers though Diavolo is not MVC. You could also redirect to "XXXX/year/2011/month/10/" to list all news for October 2011 and so on. Whether you prefer to use postbacks or URL routing, that's up to you but that opens new possibilities for SEO lovers. And we all love SEO stuff.. Wink

Then you have MS AJAX Extensions and that means UpdatePanels, AJAX Control Toolkit and everything from MS AJAX arsenal. I agree MS AJAX Control Toolkit slowed development a lot lately but UpdatePanels are, in my opinion, great stuff. Plus, you can use UpdatePanels with other AJAX frameworks to provide AJAX development and newer stuff. I don't really know if AJAX CT will still be around in a year from now since it really seems things kind of halted, however a few extenders are very useful (validator callouts, for ex.) so it's very nice to have Diavolo support both UpdatePanels and AJAX Control Toolkit, even if you won't use them.

Then you have JQuery. I agree JQuery is where most AJAX development is happening and you will find lots (and I mean: lots!) of JQuery resources, scripts, widgets, plugins and so on. So for serious Web 2.0 development, we had to support JQuery and... I did it ! Diavolo fully supports JQuery and, best of all, it allows to mix and match jQuery and MS AJAX framework together. I love those cool JQuery UI dialogs opening on my pages and displaying UpdatePanels which will make it possible to add server-side code to handle your AJAX stuff directly in your page and that means events and so on. I also love the idea to be able to control my JQuery scripts right from code-behind by emitting my management scripts right from server-side code. That makes it possible not to have logic in your JS scripts and only care to manage things server-side. And you still have power of JQuery at your disposal and that means all the cool stuff you can find on the Web. JQuery is a must and we have it.

Finally, VisualWebGUI joined the camp. Let me say that being able to integrate so different technologies is really a proof of stability for Diavolo and a proof of flexibility as well. VWG is king for AJAX development and it makes difficult tasks very to handle. Kind of Web the way it should have been since the start. Most administrative tasks in Diavolo will be handled by VisualWebGUI and they are being rewritten right now (for example, Dashboard management is know a VWG module) and it's so cool to be able to control JQuery right from VWG.

So here you go: WebForms, SEO-friendly routing, JQuery and VisualWebGUI... what would you ask more for Web development ? Cool


While I didn't post that much lately, work around Diavolo is going on. I have to say that I'm appreciating flexibility I was able to achieve everyday more ! Now the good news: I recently took some time to integrate VisualWebGUI support into Diavolo and I have to say it worked like a charm ! I already had some experience in integrating VWG into WebForms applications since I did that a few months ago in a different project. But the good news is everything works as expected, including all things which were supported in the past !

As you might know, VWG now uses JQuery as its rendering engine. Not that JQuery is exposed to VWG developers but VWG does its magic undercover. I was a bit skeptic that I could stick everything together and make it work but it was easier than expected. VWG runtime has been integrated in a pretty easily way but the most charming thing is how you can call JQuery from VWG to run your JS code inside a page.

VWG has a nice theming system but that didn't really took off. Core developers and a few users took time to develop a few themes for VWG apart standard ones but most of them don't really work as expected in modern browsers. In most cases, you end up having to use Windows Vista theme which they have implemented in a very good way but it's not what you would expose on your website. Consider dialogs, for example... plus, I worked to integrate JQuery UI into Diavolo so I was using that engine for dialogs and so on and I didn't want to use some Vista-like stuff.

To my suprise, I had to work a few hours and I was able to allow VWG forms to call JQuery and JQuery UI code to display things. Quite awesome!

In a few days, I will take care to convert all administrative logic (pages, properties for modules and so on) to VWG to achieve an easier way to manage things while still allowing modules and hance pages to work as expected without any VWG code.

All in all, that was a very good achievement as I'm now able to exploit VWG whenever I need to obtain quick development/deployment or complex interation with users.


It's been a while since my last post but I have to say that many things moved in past 3 months. Today I want to blog about a new cool feature which I added a few days ago : a very basic intra-module communication system which will allow modules to send and receive requests among each other during page life-cycle.

I know you MVC kids won't easily understand what the problem is but for WebForms developers there has always been a a quite annoying problem: when processing web controls, expecially custom controls, those which will be processed earlier in page life-cycle have no way to respond to changes performed by controls which get processed later. While this is acceptable in many cases and that's the way most framework works (including MVC), real big advantage of WebForms is to clear ground for advanced processing. That big advantage over other technologies is someway limited by restricted communications between controls since webcontols usually change their processing based on postback data or query string parameters.

Consider this example: I have many controls on my page and one of them is showing a list of pictures which are tagged. Another controls is displaying a list of blog posts and those are tagged as well. In my page life-cycle, webcontrol for pictures get processed before the one displaying posts so if I decide to select a post to read it, maybe inside the very same window, when my postback event gets processed my list of pictures have already been generated since that webcontrol will be processed earlier. So, in that case, how can I generate a list of pictures based on post tags when a user selects a specific post to read it ?

That seems to be an esotheric question for most Web developers but having such feature would be very good in a WebForms context and that's why Microsoft created its connections in WebParts Framework. Connections just do that: take data from other WebParts to create custom processing.

A MVC kid would probably laugh at this problem and state that he/she could simply generate an AJAX Javascript call to a controller and pass it a post id which could be used to generate a list of pictures to show inside pictures box. Nice and true. Except that you won't have clean and nice separation between modules (webcontrols) and independence about them. MVC solution would make posts list (or page) dependent on pictures while WebForms solution would allow both controls to be 100% independent from each other. Admins could also remove one of them and page will keep working.

So I found a nice solution which is both easy to implement and effective... at least I hope !

Each DiavoloPageBase class now has a ModuleCommunicator class inside of it, which gets initialized on page Init. Each module can subscribe this class to receive notifications from other modules where a notification is simply a combination of a name (roughly equivalent to event name), a dictionary of parameters and a flag which specifies if that call is an AJAX call.

Each module can subscribe to receive notifications (events) by simply:

CType(Page, DiavoloPageBase).ModuleComm.AddNotification(AddressOf EventTarget)

        Private Sub EventTarget(ByVal Name As String, ByVal Params As Dictionary(Of String, String), Optional ByVal IsAJAX As Boolean = False)
            ' Code here
        End Sub

First line of code will add a delegate (whose name is EventTarget) to current page module communicator. From now on, EventTarget will receive notifications from other modules and module can trigger a notification event this way:

Dim Params As New Dictionary(Of String, String)()
Params.Add("OrderID", "12345")
CType(Page, DiavoloPageBase).ModuleComm.Notify("ShowOrder", Params, True)

In this example, we create a new dictionary for parameters to pass a new order ID and then we invoke Notify for a "ShowOrder" event (or trigger name, or whatever you want to call it).

Our EventTarget procedure will receive these parameters. Notice that EventTarget will receive ALL notifications and thus it should check event name to understand if it has to handle that specific notification. Also notice that notifications targets (i.e. procedure which will be invoked) cannot modify parameters and pass them on.

Automatic subcriptions

We borrowed this idea from DotNetNuke (thank you guys!) though our implementation is quite different. Basically, a module could implement interface IDiavoloCommModule to be automatically subscribed to notifications. When adding modules, Diavolo checks if module implements that specific interface and, if that's the case, it will add a subscription to mandatory procedure which has been defined by module as part of implementation of IDiavoloCommModule.

That makes subscribing to notifications very easy and straightforward. However, that's not mandatory because, since ModuleCommunicator class is exposed as page property, modules can also decide to explicitly subscribe and unsubscribe. That might be helpful if subscription is only needed as an answer to specific conditions.

Difference among Diavolo implementation and WebParts connections

Since the purpose of this new extension is somewhat close to WebParts connections, what's the difference between them ? Well, Diavolo Module Communicator is less formal and, in my opinion, less flexible than our implementation which is quite open. WP connections goal is to automatically share data among controls and they require a less flexible approach which force developers to decorate their code with specific attribute. This is less flexible because :

  • consumers just pull data out of providers: nothing more;
  • connection only happens during page pre-rendering;
  • you cannot unsubscribe because your connection has been in code via attributes;
  • you only have one way to communicate, i.e. from providers to consumer, and nothing else.

In our implementation, notifications can happen anytime during page life-cycle and notifier aren't just providing data to consumers. In WP implementation, my consumer gets all relevant data in a property which also requires (in most cases) to define an interface to give shared data a structure, as long as you don't need a primitive type. Diavolo implementation is less formal since you could stick all information you want inside a dictionary. Purists would say formal is better but I'd say that flexible is better.

Moreover, with connections you only have one way to communicate and a single provider with multiple consumers which, by the way, must be aware of providers in order to properly function. Our implementation doesn't require module to be aware of each other but just to agree on a name and parameters. Knowing that a blog module would broadcast blog post ID when displaying its content, another module only need to know trigger name and parameter name to receive such notification.

Modules can freely communicate among each other as there's not a clean separation between providers and consumers: modules can invoke each other multiple times. For example, blog module could broadcast post ID it is showing and a pictures gallery module could use post tags to display relevant pictures but at a later time in page life-cycle, another module could use same tags to download a RSS feed and discover new tags which could be relevant for the same post and broadcast them as well. Gallery module could then expand / refine its search for pictures based on those new tags and a message from a module appearing later in page life-cycle. That looks flexible to me !

Comparing to DNN  module communication

Things are very close to DNN implementation but again our implementation is less formal and more open. However, a cool idea for DNN is to be able to pass sender control reference which might be useful in some cases. That's something we could decide to implement at a later time. However, we will probably keep our ability to pass a dictionary of strings in order to let module basically communicate anything.

Other than that, idea behind Diavolo Module Communicator is very close to DNN one.


Diavolo localization subsystem is now 100% ready. I have to say I'm really happy about this result because now everything is in place and I can really appreciate its strength, thank to .NET framework structure.

As I wrote in my last post, all localization features are supported and can be used but what I find extremely useful is inline (literal) expressions. While Javascript is getting stronger and stronger for advanced UIs, JS code is usually markup-based and in that context .NET developers have two choices: embedding JS scripts or emit them, meaning using server code to emit client-side scripts. The second way makes it easy to support localization and other customization while first one is easier to use and maintain. Diavolo now makes it possible to produce markup like this:

 

<ul id="sdt_menu" class="sdt_menu">
		<li>
			<a href="/">
				<img src="/images/2.jpg" alt=""/>
				<span class="sdt_active"></span>
				<span class="sdt_wrap">
				<span class="sdt_link">
<asp:Literal ID="Literal1" runat="server" Text="<%$ Resources:system, home >" />                                                                                </span>
					<span class="sdt_descr">Torna alla pagina<br />principale del sito</span>
				</span>
			</a>
		</li>

 

This is a simple unordered list which can be used to create menus and other elements and to support localization and customization you can simply replace relevant code (for example, menu items or text) with a simple server-side code and leave other stuff intact. At run time, this code:

<asp:Literal ID="Literal1" runat="server" Text="<%$ Resources:system, home%>" />

will simply be replaced by relevant text, using different languages according to user locale. That's exetremely useful per se but you must also consider that things get very juicy when you couple that with administrator's ability to edit keys in a very easy way inside portal itself. Following picture shows a (very basic) localization editor where portal administrators can explore keys for a specific category and locale:

On top of page you will find a list of categories (more about them in a moment) and locales while table shows all defined keys and their values. Single items can be edited and updated by portal administrator or even deleted. Unless they are system keys, the meaning of which I will explain in a moment. Inline editing makes it very easy to change key values and browsing among locales is also very easy. I also plan to add a way to filter items, maybe by specifying part of the key. When you will have tens of keys, even if they will be sorted, you need a quick way to find a specific key or at least a group of them.

Advanced features
Earlier, I mentioned a few things which probably aren't so clear. Let me start by explaining categories. In old, RESX-based context, first item in your key specification was resource filename or assembly name. That is, in "system, home" first term was meant to specify filename or assembly name and secondo one its actualy key. Diavolo has no assembly name nor RESX files but I thought that way to create keys partition was very handy so I decided to support that scheme by creating categories, which have the same task. Their goal is to provide a logical boundary among keys so, for example, system category will probably contain most system-wide keys while a blog category could include all keys related to a blog module or to blogging support. That's very handy.

Also note that Diavolo fully supports local resources, that is resources which are created for specific controls or pages, which were represented by old App_LocalResources folders. It's important to note that part of original specifications is fully supported and basically virtual path to object will become category name when you will use local resources. And, best of all, local resources will be editable as well and that's so handy for module developers !

Finally, we have system keys. While each installation of Diavolo will feature a system category which will group most system-wide, platform-defined keys, developers and administrators can also turn each key into a system key. Those keys need to be considered vital in order to let portal work as expected, maybe because they're used in administration pages or other important areas. Maybe they're just hardcoded in ASPX/ASCX code and, if deleted, compilation would not succeed. Either way, removing such key could be harmful and that's why portal administrators won't be able to remove them. Also, there will be two different functions in code to remove standard and system keys, so developer must be aware about what he/she's doing before doing that.

The good part is portal administrators can promove keys from standard to system. They will be able to edit such key and select specific checkbox to turn them into SKs and that's very useful. Imagine an admin installs a new module which rely on a specific key to be present. Now that key wasn't a SK and could be removed by other modules or maybe another admin who doesn't know such module has been installed. By promoving such key to be a system key, administrators won't be able to delete them by using editor and other modules will need to treat it as a system key in order to remove it and that probably means they will show a warning about it. This will reduce chances that key will be deleted by accident.

Lastly, I wrote that admins won't be able to delete a system key: that's not 100% true. If they use editor to remove one, an error will occurr. If they are 100% sure they want to remove it, they can edit such key and turn it into a non-SK and then they will be able to delete it. Either way, that extra operation will make them aware they are potentially deleting an important key.


It took really a breeze to implement a custom resource provider to replace default RESX-based one. Really, I didn't think it could be so easy! Kudos must go to .NET framework engineers because you can literally customize basically any aspect of the framework, down to every basic detail. Microsoft can be great when they really want...

So Diavolo localization system basically integrates with standard system one and that means developers can use everything they are used to : implicit localization, explicit one, programmatic access. Everything is supported while developers can access and modify data at runtime without any need to restart application or influence performance.

As a reference, I used Michèle Bustamente's article about custom resource providers: you can read that article here. It's a very nice article and tells basically everything you need to consider to implement your own resource provider.

This is another huge step toward a first BETA version of Diavolo.


Nice hard-working weekend

I had a very hard-working weekend because of problems I reported in my last posts but I'm really amazed how quick it was to scrap all webparts code and start over. Really, I thought it was going to be difficult after long months of coding but in one day and a half I basically converted most of my code to a simpler version based on webcontrols instead of webparts and I'm basically 70% done for display things.

I have to say that most of such "easyness" was related to how simple is to work with objects databases and, specifically, Perst. If I wanted to be sure that I'm getting down to the right path, I'm more than sure now. I was able to refactor about 10 classes, extend another 2-3 of them and plug such code into old codebase and everything started working again without any reference to webparts. Amazing ! I'm not loosing my time to adapt / create / generate / map DB code. I simply refactor classes I needed, create new fields and use them. Sure, I also notice that it's mandatory to pay attention to new classes structure and that objects upgrades, while included, cannot be done without taking care to explicitly handle them. For example, you can upgrade a class to a new version and include a new field and that field will instantly be available to your code and your old objects will be able to use it. BUT you also have to take care to upgrade your objects some way or handle versioning. Such new fields will surely be nothing (null) for new objects and you must handle such case or upgrade and initialize your objects before using them. That's a problem for sure but it's extremely easier to take care about this rather than refactor your classes since you change your DB schema, expecially when your dealing with hundreds, if not thousands, of lines of code. Plus, how sweet is to work with your objects, using those nice and useful collections where needed to make things for you (and people using your API !) much simpler !

Perst (and database objects in general) are hot technologies. Don't forget to give them a chance.


So yesterday I posted about the big big big problem of webparts framework

Personalization.ToggleScope()

method which will issue a

Server.Transfer()

call and that will make it incompatible with ASP.NET routing because when page has been routed (that is, it doesn't exist) that Server.Transfer() call will end up in error because (guess!) file doesn't exist. Googling around Web, I found that problem to be common for MVC framework and some code has been posted to emulate it. So my first attempt was to try to get around that problem by using

Server.TransferRequest(url,false)

since that method, only available on IIS7, should process the whole IIS pipeline instead of skipping it the way Server.Transfer() does and I thought that could be useful to include URLRoutingModule into chain and thus having my routed file back to life. No luck: same file does not exist error. I was already subclassing WebpartManager and WebpartPersonalization classes in order to provide my own logic there and I thought to replace protected

Personalization.TransferToPage()

method in order to replace Server.Transfer logic to something else, Server.TransferRequest for example. So I came up with one of those solutions for MVC somewhere on the Internet and implemented this:

Private Shadows Sub TransferToCurrentPage(ByVal page As Page)
            Dim request As HttpRequest = page.Request
            If request Is Nothing Then
                Throw New InvalidOperationException("WebPartManager.Page.Request Cannot Be Null")
            End If

            Dim url As String = request.CurrentExecutionFilePath
            If page.Form Is Nothing OrElse String.Equals(page.Form.Method, "post", StringComparison.OrdinalIgnoreCase) Then
                Dim queryString As String = page.ClientQueryString
                If Not String.IsNullOrEmpty(queryString) Then
                    url = String.Concat(url, "?", queryString)
                End If
            End If

            Dim scriptManager As ScriptManager = System.Web.UI.ScriptManager.GetCurrent(page)
            If Not scriptManager Is Nothing AndAlso scriptManager.IsInAsyncPostBack Then
                page.Response.Redirect(url)
            Else
                'page.Server.TransferRequest(url, False)

                If HttpContext.Current.Items("Post!") Is Nothing OrElse HttpContext.Current.Items("Post!") <> True Then
                    ' Adapted from : http://forums.asp.net/p/1458709/3347077.aspx
                    HttpContext.Current.RewritePath(url)
                    Dim context As HttpContextBase = New HttpContextWrapper(HttpContext.Current)
                    Dim routeData As RouteData = RouteTable.Routes.GetRouteData(context)
                    If routeData IsNot Nothing Then
                        Dim routeHandler As IRouteHandler = routeData.RouteHandler
                        If routeHandler IsNot Nothing Then
                            Dim requestContext As RequestContext = New RequestContext(context, routeData)
                            Dim httpHandler As IHttpHandler = routeHandler.GetHttpHandler(requestContext)
                            If httpHandler IsNot Nothing Then
                                HttpContext.Current.Items("Post!") = True
                                httpHandler.ProcessRequest(HttpContext.Current)
                            End If
                        End If
                    End If
                End If
            End If
        End Sub

Ugly Post! references in Context.Items were just a quick way to avoid infinite loops when calling the same page, which is always the case for ToggleScope(). This probably causes problems with postback logic because after click on a button to trigger scope change, scope doesn't change and, if left without any control, that ProcessRequest call will simply trigger an infinite loop of calling back itself again and again. Even when I stop that loop and take care to only run ProcessRequest a single time, postback won't occurr and scope won't be changed, though everything else keeps working.

 Then I tried to work on a different basis, leaving Server.Transfer() call in place and working on that specific file does not exist error. By using a custom VirtualPathProvider, I could return true to calls from ASP.NET over specific files and that would get rid of that error but, unfortunately, URLRoutingModule doesn't come into action if file exists so that's not an option. Then I tried to set module to route existing files too and I'm trying to provide and implementation so my VirtualPathProvider will return contents from default.aspx to such requests: let's see what happens but this will be my last attempt before dropping v4.0 and start over to v4.2, getting rid of Webparts framework.


Given my previous (angry) post about my experience with webparts framework, I need to plan a conversion between current solution and a new one. While areas where such conversion is needed aren't many, there will be a lot of code to write and I will also need to update my objects database schema / structure. 

Portal themes are based on standard ASP.NET themes so I won't need to change that. However, webpart zones must be replaced by standard controls like panels to host all modules. I will also change logic to apply containers which were integrated into webparts via webpartchrome classes: such code must be replaced. I plan to drop drag&drop functionalities which were integrated into webparts framework as that would be much work to implement that now. I will also need to modify modules classes to enable editors and so on.

As a list:

  • modules must now be changed to enable editors. That will probably be an annotation or attribute applied to class in order to simplify installation;
  • webpart zones must be replaced by standard controls like panels;
  • I need to change page logic in order to let all modules be surrounded by a container, if selected by admin;
  • I need to plan a way to let module developers include their editors (for module contents) into a portal-wide set of standard editors, something which was provided by webparts framework out of the box;
  • code to move modules between zones must be implemented as it was provided by webparts too;
  • drag&drop ? There's a lot of work to implement that so I think I will skip that for now;
  • must implement a way to register modules in a catalog and present them to users so they will be able to add them to portal zones;
  • some kind of personalization must be available to modules and that needs to be implemented.

There's a lot of work to do, unfortunately. And I need to be quick, quick, quick ! Welcome Diavolo 4.2... !


Coding is fun... usually !

One has to say that coding is fun... because it really is ! Well, usually ... !

The THAT day comes and you feel like someone really crashed into your car ! And THAT day is today for me. My Diavolo CMS was almost ready to debut when I crashed into a very nasty problem which will probably force me to work on that the whole Summer to basically change... everything ! So hello blogs for coding : this is my first BIG rant ! Sealed Ok, this will be my 2nd rant since my first one is: has this editor a way to ABSalign pictures? Whoa! Can't believe I have to manually edit HTML to get that...

So I had this new CMS and I've been working on that since many months. Basically, I wanted to use Microsoft Webparts framework and have all those nice features packed and ready, while I was going to extend a lot of them. It's not Sharepoint but it's an awesome piece of software. I won't go into details like support for objects database and stuff, but I have to say I had all the theming stuff ready, wrote a lot of classes and extensions to basically reproduce everything from big CMSes and of course it was .NET 4 only ! The I came down to new routing functionalities for .NET (those are awesome!) and had this strange error, you know that one which reads like:

Server Error in '/' Application.


The file '/pageid/1/default.aspx' does not exist.

 

Undecided Hmm... what ? Anyway, when you've been down to subclass WebpartManager, WebpartZone, Webpart themselves, WebpartChrome, WebpartEditorZone, lots of providers including personalization, membership, VirtualPathProvider, lots of other stuff from other parts of the framework, well, then you've been down to cope with a lots of problems so I wasn't very scaried about that.

I have to admit I really underestimated that problem as I was sure I could find a way to fix that and go on, like in past 4-5-6 months. But I really feel this time I was wrong!

The problem is, when you toggle scope for webpartmanager class, and switch to shared from user, ToggleScope() method internally uses Server.Transfer to return control to page where that has been generated. Guess what? Using Server.Transfer is a big NO-NO when using .NET routing as that method won't pass through standard IIS pipeline and thus routed paths (i.e. virtual, non-existent paths generated by your routing mappings) will generate that error. That is, file does not exist. I started to get scaried when I read a blog post about this problem where author was stating that this is a bug Microsoft acknowledged but that would hardly be fixed anytime soon, and that was Sept. 2009 !

So it's great ! I have this big load of code working and almost ready, anything, zones, modules, editors, chromes, providers, jQuery, MS AJAX, themes and everything... but now I have to decide what to drop ! If I drop routing, I could revert to standard, ugly, QueryString-enabled pages (bad bad bad!) and say goodbye to fancy AND useful SEO-friendly URLs, unless I want to use URL rewriting module, maybe; or I need to drop the WHOLE webpart framework, keep SEO URLs and start over to write replacement for webparts in my portal code. Darn!

How can Microsoft fail to emphasize that two very important parts of their framework aren't compatible ? True, webparts are probably one of less used (but most powerful) features in ASP.NET but ... !

Yeah, I was really stupid not to extensively try routing and webparts together before going down to write tons of code but I didn't expect them not work AT ALL ! I expected that to be hard and difficult, the way it usually is, but didn't suspect Microsoft could leave such hole opened. My fault, I guess, but MS doesn't work like that, usually and that's why I didn't expect.

Great! My first rant on this blog... !