Latest Posts
Embarcadero
The Embarcadero deal seems, from my outsider’s point of view, to be a great deal for CodeGear and for Embarcadero. There are so many different ways that the two companies’ products can work together that it just makes a great deal of sense to me. It’s less clear to me how it makes sense for Borland, but that’s their problem. I’m curious about one thing, though. Embarcadero is, at present, a database tools vendor. With their purchase of CodeGear, they are acquiring two fine databases: InterBase and Blackfish SQL. Thus far, they’ve said very little about their intentions for these products, at least relative to what they’ve said about other CodeGear tools. Again, it seems like a great fit for Embarcadero’s product line; there’s lots of ways this could make sense. But I’d be interested to know what their plans are.
Share This | Email this page to a friend
posted @ Fri, 09 May 2008 13:38:23 +0000 by Craig Stuntz
Add a Feature, Write an Article
I just implemented support for client-side caching for the CodeGear sites. Here’s how it works:
Enabling Client-Side Caching of Generated Content in ASP.NET.
If You Use OnUpdate, Your Code Must Be Perfect
I’ve never particularly liked the idea of OnUpdate events. They have always seemed kind of inelegant to me, but I haven’t talked about it much since I don’t really have a compelling argument against them, save for "bad smells." Today, though, I was using RAD Studio and saw an access violation error in the IDE. This was immediately followed by the following error, which repeated over and over again. The stack dump tells the story:
[20A635EB]{coreide100.bpl} DocModul.AnyModuleModified (Line 3466, "DocModul.pas" + 10) + $41
[20A635E5]{coreide100.bpl} DocModul.AnyModuleModified (Line 3466, "DocModul.pas" + 10) + $3B
[00419D8B]{bds.exe} AppMain.TAppBuilder.FileSaveAllActionUpdate (Line 4067, "ui\AppMain.pas" + 1) + $0
[200401A7]{rtl100.bpl } Classes.TBasicAction.SetOnExecute (Line 11109, "common\Classes.pas" + 9) + $9
[201513B1]{vcl100.bpl } Forms..TScrollBox + $231
[2004009D]{rtl100.bpl } Classes.TBasicAction.Destroy (Line 11048, "common\Classes.pas" + 2) + $5
[2013D8C7]{vcl100.bpl } Controls.TControl.ScaleConstraints (Line 4033, "Controls.pas" + 12) + $7
[2015E854]{vcl100.bpl } Forms.DoPosition (Line 6778, "Forms.pas" + 44) + $2
[2015E86E]{vcl100.bpl } Forms.DoPosition (Line 6778, "Forms.pas" + 44) + $1C
[2015E8DC]{vcl100.bpl } Forms.DoAlign (Line 6795, "Forms.pas" + 0) + $4
[201631AA]{vcl100.bpl } Clipbrd.TClipboard.AssignPicture (Line 386, "Clipbrd.pas" + 7) + $14
[201632F9]{vcl100.bpl } Clipbrd.TClipboard.SetAsHandle (Line 430, "Clipbrd.pas" + 0) + $9
[20162637]{vcl100.bpl } Forms.TCustomFormHelper.WriteGlassFrameBottom (Line 8945, "Forms.pas" + 2) + $0
[2016291F]{vcl100.bpl } Forms.TApplicationHelper.GetEnumAllWindowsOnActivateHint (Line 9087, "Forms.pas" + 0) + $3
[0042297A]{bds.exe } bds.bds (Line 195, "" + 7) + $7
Note the line in bold. Because this is an OnUpdate event, it’s going to repeat forever. The user is trapped. There is no chance to save your work, or to exit the IDE normally. You have to end task. Sure, this is a secondary error; it’s almost certainly the consequence of the preceding access violation, rather than a defect in the OnUpdate event itself. But because that event is not robust enough to handle the consequences of the previous error, the user is trapped.
My conclusion is that OnUpdate event handlers are dangerous if they do not detect anything that could possibly go wrong with the references they use, and exit gracefully in that case.
Share This | Email this page to a friend
posted @ Mon, 05 May 2008 15:04:13 +0000 by Craig Stuntz
New Delphi Blog
MelanderBlog is the home of Anders Melander, who, among other things, developed the TGIFImage component included with Delphi 2007. This site includes a number of useful Delphi components.
Share This | Email this page to a friend
posted @ Fri, 02 May 2008 14:47:16 +0000 by Craig Stuntz
Document Adapters
One of the core elements of GetPublished is the Document Adapter, or DocAdapter. DocAdapter is a set of web services and .NET assemblies for converting rich documents into standard HTML articles that can be displayed on the CodeGear Developer Network sites. By using DocAdapter, we can accept articles in multiple formats, and ensure we get valid HTML that works with the site’s overall look and feel.
DocAdapter is accessible in two ways: as a set of web services and as a set of .NET assemblies/Delphi packages. The web services and assemblies reference each other (the web service calls the assemblies to perform its tasks, and the assemblies can reference the web service to perform their tasks remotely). Because the web service and the assemblies know each other, we don’t have to worry about type matching (making sure the types used by the web service match those used by the assemblies).
Client applications can use either technology, or both. GetPublished, for example, uses the DocAdapter web service to perform the conversion from the source format to XHTML, and the CDN.Documents assembly to generate HTML, thumbnails, and other document elements.
Format Conversion
The CDN.DocumentConverters assembly (and its web service wrapper, cleverly titled DocAdapterService) contains conversion classes capable of reading files in several formats. The classes convert document text to XHTML, which is a convenient format for additional processing. Depending on the format, they can also extract additional data. For example, the Word conversion class extracts embedded images from Word documents, stores them as separate files, and creates <img> elements in the XHTML that refer to these files. A conversion class is any implementation of the IDocumentImport interface:
IDocumentImport =interfaceprocedureImport(inputStream: Stream; docAdapter: DocumentAdapter; extractFields: Boolean);end;
Because we’re using an interface, the DocumentAdapter class doesn’t need to know anything about the conversion class other than the fact it implements the interface. This means we can implement converters without recompiling the CDN.Documents assembly, and add them as plug-ins to the calling application.
The WebServiceConversion class is a special implementation of the IDocumentImport interface that calls DocAdapter web services. All DocAdapter services are based on the same definition, expressed in WSDL. All a client application needs in order to convert a document to XHTML to to pass the URL of such a web service to the WebServiceConversion class. GetPublished stores the URLs of the DocAdapter services in the database, so new formats can be supported by simply deploying a web service and adding a single record to GetPublished’s database.
Document Processing
Once the text and images are extracted, DocAdapter can create the HTML and necessary supporting files that can be displayed on a web site. This is done by calling a single method, CreateDocumentArchive, which returns a DocumentArchive object. The DocumentArchive object contains all the necessary information, such as the final HTML (including syntax highlighting), all referenced images, thumbnails, a table of contents, keywords, and other information that may be useful. The generation of these elements is controlled by parameters passed to the CreateDocumentArchive method. Some of these parameters are:
- Maximum image width. Images wider than this value will be replaced by thumbnails that link to the full-size image.
- Maximum image height. Images taller than this value will be replaced by thumbnails that link to the full-size image.
- The width of thumbnail images generated for images wider than the maximum specified width or taller than the maximum specified height.
- Whether to keep embedded images in the final document (external image references remain unmodified).
- The depth of the table of content to generate.
- Whether to produce printer-friendly output, which doesn’t include JavaScript elements for dynamically hiding and showing images and sections.
In GetPublished, most of these parameters are associated with specific content types configurable by system administrators.
CDN Technology Overview
In response to some questions in the newsgroups, I thought I’d write a short overview of the tools and technologies used to manage and display the CodeGear web site.
The CodeGear web site, as well as the Developer Network, the CodeGear Support site, and several other sites all run as part of a single application. This application is an ASP.NET 2.0 application, written in Delphi using RAD Studio 2007, and running on several load-balanced Windows servers.
GetPublished is the content management system. It’s also an ASP.NET 2.0 application, written in Delphi. On the client side, we also have some JavaScript code. GetPublished does most of the heavy lifting - content management, author roles and permissions, workflow, support for multiple sites, languages, and locations, and all direct data access.
SiteCentral is the internal name of the presentation layer - the application that displays web pages when you go to any of the CodeGear sites. The application doesn’t access data directly - it uses GetPublished (in a .NET assembly) as its data access and processing layer. In addition to standard pages, such as article lists and article pages, SiteCentral includes a set of pluggable modules. A lot of stuff you see on the page, such as the table of contents, article ratings, and even actual content, is displayed by these modules, which are configurable in GetPublished by users with sufficient permissions.
Content posted to the sites is processed by Document Adapters, which are pluggable modules capable of converting content in various formats to HTML that can be placed on the site. Document adapters are also written in Delphi, and are used as .NET assemblies and as a web service.
The applications talk to other CDN services, such as the membership service to access user accounts and the digital signatures service to accept digital signatures for submitted content.
Pages use a special template page class, which lets us separate the overall look and feel from both functional and content pages. The concept is similar to ASP.NET 2.0 master pages, but the implementation is different (mostly because we wrote it before .NET 2.0 was released), and in some ways, more flexible. For example, it’s very easy for us to replace the templates without recompiling the application.
The pages also support localization, and localized strings are also managed independently of the application. In fact, we encourage community members to help in providing localized strings in their own language, and provide a tool for this. See http://dn.codegear.com/article/33873 for details.
You can find some user-oriented documentation for the sites and application in the Help area of the Developer Network. I’ll also try to post more technical information here and/or on the Developer Network from time to time.
Stupid PageControl Tricks
Most of the user interface for our Delphi applications is generated dynamically, at runtime. When we display this dynamically-generated user interface in a page control, we don’t generate the user interface for each page until you first go to it. This is for performance reasons, as there is some overhead to this, mostly due to database access.
When we enabled XP themes for our applications, I started noticing a flashing on the screen the first time you went to each tab in the page control. A little testing demonstrated that the gradient background of the tab was being rendered before the controls were created.
This turned out to be a fairly difficult problem to solve. When the user changes tabs, I need to figure out to which tab they are changing, and create the controls for that tab if they are not already created. In the TPageControl.OnChanging event, I don’t know to which tab the user is changing. Unfortunately, both the TPageControl.OnChange and the TTabSheet.OnShow event occur after the background of the tab has already been painted. I needed a way to render the controls, if necessary, in between these events, but after the tab to which the user is changing is already known.
After some rooting around in the VCL source code, I came up with the following solution.
type TChangeToPageEvent = procedure (Sender: TObject; const APageIndex: Integer) of object; TChangeToEventPageControl = class(TPageControl) private FOnChangeToPage: TChangeToPageEvent; procedure DoOnChangeToPage(const APageIndex: Integer); protected procedure Change; override; public property OnChangeToPage: TChangeToPageEvent read FOnChangeToPage write FOnChangeToPage; end; { TChangeToEventPageControl } procedure TChangeToEventPageControl.Change; begin DoOnChangeToPage(TabIndex); inherited; end; procedure TChangeToEventPageControl.DoOnChangeToPage(const APageIndex: Integer); begin if Assigned(FOnChangeToPage) then begin FOnChangeToPage(Self, APageIndex); end; end;
Since I also create the page control itself dynamically, I don’t even have to install this on the component palette to make it work. Handling the OnChangeToPage event allows me to create the controls when the page to which the user is changing is known, but before the tab itself renders.
Share This | Email this page to a friend
posted @ Thu, 01 May 2008 18:26:36 +0000 by Craig Stuntz
ASP.NET MVC Membership
One of my disappointments with Ruby on Rails is that it provides no support whatsoever for site logins/membership, which I consider to be a fundamental part of many database-driven websites. Of course, the Rails community has responded — and responded, and responded, and responded — to this need. The Rails wiki notes that there are about a "gazillion" different systems for solving this problem in Rails. I’ve tried acts_as_authenticated, which does work, but, at least at the time I tried it, required quite a bit of manual tweaking and patching to get it going.
ASP.NET 2.0 and higher, on the other hand, includes a standard membership framework which is sufficiently extensible that I’ve never seen an application which it couldn’t be adapted to fit, and, for most applications, it will work right out of the box.
The usual way to configure page access rights for the ASP.NET membership (in web.config), however, isn’t really applicable to the new MVC framework. Scott Guthrie had promised to cover this subject, but it was Rob Conery who finally delivered the goods. In short, you decorate your controller actions with attributes specifying whether the current user must be logged in, or be within a certain role, and a controller filter handles the work of validating those security assertions at runtime. Troy Goode built upon Rob’s original code, producing the ASP.Net MVC Membership Starter Kit.
The Starter Kit includes a lot of really valuable code, but very little documentation, so I’m going to write out instructions for "getting started with the Starter Kit." I’m presuming that you’re going to use the SQL membership provider in this example, although the membership framework does include support for OpenID and Windows Live authentication as well. You can, of course, use any authentication framework you care to.
- Download the full installer from the Releases tab at CodePlex. I’d also strongly recommend getting the source code download, which is available on the same page.
- Run the installer.
- If you’re using Visual Studio, you want to install the templates into VS. There’s an item on your Start menu (in the "Starter Kits") folder which will do that.
- If you are creating a brand-new site, you can start by just copying the sample project included with the download. Skip ahead to step 10.
- If you are adding membership support to an existing project, there’s a few things you need to do. First, you need to reference the assembly you’ve just installed. One way to do this is to simply reference the StarterKits.Mvc assembly in your project. But I chose to add the source code for the StarterKits.Mvc project to my solution instead, which helps with debugging.
- If you haven’t done so already, edit your web.config to include Forms authentication and set the Login URL, and specify a ConnectionString if you’re not using the default. I’ll include an example of this below. Note that you need to include a membership provider and a role provider. Also, if you’re not using the default database, run aspnet_regsql.exe to add the required metadata to your database.
- You are not required to use the controllers or views supplied with the Starter Kit. The filters in the Starter Kit will work fine with any controller you care to write. However, it’s probably easiest, especially at the beginning, to use the supplied code. If you’re using Visual Studio, you can do this with the templates you installed in step 3. Right click the Controllers folder in Solution Explorer, choose Add New Item, then choose MvcMembership Controllers FormsAuthenticationController. Do the same for FormsAuthenticationAdministrationController. Right click the Views folder, and add both View templates.
- The current release of the Starter Kit has a bug where the codebehind and codegen files are not added along with the aspx files to the FormsAuthenticationAdministration views. This will be fixed in the next release (see Troy Goode’s comment below for details), but if you encounter this bug, I’ve included a workaround in my defect report.
- You need to add a couple of lines to Global.asax in order to register the routes for the new controllers. You can copy those from the sample project.
- Your site should now work, but there are a few things you need to do in order to configure e-mail and the like. These are indicated with TODO comments. You can find these in the Task List.
As promised, here is what you need to add to web.config to use a custom database. I’ve omitted the connection string definition, which is specified like any other connection string.
<authentication mode="Forms"> <forms loginUrl="/Login"/> </authentication> <roleManager enabled="true"> <providers> <clear/> <add name="AspNetSqlRoleProvider" connectionStringName="MyConnectionString" applicationName="MyApp" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </providers> </roleManager> <membership defaultProvider="MySqlMembershipProvider"> <providers> <add name="MySqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="MyConnectionString" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="MyApp" requiresUniqueEmail="true" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression=""/> </providers> </membership>
Share This | Email this page to a friend
posted @ Tue, 29 Apr 2008 15:20:50 +0000 by Craig Stuntz
Literate Programming
Andrew Binstock’s recent interview with Donald Knuth has received a lot of attention for his comments about unit testing, multicore, and other things of which Knuth is skeptical, but I was more interested in his comments on the topics which he endorses. Knuth is the originator of (amongst many other things) literate programming. One way to think about literate programming is that it turns the notion of commenting on its head. In a traditional program, you use relatively few comments, and partition the comments with the limiters. Literate programs are mostly English text, with compilable code delimited.
Knuth seems uncertain as to why literate programming hasn’t really taken off:
Jon Bentley probably hit the nail on the head when he once was asked why literate programming hasn’t taken the whole world by storm. He observed that a small percentage of the world’s population is good at programming, and a small percentage is good at writing; apparently I am asking everybody to be in both subsets.
Well, sure, but even if one assumes that 1/10 of working programmers are decent at their job, and 1/10 of those can express themselves in English and a reasonably clear manner, that’s not really enough to account for the low use of literate code. There are number of good bloggers and technical publication authors, and literate coding would seem to be well suited for these tasks. Maybe we just need some better examples to get the ball rolling.
You don’t have to read it many books on software development before you’ll encounter one with source code examples which produce erroneous results, or even fail to compile altogether. It’s an absurdly common error. Taken to an extreme, it can be maddening. In the past month or so, I’ve been reading the preprints at Bruce Eckel and Jamie King’s new book on LINQ. One interesting thing about this book is that in the process of writing it, the authors have developed a build system which builds all of the source code examples, including verifying the in-line assertions, and even goes so far as to ensure that the examples which are not supposed to build at all really do fail to compile. The finished book may not be flawless, but the examples are going to work.
I think it’s a perfect application of literate programming; I cannot think of a better solution to this problem.
Share This | Email this page to a friend
posted @ Mon, 28 Apr 2008 21:03:50 +0000 by Craig Stuntz
Select your location
There’s more to the new web site than first meets the eye. One of the features we’ve added is making it much easier to buy CodeGear products online. If you look at the top-right corner of the page, you’ll see a bunch of icons, and a drop-down menu that let you select your location.
Selecting your location is a Good Thing™ - when you click on the "buy" icon (the one that looks like a couple of credit cards), the site redirects you to the correct shop site for your selected location. If you’re logged in, the site uses your country selection from your CDN account, but you can override this setting by selecting a location from the menu. To go back to your account settings, select "My default location" from the menu.
The cool thing about these buttons is that they’re automatically generated based on a database of products, shop sites, and locations. Both the "download" and the "buy" buttons are generated based on where you are (physically and on the site). At least, I think it’s cool. Users don’t really case, but developers should appreciate how much cleaner this is than manually typing in links for each product page.
Anyway, getting the correct shop site is just one of many things the web site can do once you select your location. It can give you content specific to your location, for example. Other features will be made visible in the future, so make sure you set your location.
Server Response from: dnrh1.codegear.com
