While ActiveX has enjoyed a (mostly) deserved reputation for being ill-tempered and hard to work with, I've used it successfully for many years as a deployment vehicle for rich-client web applications that run inside the context of IE. I'll attribute most of that success to Delphi and ActiveForms since the most complicated plumbing is taken care of for me automatically, but still gives me the ability to override what I need. Special thanks should go to Lino Tadros and Steve Teixeira, former members of the Delphi R&D team, to allow this.
Fast forward to today. My goal is relatively simple. I have a .NET application built with WinForms. I'd like the same type of ease of use in deploying this WinForm application to my users. I've done quite a bit of research on the best way to achieve this, but I haven't come up with the perfect solution yet. I'm close, but it's not buttoned up all of the way yet. For starters, I would highly recommend the following articles:
I wasn't able to find this nugget anywhere, though. If you want to call methods from your HTML page via (e.g.) JScript, you need to make your EXE assembly COM visible. The easiest way to do this is to select Properties for the EXE in the Project Manager, and press Assembly Information. There, you'll find a checkbox to make this assembly COM visible.
So far, I've been able to get a WinForm EXE hosted in IE, strong-name it and communicate from the web page to the control. I still have several items left to tackle, like wrapping everything up in a CAB file for easier deployment, and getting calls to other assemblies working. I'll comment on those as I get to them, but if you have any pointers on better ways to do any of this, I'm all ears!
LINQ is coming along nicely. This release is full of goodies. Among my favorites: join support, DLINQ graphical designer, LINQ over DataSet, multi-tier enhancements, and improved conflict resolution. But the thing I'm most excited about is the addition of XML mapping of elements! I attended a talk on ORM by Sean McCormack at the WI .NET User Group last night. He is an excellent speaker and spoke about DLINQ for a couple minutes, but hated the fact that it didn't have XML mapping. Maybe this will bring him around.  It also looks like I'll be speaking at the WI .NET User Group on LINQ in the medium-term future. I'll be sure to post more about that event and more findings with LINQ. In the meantime, download the CTP now.
Here are my notes on hosting remote objects in .NET:
- MSDN has a Remoting Example for hosting in IIS. The nice thing about the sample is that it shows how to use the BinaryFormatter for optimized communication over the HTTP channel.
- When using the HttpChannel and BinaryFormatter combination, HTTP errors still come back as text. This leads to SerializationException errors on the client. Richard Blewett wrote a custom channel sink to fix the HttpChannel/BinaryFormatter/ASP.NET host bug.
- The connectionStrings element in the web.config file is only valid when running ASP.NET 2.0. You can change your virtual directory to use ASP.NET 2.0 by running Internet Information Services, right-clicking on your virtual directory and selecting Properties. Then, go to the ASP.NET tab and select the 2.0 ASP.NET version in the combo box. You can also modify the web.config file here by pressing the Edit Configuration button.
- Put the web.config file in the root of the virtual directory, and the assemblies in the bin subdirectory.
- If you want to test the remote object, you can't just surf to the URI of the object (e.g. http://localhost/tipnet/MyAppServer.rem). If you do, you'll get a "Requested Service Not Found" error. You need to go to http://localhost/tipnet/MyAppServer.rem?wsdl instead. Be sure you explicitly specified the SdlChannelSink if you use other channel sinks.
- Lastly, be careful when defining channel tags in web.config. Don't close XML elements out prematurely. It's easy to do, and hard to spot once you've done it.
Here's an article from MSDN that talks about how to host your .NET Remote objects in an NT Service. Also be sure to check out the information on the ServiceController class to work with NT Services programatically and the ServiceInstaller class. Lastly, this article talks about how to debug and deploy NT Service applications.
First things first: I hate that ASP.NET forces you to put all assemblies in a bin subdirectory. To make matters worse, there is no way to change the private probing path in an ASP.NET application. Couple this with the directory structure that VS.NET creates for projects (e.g. MyProject\bin\Debug), and you end up with the fact that you can't simply create a virtual directory that points to the location of your assemblies that were generated by doing a build in VS.NET
I couldn't find anything to easily deploy my project with remote objects and a web.config to an ASP.NET virtual directory. I did see some documentation about Visual WebDeveloper, but I don't have that installed here. Perhaps I didn't install it when I installed VS.NET. Regardless, I really don't want to go back to install this since I'm not creating aspx pages and the like, but I do want to host my remoting objects in IIS (more details on this in a later post).
What I ended up doing was using the post-build Build Event to create events to copy the files over to the IIS directory, e.g.:
copy $(TargetDir)\*.* \inetpub\wwwroot\tipnet\bin
copy $(ProjectDir)\web.config \inetpub\wwwroot\tipnet
At least this way, my IIS files stay in sync with what I just compiled. It might not be the most beautiful thing ever, but it works.
Building on my last post on .NET Remoting Sinks, you may notice that when you define a serverProvders block in the config file, you no longer are able to reference the WSDL from your remote object (e.g. http://localhost/tipnet/MyAppServer.rem?wsdl). The reason for this is that when there is no serverProviders block, .NET creates a few default sinks, including the sink that generates WSDL output of your object. However, when you define sinks manually, .NET expects you to explicitly list all of the sinks that you want to use. The WSDL output is generated by the SdlChannelSink class. So to get WSDL back with your custom sinks, simply use a block like this:
<serverProviders>
<formatter ref="binary" />
<provider ref="wsdl" />
<provider type="LoggingSink.ServerSinkLoggerProvider, LoggingSink" />
</serverProviders>
The ref syntax is a shorthand way of referencing the SdlChannelSink class. You could use the full type name by specifying the strong name of the SdlChannelSink class if you wish, but this is much easier.
When writing server-side sinks, it's important to remember that order matters when declaring your sinks. For example, consider the following block in a config file:
<serverProviders>
<provider type="LoggingSink.ServerSinkLoggerProvider, LoggingSink" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
The problem here is that if ServerSinkLoggerProvider is a class that implements IServerChannelSink, the requestMsg parameter will be null in the ProcessMessage method. If you reverse the ordering of the 2 lines (i.e. put the formatter first), everything works fine.
This is by design, and due to the chaining of sinks that occurs in .NET Remoting. In the first case, the formatter has not yet deserialized the message, so we can't easily get to the IMessage that was passed to us. In the second case, the formatter takes care of the deserialization first, and can then pass the IMessage to the DispatchChannelSinks for further processing and investigation. The overview of .NET Remoting layers from MSDN is lacking a lot of detail, but hints at what causes the differences. Once I went back to Ingo Rammer's book, the technical reason for this behavior became obvious.
I use OutputDebugString (ODS) a lot when trying to trace through a problem with an application. Coupled with using DebugView from SysInternals, you can easily get a real good idea of where things are going wrong.
In .NET, you use Debug.WriteLine() to write ODS messages. This requires that your assemblies be built with DEBUG information to see the ODS messages. When working with ASP.NET pages, you have a some options to achieve this (the easiest couple are listed here):
- Include the Debug attribute in the Page tag for the aspx page. For example:
<%@ Page language="c#" Debug="true"%>
Testing:<br>
<%
System.Diagnostics.Debug.Write("Testing");
%>
- Include the debug attribute in the web.config file. For example, put this in a section underneath the <configuration> node. You can also do this by changing the config file when running the IIS Manager, selecting Properties for the Virtual Directory, going to the ASP.NET page, selecting Edit Configuration, going to the Application tab, and selecting Enable Debugging.
<system.web>
<compilation debug="true" defaultLanguage="c#" />
</system.web>
|