Thursday, February 02, 2006

VS2005 inherited user control, abstract factory/dependency injection pattern

Yesterday the need arised for inheriting a WinForms user control to implement four specializations of a user control. Creating inherited user controls is very simple in Visual Studio, just click 'Add-New item' and select 'Inherited user control', name the new user control appropiately and click 'Add'. I then selected the user control to derive from (note that the class must be plain public, while the constructor can be protected) and clicked OK.

VS2005 dutifully added the new user control files, but the VS designer failed with this error message:

One or more errors encountered while loading the designer. The errors are listed below. Some errors can be fixed by rebuilding your project, while others may require code changes.

Object reference not set to an instance of an object.

at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
. . .
at Microsoft.VisualStudio.Design.Serialization.CodeDom. VSCodeDomDesignerLoader.DeferredLoadHandler.Microsoft.VisualStudio. TextManager.Interop.IVsTextBufferDataEvents.OnLoadCompleted(Int32 fReload)

Some googling did provided me with only a few similar problems, but none of the suggested solutions worked for me. Even adding two new user controls with no code or children would fail when trying to inherit one from the other.

The designer always calls the user control's parameterless constructor New and the control's Load event handler (provided that they exists), thus these methods should not do any operations that depend on some run-time resources.

I was quite puzzled when even added the two new plain user controls failed, as they of course had no constructor or load event handler. Then I noted that I could no longer open any user controls or forms in the whole solution. The VS designer had gone into a zombie state. A restart of Visual Studio, plus clearing the TFS workspace cache, corrected the VS designer problem for all the forms and the non-inherited user controls. Before I even tried to add a derived user control again, I did the recommended rebuild (see error message above) to ensure that coding errors did not cause the problem.

This time I added a standard user control and manually added the Inherits statement to the class code file and the designer partial class file. Then I opened the user control in designer mode, and I still got the famous designer error page, but this time with useful error details pointing me to a specific line in the base class.

The offending line was a private class member that created a biz-logic component using the abstract factory pattern:

Public Class GenericUCBase
. . .
Private _bizManager As IBizManager = ObjectFactory.BizManager()
. . .

Protected Sub New()

' This call is required by the Windows Form Designer.
End Sub
. . .

The factory uses a Public Shared (static) method that creates objects using the dependency injection pattern (interface based) from pre-compiled assemblies deployed to the VS project location. It was the Type.GetType(typeName as String) method that failed when called from the private member initialization. I don't know why the VS designer failed to locate my assemblies during initialization, VS has no problems with non-derived controls, or with running or debugging the project.

All class members that are "declare initialized" gets called when the designer loads a user control, along with the constructor and the load event handler. Knowing this, I moved the initialization of the biz-logic component to my InitControl method, and this solved the VS designer problem.

The TFS workspace cache is located here:

\Documents and Settings\%user%\Local Settings\Application Data\Microsoft\Team Foundation\1.0\Cache

I clear it whenever I have problems with VS or TFVC, typically when the 'pending check-ins' list contains bogus entries from other solutions that cannot be removed in any other way.


Anonymous said...

Did you really implement a generic user control ?
I implemented it too, but the VS Designer dont let me design controls which inherits the generic one.

My generic control :
public partial class ListenControl"TagTyp" : UserControl

And the control which inherits the generic control :
public partial class ListenControlCDaten : ListenControl"CDaten"

Note: The " stands for <>

What can I do so the VS Designer show me the control which inherits the generic one ? I get error messages if I open the control.

bei System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.EnsureDocument(IDesignerSerializationManager manager)
bei System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager manager)
bei Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager serializationManager)
bei Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.DeferredLoadHandler.Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferDataEvents.OnLoadCompleted(Int32 fReload)

I know that it has to do with the way the VS Designer builds the control. It tries to make an instance of the generic control and add only new elements to the control.

The same problem existed by designing controls which inherits abstract classes:

I took this solution and tried to solve my problem but failed.

Now I found your article but I dont understand it completly.
Maybe you can post an example code so I can better understand how to do it ??



Kjell-Sverre Jerijærvi said...

No, I did not implement the user-control using generics. I used the term "generic" (base class) as as the opposite of "specialized" (derived class) for my description of the inheritance.

I realize that "generic" is a bad term these days.

Anonymous said...

okay, now I understand :-)

Thanks a lot.

Unfortunately many people use the word "generic" this way, so searching for real generic code is difficult.