.NET is not ready for prime time

Stéphane Rodriguez - Aug 7, 2004

 

Motivation

This article is not supposed to be an anti-microsoft bashing. It's entirely based on facts and various experiments around .NET technologies. Let it be clear, there are plenty of reasons to capitalize on a managed development environment today and start moving forward, be it for the languages or for the run-time that takes care of usual problems for you. Java is a managed environment, but so is Visual Basic, Ruby, ... There are plenty of choices for software developers.

What is the problem then? The problem is .NET per se does not deliver, the ability to execute some .NET application is random. .NET does not deliver its premise in the following areas : executability, predictability, deployability, reliability, interoperability. In this article I'll define the terms and give a few examples of the first. This article should be in multiple parts, and is probably the start of some iteration.

Each of these areas are show stoppers when it comes to adopting a new "technology". That's the key point. The marketing brochure says "Productivity gain". That goes without saying, the marketing brochure implies productivity gain with an equivalent software quality. This is simply not true unless proven otherwise, the examples demonstrate that neither productivity gain is an obvious thing whenever it comes to the entire development lifecycle -- it dismisses the usual "a few drag and drops before you are done" -- nor the claimed productivity gain at an equivalent level of quality of software than current. For those interested in the 70% code reduction claims, take a look here (Wintellect employee, pro-Microsoft consulting company), and may be you'll figure out that you'll need at least 70% more time to get to that 70% less code, especially in every single custom solution development.

 

Executability

Number one requirement for whatever development framework is executability. Executability is the ability to execute one's program onto someone else's machine. Without executability, there is strictly no grant that the program one is developing will ever run, whether the audience is your room mates, pals, corporate accounts, integrators, other software vendors or simply Joe smooth. So whether you are working on personal projects, or doing professional work, a basic assumption when writing software is that it will run on someone else's machine, otherwise why write the software in the first place?

It's commonly known that, regardless what your software is about, what customers see first is not the software itself, but the installer setup instead. Screw it up, and customers will axe you. Last decade's software industry history is filled with companies that couldn't get the installer to work properly.

Even though the installation is an interesting topic, that will be covered in more details, we'll assume for the purpose of the executability matters that the software got properly installed, and that the customer is ready to double-click it. Executability is the ability to run the software at this point.

My assertion is that .NET does not provide executability.

.NET changes things a bit with earlier native apps regarding executability. .NET is centered around that assumption that whatever run-time is used at compile-time will in practice be deployed onto customer 's machines and so will work on a side-by-side basis with existing run-time versions. While this looks good, this is necessary, just like deploying the C-runtime dlls in all your native apps using the C-runtime dlls, it's not sufficient however. There are issues. One big issue is that .NET does not grant to be an ecosystem per se, a lot of method calls will end up being developer-side P/Invoke or COM calls, and so developers actually end up with the old rules of native app deployment, in addition to the new .NET app deployment rules. The example below puts that in perspective.

Another key misconception is that .NET works well enough so you can confidently write your apps and expect them to work on someone else's machine provided the run-time fits. We are not talking here about run-time redirection through config files, this just adds yet another layer of bugs and issues for executability. No, here we limit ourselves to very simple scenarios where the same .NET run-time distribuable is deployed along with your app at deployment time. An example below shows that even in this "ecosystem" scenario, there is still no grant that anything works either. This probably makes you think about "100% Java" labels that are less trendy these days. "100% Java" was really about no native method calls, or at least self-sufficient fully -granted-to-execute-and-run-well native method calls. At this point, if you are interested in this why not double-check the claims of IronPython, a language for .NET : "Managed and verifiable - IronPython generates verifiable assemblies with no dependencies on native libraries that can run in environments which require verifiable managed code."

Examples:

Example 1 : Using the Windows media player in windows forms.
Example 2 : HTTP credentials implied for the execution of the web requests layer implemented in .NET Explained here : guess what, the XML namespace program manager, certainly not a newbie, got troubles getting the http web request running. More information here (MS XML team).

Example 1 : Using the Windows media player in windows forms.

This example underlines the following : 1) poor user experience at assembly load time 2) lack of dependency checking with system libraries (WMP is part of the system libraries) 3) unability to anticipate catastrophic scenarios without cumbersome code 4) failure of drag-and-drop scenarios to deliver reliable applications in practice.

The goal is quite simple, provide rendering of some media in a windows forms. This could be audio or video of any format supported by the Windows media player (WMP). The way media rendering gets implemented in the .NET world is by relying on third parties (even though the third party happens to come not only from the same company, but also bundled with the operating system) since there is no support for media types in .NET run-time. Of course, you could buy another third-party, but what if the media file format is only supported by WMP? Alternatively you could also start writing your own renderer but since you are supposed to come up with a product in less than a decade of work, we'll just pretend for the remainder of this example that your only option is to actually rely on WMP, which by the way makes complete sense in simple component usage scenarios : after all, if the Visual Studio development environment doesn't let you grab components and assemble those together, how could this be labelled "productive" then?

So let's reproduce the following steps :

  1. Start Visual Studio.NET (the scenario doesn't require to use VisualStudio.NET 2003, it can be whatever version)
  2. Create a new C# project, a Windows application (alternatively you can create a new VB.NET project, MC++ project, or whatever project whose development language supports COM objects)
  3. When the form shows up, right-click the Toolbox window in Visual Studio, click Customize (the label changes depending on your Visual Studio version), then from the modal window, select the COM components tab and select "Windows media player" : notice that the environment doesn't tell the GUID of the COM component like it doesn't care at all.
  4. Drag and drop (magic words) the component onto your form, and change the dock properties so that it fills the entire surface.
  5. You are done. There is no need to set a url or filename to experience the problems. In case you want to do so however, go in the Properties window, and fill the URL or FileName property.
  6. Compile the project and start it. You should see WMP. That's a proof of concept at this point, this piece of code can play any media type supported by WMP.
  7. Before you leave Visual Studio, notice that in the Solution Explorer window, the References folder mentions WMPLib and AxWMPLib which are COM interop wrappers created by Visual Studio when you drag and dropped the component.

So far, looks like everything is fine, you're about to package and distribute that application that will make you the next billionaire. Note that so far the Visual Studio environment never warned of anything, and on the other hand doesn't provide any particular dependency checking tool before you start distributing your app.

Indeed everything looks so good that you start uploading download sites for people to test your application, and also send copies of it by email to your friends, until...you receive feedback telling you that the application awfully crashes at start-up. The error message is irrelevant and doesn't tell anything meaningful. One of your friends tells you that he's using version 1.1 of the .NET run-time which happens to be the same than yours (Visual Studio .NET 2003 targets .NET 1.1 by default), and that he's using Windows 2000. You developed the application on Windows XP but the different OS shall of course not be a legitimate reason to wonder : how the hell can an application developer be successful if he has to develop the software on some OS rather than another? It wouldn't make sense, right?

The issue is then isolated by sending this friend another Windows application, which does nothing and is obtained by creating a new C# Windows application project and packaging the resulting application. It works fine. Since it's compiled using the same environment than the WMP application, it tells whether the core .NET run-time libraries are the origin of the issue. The WMP libraries are the problem. By opening the Visual Studio csproj file for the application, where an excerpt is shown below :

  <ItemGroup>
    <COMReference Include="AxWMPLib">
      <Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid>
      <VersionMajor>1</VersionMajor>
      <VersionMinor>0</VersionMinor>
      <Lcid>0</Lcid>
      <WrapperTool>aximp</WrapperTool>
      <Isolated>False</Isolated>
    </COMReference>
    <COMReference Include="stdole">
      <Guid>{00020430-0000-0000-C000-000000000046}</Guid>
      <VersionMajor>2</VersionMajor>
      <VersionMinor>0</VersionMinor>
      <Lcid>0</Lcid>
      <WrapperTool>primary</WrapperTool>
      <Isolated>False</Isolated>
    </COMReference>
    <COMReference Include="WMPLib">
      <Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid>
      <VersionMajor>1</VersionMajor>
      <VersionMinor>0</VersionMinor>
      <Lcid>0</Lcid>
      <WrapperTool>tlbimp</WrapperTool>
      <Isolated>False</Isolated>
    </COMReference>
  </ItemGroup>

Obviously, one can conclude our application is depending on the COM object whose identifier is {6BF52A50-394A-11D3-B153-00C04F79FAA6}. The stdole component is a standard operating system component and is not subject to changes over time since it's a core system library. By double-checking your computer, you find it in HKEY_LOCAL_MACHINE/TypeLib and indeed the registry key points to the primary WMP COM dll. By asking your friend to double-check this, you are told that no such key exist. What we can conclude so far is that he's using a different version of WMP and that, for some unfortunate reason, the type library identifier has changed over time, which pretty much is the absolute reason why the application crashes.

But the application crashes like this : 1) it doesn't tell the user what's the problem 2) there's no advanced option in the error dialog that allows one to grab and send the application author information about the problem 3) it crashes because one component is missing, but that component is not some weird third-party component, that's an operating system component and as such Microsoft should have done a much better job with compatibility 4) the .NET assembly loading infrastructure doesn't bring seamless dll updates that would for instance check the missing component on some server repository, especially in the case of a Microsoft component, and download it.

Bad luck indeed, there's no way for the friend to be able to enjoy the application. The problem could be solved alternatively by bundling the WMP libraries in the package but there is another problem then : Microsoft prevents the redistribution of WMP libraries prior to signing an awful 40-page contract which mandates a lot more efforts : logos, disclaimer, policies... to cover their asses. So, it's pretty much like the journey ends here and the conclusion is ugly.

Even though the application won't run, it's better from a developer perspective to show a proper message rather than let the application crash. Again, the .NET run-time does not provide any help on that matter. Since libraries are loaded on-demand, one could use the assembly loader helper functions to ensure that the library is there or not, and pop up an appropriate message. For that matter, the following piece of code should help :

using System.Reflection;

partial class Form1 : Form
{
  public Form1()
  {
    try
    {
      Assembly.LoadFrom(@"Interop.WMPLib.dll");
    }
    catch(Exception e)
    {
      MessageBox.Show (e.ToString());
    }

    InitializeComponent();

    this.axWindowsMediaPlayer1.URL = @"C:\software_download\videos\trailer.mpeg";
}

The purpose of this code is to preload the COM interop assembly so as to make sure the underlying COM component can be loaded. This is done in the constructor of the Form class, before the WMP gets instantiated anyway, which is a good place to perform that kind of checking. Unfortunately again, all this code does test is that the interop assembly file itself (Interop.WMPLib.dll) can be loaded. This tells nothing about the COM component itself. The source code will badly crash after the InitializeComponent method implementation, where the parent AxHost instance gets initialized. Due to the asynchronous nature of the AxHost initialization (you can figure this out by using Reflector and see the actual implementation of AxHost, full of implicit ActiveX OLE related things), this is not really practical to know when the code will crash in practice. It crashes in the underlying OLE libraries, and then the exception is raised in higher stacks.

Of course, there is one way to anticipate the absence of the COM component, it's by making a registry lookup, but this implies the knowledge of the type library identifier of the WMP and may be more such identifiers (CLSID, ...). Not really what you would expect from a higher-level development platform, and not really the kind of drag-and-drop scenario the marketing brochure boasts so much about.

This ends up the comments for Example 1, and we indeed are in a bad shape. Whenever the application relies on some component, regular dependencies problem arise. If those components are COM components, then it's an order of magnitude harder to come up with an application that doesn't badly crash. In all cases, the drag-and-drop scenario is a misleading marketing statement.

 

Example 2 : HTTP credentials implied for the execution of the web requests layer implemented in .NET

This example underlines the following : 1) http web request default credentials are not set, and prevents users from performing web requests depending on their configuration 2) tricky credential management which makes the developer think false positives about the health of his application and its ability to run the same on someone else's machine 3) same for proxy settings 4) inconsistency in the .NET framework whenever some class uses web requests internally (example : the XmlReader class).

This scenario is really simple, we want to parse an xml document from the web. The XmlReader class does exactly this. Instantiation lets pass a url which identifies either a local file or a url, and then the buffer retrieval and element parsing loop lets one do something relevant with that xml document.

The following code for it is the following, in quite a declarative manner :

System.Xml.XmlTextReader reader = null;

try
{  
  //Load the reader with the internet url and ignore all white space nodes.         
  reader = new System.Xml.XmlTextReader(url);
  reader.WhitespaceHandling = System.Xml.WhitespaceHandling.None;

  //Parse the file and display each of the nodes.
  while ( reader.Read() )
  {
    ...
  }
}
catch( System.Xml.XmlException e)
{
  MessageBox.Show("exception : " + e.ToString());
}
finally
{
  if (reader!=null) reader.Close();
}

The big trouble for the executability is not that it doesn't work, it is that it may work in some circumstances. If you are using this code at home, then you're likely not to be in a LAN, and thus network connection be it internet or not will not behave the same than if you execute the same code at your work place. The point in case is the credentials. Default credentials must be assigned, and unfortunately the entire MSDN .NET guide regarding the usage of the System.Net classes is silent about this. This would be perfectly fine if the connection would fail as long as the credentials are not set in any configuration. But it's not how it works. One way to solve the problem is to assign default credentials, which brings even more non-intuitive implementation details :

IWebRequest.Credentials = CredentialCache.DefaultCredentials;

The above line of code is what you would add to a WebRequest-derived class like the one used in the XmlReader internally. Unfortunately, you have no access to it, thus you end up nowhere. In fact, you'll need a special hack, a derived helper class, only to get through the inners-outers of the XmlReader configuration to set up before you can perform a request safely.

If that wasn't enough, the latest XP SP2 introduces regression regarding credentials. See this article for example. Needless to say, it's annoying that at any moment the underlying framework you capitalize everything on can fall under. I guess this is the consequence of leaving a great control to a single vendor. Companies spend a lot of time and money reinventing the wheel and end up doing all those things that you can find in the .NET framework today, or the Java SDK if you will. But if it doesn't work, or can break for any reason, there is no question that "reinventing the wheel" is the only way to go anyway if you want your customers to buy your products.

In fact, when credentials are properly set, there is still the proxy problem. This is yet another thing absolutely understated by those who have written the .NET framework. Obviously they spend too much time with default IE credentials so much that they think that what may fit their needs fits other's needs, which is absurd when you know the amount of applications out there based on specific proxy settings.

Proxy options must be set whenever required thus, and again since the WebRequest derived class is not directly exposed in the XmlReader class, there is no way to just assign values to properties. There is a dirty workaround that is not documented anywhere in MSDN. Here is the piece of code that manages both credentials and proxy options :

//
// The application lets the user edit proxy options (url and port)
// If such options are edited then they are used, otherwise
// default IE proxy options are used.
//

public class ProxyXmlUrlResolver : System.Xml.XmlUrlResolver 
{
  public  IWebProxy Proxy = WebProxy.GetDefaultProxy();

  public override object GetEntity(Uri absoluteUri, string role,
           Type ofObjectToReturn) 
  {
    HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(absoluteUri);
    req.Proxy = Proxy;
    return req.GetResponse().GetResponseStream();
  }
}

System.Xml.XmlTextReader reader = null;

try
{
  //Load the reader with the internet url and ignore all white space nodes.
  reader = new System.Xml.XmlTextReader(url);                              
  reader.WhitespaceHandling = System.Xml.WhitespaceHandling.None;          

  // resolve proxy/credentials                              
  ProxyXmlUrlResolver resolver = new ProxyXmlUrlResolver(); 
  resolver.Credentials = CredentialCache.DefaultCredentials;
                                                          
  if (m_proxyUrl.Length == 0)                               
    resolver.Proxy = WebProxy.GetDefaultProxy();          
  else                                                      
  {                                                         
    String proxy = m_proxyUrl + ":" + m_proxyPort;        
    resolver.Proxy = new WebProxy(proxy,true);            
  }                                                         

  reader.XmlResolver = resolver;                            

  //Parse the file and display each of the nodes.
  while ( reader.Read() )                        
  {                                              
    ...
  }

}                                                    
catch( System.Xml.XmlException e)                    
{                                                    
  MessageBox.Show("exception : " + e.ToString());  
}                                                    
finally                                              
{                                                    
  if (reader!=null) reader.Close();                
}                                                    

Quite ironically, the MS Xml PM stumbled on this issue lately only (the .NET framework is in beta since feb 2001 if I remember), get a good laugh here. Again, without this, web requests simply don't work. Isn't it ironic that IJW! (It just works) was invented as an acronym to refer to .NET interop, and well accepted in the community (on the basis of a basic Google check).

 

Predictability

This example (ISV), bug and issue, says it all. Heard about any warning regarding font selection so far? Yet, whatever document-related application will do that, ending as bad as the author explains.

How is it that integration patterns like this look so intuitive, and end up so bad?

What is predictable is that it just doesn't work. If you want to know more about what's coming in the future of .NET, just view this recorded .NET rocks show where Jeffrey Richter (Wintellect co-founder, a consulting company), someone in touch with CLR versioning and executability for a long time is saying it all : in .NET 3.0 (Longhorn), assemblies are splitted in system assemblies and third-party assemblies, and assemblies will always target the latest CLR.

 

Deployability

Deployability is the ability to install some software. This topic will be covered more in depth in future article updates. The VS.NET installer that comes with Visual Studio is only targeting POC and demos which can be installed in some application folder. Real world applications are a little more complex than this and thus don't fit the tools sold in the development suite. Indeed, what are you supposed to think when you know that the Fusion guys from the CLR team (Fusion is the assembly loader in the .NET run-time) have first and foremost added cumbersome versioning rules and practically adding yet another layer of problems. What were they thinking?

 

Reliability

Reliability is the ability to make method calls as documented. This topic will be covered in future article updates. The garbage collector is able to release your COM object instance between the moment the object is created, and the moment you make a method call. Orcas changes the assembly bindings and there is no way to anticipate what users have on their machine prior the installation of some software. Since .NET apps share both system-wide bindings, as well local bindings (with some more dirty details in between, like in all pieces of code), there is no way to bet anything will ever work. Of course, at this point, there is no telling to the user to make changes to the .NET configuration applets given the complexity of how it works. Depending on the run-time version, assembly bindings, a key element to the entire infrastructure, changes (CLR team).

Reliability has certainly something to do with installers, shared components, DLL hell, and all the usual issues of bad reference counting. This post (CLR team) introduces a registration-free use of COM components, which is good to know after all these years (!), even though this does not help solving shared component related issues since they need to reside in some shared folder rather than a local folder.

 

Interoperability

Interoperability is the ability to integrate components and/or file formats that were developed from different parties. Interoperability can happen at compile-time, or at run-time. Each and every single Visual Studio release has broken the file formats used to describe makefiles. The notion of backwards compatibility has been thrown in the dust, even among Microsoft own products. That's still fine for demos or simple projects, but what about real projects? .NET architects have made the strong bid that projects will throw the legacy native code overnight (notice the pejorative way to describe today's code in every single marketing brochure related to .NET), which is absurd. .NET 2.0 changes (CLR team) : "You can use the HttpListener class, you can create a simple Web server that responds to HTTP requests. The Web server is active for the lifetime of the HttpListener object and runs within your application with your application's permissions. This class is available only on computers running the Windows XP Service Pack 2 or Windows Server 2003 operating systems.". What is this supposed to mean? that only XP kernels can serve http requests?

 

Durability

An infrastructure that changes heavily every year or two is unsuitable to most businesses, which for most of them have decade-like cycles. Introducing breaking changes, and coming up with "next version is better" is the usual problem of "when is software good enough?". Microsoft has no choice but force customers into infinite upgrades as part of their agenda. As an aside, from a developer point of view, keeping track of all the tiny details going on, the changes between versions, is much more than a full-time job, and is indeed quite daunting. In such a situation, as a developer, you tend to either focus 100% of your time on the subject, or entirely skip it. There is hardly any other in-between choice.

If that wasn't enough, what to think of this : "some folks from Microsoft promised that right after the SP2 release, there will be a service release for VS.NET 2002 and 2003 to address the problems with XP SP2 (debugging, and security, the main issues)", an excerpt from this post. What does it mean if Visual Studio .NET is affected by some OS service pack? Does it mean that the developer's machine is behaving differently then, and that the compiled code is not the same anymore? What about users at the other end of the spectrum, for instance those using XP without SP2 being installed? What a mess!

 

 

The MS Fusion team : Mike Grier, Suzanne Cook, Jon Wiswall, Junfeng Zhang, Alan Shi. Actually, Alan Shi got the last words in his very post : he says to avoid the GAC. I would say to not only avoid the GAC but to avoid the CLR for real applications, that's an even safer bet for the time being. For entirely isolated applications however, which is not a general purpose scenario, the CLR behaves fine (not over time, but that's a different topic).

That's all for now folks.

Want to leave a comment?

 

 

 


Home
Blog