Continued from Part Four
Borland Delphi 2005 Architect contains
a featureset called Enterprise Core Objects 2,
which allows us developers to create applications
based on a model (with objects, inheritance and
associations), which can be made persistent in
a DBMS, and used to create GUI as well as web applications.
In this multi-part article, I’ll use Delphi
2005 and Enterprise Core Objects to define and
implement an application handling web logs - also
called blogs. This
time, with the focus on RSS Feeds, adding an ASP.NET
ECO Web Service to the existing ASP.NET ECO application. |
ECO Web Service
Technically, we don’t need a web service to return
the RSS feed – a special ASP.NET HttpModule would
. In fact, the RSS feed itself will just be a static
XML document, since it won’t change that often
(so it’s actually more efficient to regenerate
it when the EcoSpace is modified with a new Category
or Post, but not more often). However, just for testing
purposes, let’s assume we also want a web method
to refresh the generated RSS feed file (so we don’t
have to change the model just to test the RSS file generation).
If you want to play along, and reuse the existing model,
just reopen the Weblog.bdsproj project, and then do File
| New – Other, go to the New ASP.NET Files
in the Delphi for .NET Projects category, and select
the ECO ASP.NET Web Service icon, as shown below.
This will create a new ECO web page with files WebService1.asmx and WebService1.pas and add them to the Weblog project.
Since the name WebService1.asmx will become part of the
URL to call the Web Service, let’s rename WebService1 to RSS using File | Save As.
Now, inside the RSS.pas file, you’ll notice that
the Web Service itself is called TWebService1, which
also is not very descriptive of our particular web service,
so let’s rename that TWebService1 to TRSSWebService (personally, I use refactoring for that).
Alternately, if you do not want to reuse the Weblog
application, you can create a fresh new ECO ASP.NET Web
Service project. In that case, you will need to design
a model and make the model persistent (like we’ve
done in the previous articles, so I won’t repeat
that information here again).
RSS Web Service
Moving along to the design area for the RSS web service,
you may notice that this design area RSS is not empty,
as with a “regular” ASP.NET Web Service,
but it already contains a component called rhRoot. This
is actually the root handle, which should be assigned
to our EcoSpace. We can make sure of that by pointing
the EcoSpaceType property of the rhRoot component to
WeblogEcoSpace.TWeblogEcoSpace.
Let’s now consider what kind of RSS feeds I want
to offer. I want to offer an RSS file with the complete
feed, but also individual RSS files for all categories.
The latter could be made dynamic (based on the available
category instances in the EcoSpace), but since it’s
only my weblog, I’ve decided to hardcode the categories,
as we’ll see in the first listing below.
As a web service interface, I want to expose a method
that can refresh the static RSS files. Called RefreshRSS,
it will call a method GenerateRSS that will produce an
RSS feed for all posts in the given category.
The definition for the WebMethod RefreshRSS is as follows:
[WebMethod]
procedure RefreshRSS;
With the following implementation, calling a yet to
be designed and implemented method GenerateRSS (the main
topic of this article):
procedure TRSSWebService.RefreshRSS;
begin
GenerateRSS(EcoSpace);
GenerateRSS(EcoSpace,
'.NET Framework', 'dotnet');
GenerateRSS(EcoSpace,
'ADO.NET / BDP', 'adonet');
GenerateRSS(EcoSpace,
'ASP.NET', 'aspnet');
GenerateRSS(EcoSpace, 'Compact
Framework', 'cfnet');
GenerateRSS(EcoSpace, 'Conferences
/ Events', 'events');
GenerateRSS(EcoSpace, 'Delphi',
'delphi');
GenerateRSS(EcoSpace, 'Enterprise Core
Objects', 'eco');
GenerateRSS(EcoSpace, 'Windows
Longhorn / Vista', 'vista');
GenerateRSS(EcoSpace, 'XML, SOAP & Web
Services', 'xml-soap');
DoneWithEcoSpace;
end;
Note that my RSS feed will only contain the Posts, and
not the Comments on the Posts. Also note that I want
all Posts (from all Categories) to be put in the RSS
feed when I call GenerateRSS without arguments, which
leads to the following definition of the method:
class
procedure GenerateRSS(EcoSpace:
Borland.Eco.Handles.EcoSpace;
const Cat:
String = ''; const FileName: String
= 'Weblog');
I’ve turned it into a class procedure, so anyone
can call it, without the need for an instance of the
TRSSWebService class. This means we
can later also call it from the ASP.NET web form pages,
for example after a Post has been modified (see the btnSaveChanges_Click event
at the end of this paper).
ECO Web Methods
Before we can implement this RSS Web Method, however,
we should first take a closer look inside the generated
source code for our ECO web service. Here, you’ll
find a number of example web methods to give you an idea
of how to get your hands on the EcoSpace from an ASP.NET
Web Service. Apart from the general HelloWorld example
web method, there are two ECO specific examples: OrderCount and NewOrder. The first one is an example of a read-only
web method, which leaves the EcoSpace intact. The second
is a web method that modifies the contents of the EcoSpace.
The main difference is that the latter needs to call
UpdateDatabase before calling the DoneWithEcoSpace method.
That’s actually no different than we did in the
ASP.NET Web Forms application. However, unlike an ASP.NET
Web Forms application, an ASP.NET Web Service doesn’t
use visual controls to do the data binding. We don’t
use DataGrids or TextBox controls now, so the use of
an ExpressionHandle component is also unnecessary.
The example OrderCount web method shows how to read
data and return it in a non-visual way. This automatically
generated code is as follows:
function TWebService1.OrderCount:
integer;
var
OclService: IOclService;
ResultElement:
IElement;
begin
OclService := EcoSpace.GetEcoService(typeof(IOclService))
as IOclService;
ResultElement
:= OclService.EvaluateAndSubscribe(nil,
'Order.allInstances->size', nil,
nil);
Result
:= Integer(ResultElement.AsObject);
DoneWithEcoSpace;
end;
The EcoSpace used here is the EcoSpace property from
the web service itself (note that the class for the OrderCount method is still TWebService1 and was not renamed to TRSSWebService by refactoring, because it is placed in comments). The
get_EcoSpace method is implemented as follows:
function TRSSWebService.get_EcoSpace:
Borland.Eco.Handles.EcoSpace;
begin
if not Assigned(fEcoSpace) then
begin
fecoSpace
:= TEcoSpaceProvider.GetSessionFreeEcoSpace;
rhRoot.EcoSpace
:= fEcoSpace;
end;
Result := fEcoSpace;
end;
This code makes sure that the EcoSpace is obtained from
the EcoSpaceProvider (which in turn can retrieve it from
the pool of available EcoSpaces).
Anyway, back to the example OrderCount method, which
uses the EcoSpace property to call the GetEcoService method, checking to see if the IOclService is implemented,
returning the IOclService as result. Using the IOclService,
we can evaluate OCL expressions, which is just what we
would do using ExpressionHandles in regular (visual)
ECO applications.
Note that the Delphi online help seems to suffer from
a little copy-and-paste daemon when it describes the
way to obtain the IOclService (by incorrectly using the
typeof(IStateService), which is also used to describe
the way to obtain most of the other ECO interfaces. Obviously,
we need to pass the actual typeof(Ixxx) here.
IOclService
The IOclService interface offers a number of helpful
methods to evaluate OCL expressions, including the EvaluateAndSubscribe method to evaluate the given OCL expression and subscribe
to the result. The Delphi 2005 online help is a bit sparse
here, and also forgets to list a number of additional
methods, like the simple Evaluate method that returns
an IElement. This method – which is mentioned in
the Delphi 2006 helpfile - is actually more useful in
the stateless ASP.NET environment, since I don’t
need the subscribe trigger to obtain the result of my
OCL expression, so in my opinion, the example call to
OclService.EvaluateAndSubscribe could have been replaced
by OclService.Evaluate instead.
Once we have the resulting IElement, we can cast it
to an Object using the AsObject method. This will either
return a simple value, like with the example expression,
or an ECO class type that we can then cast to the correct
ECO type and use (like I’ll be needing for all
Posts of a selected Category).
Let’s see how the code for my GenerateRSS would
look like. First of all, we also need to obtain the IOclService,
as follows:
var
OclService: I OclService;
begin
OclService := EcoSpace.GetEcoService(typeof(IOclService))
as IOclService;
and then I want to find all instances of Posts, ordered
(descending) by the date they are posted. This can be
encoded as follows:
OclService.Evaluate('Post.allInstances->orderdescending(Posted)')
The special issue we have to take into account, is that
this evaluation doesn’t return a single value or
class instance, but rather a collection of Posts (even
if there are 0 or just 1 Post, you will get a collection
as result). So instead of a single IElement, we must
assign the result to an IElementCollection, which changes
the code to:
var
ResultElements: IElementCollection;
ResultElements :=
OclService.Evaluate('Post.allInstances->orderdescending(Posted)').GetAsCollection;
And now we can iterate through the ResultElements and
cast them as Post instances, as follows:
var
Posts: Post;
for i:=0 to ResultElements.Count-1 do
begin
Posts
:= (ResultElements[i].AsObject as Post);
The full source code for the GenerateRSS method will
follow at the end of this article. First, let’s
recall the fact that I wanted to pass an optional name
for a specific Category, so we would only see Posts that
belong to that category. That means a different OCL expression,
selecting the Category with a specific name, and then
returning the ordered Posts of that particular Category
instance. Given that Cat is the string with the name
of the Category, the OCL expression is as follows:
ResultElements :=
OclService.Evaluate('Category->allInstances->select(c
| c.Name = ''' +
Cat + ''').Posts->orderdescending(Posted)').GetAsCollection;
The remaining code can be the same, working with the
individual Post objects from the collection of Posts.
Producing RSS (XML)
The final step is simple now, and consists of nothing
more than producing a static XML page with the RSS of
the selected Posts. An RSS feed is in fact nothing more
than an XML document using a specific set of allowed
elements and values.
I’ve used the XmlTextWriter class to produce the
RSS feed in the RSS subdirectory of my virtual directory
on the web server. To get the RSS subdirectory, I should
first find out where the Weblog virtual directory itself
is located, which can be done by calling the Server.MapPath method, as follows:
HttpContext.Current.Server.MapPath('\Weblog')
+ '\RSS\Weblog.xml', nil);
After that, it’s just a matter of specifying the
formatting (none instead of indenting, since I would
expect no human to want to read the RSS XML), and the
rss starting element.
The more challenging problem I faced here was the format
of the dates. RSS checkers gave me errors when I tried
to format the DateTime fields (like Posted) to a string,
since RSS is very strict on the date format. I had to
construct a special DateTime formatting expression as
follows:
Feed.WriteElementString('pubDate',
Posts.Posted.ToString('ddd,
dd MMM yyyy HH:mm:ss zz')+'00');
Note the extra two '00' characters that
I had to append, since the normal +1 would not be accepted
by the RSS format checkers. This is one of those times
where I hate dates, but at least it all works now.
You can view the complete listing for the GenerateRSS
method HERE (in a popup window) |
As you can see in the code of this method, I’m
using the Summary field in the RSS feed, instead of the
complete contents of the blog Post. If a Summary is not
available, then I’m taking the first line from
the blog Posts (i.e. the text in the Contents before
a dot followed by a space, carriage return or line feed).
I also include a link to the blog Post, and for that
I need the Id of the Post, which can be obtained by calling
the IdForObject function from the IdService interface,
passing the Posts.AsIObject as argument, as follows:
Feed.WriteElementString('link', WeblogUrl +
IdService.IdForObject(Posts.AsIObject));
Note that the actual WeblogUrl is obtained from the
AppSettings in the web.config, right next to the ECO
Pool settings, as follows:
<appSettings>
<add key="Borland.Eco.Web.MaxPool" value="0" />
<add key="Borland.Eco.Web.MaxAge" value="600" />
<add key="URL" value="http://85.146.33.29/Weblog/Blog.aspx?RootId=" />
</appSettings>
And finally, we can incorporate the call to GenerateRSS into
the btnSaveChanges_Click,
so a new RSS file will be generated each time a Post
is modified (by me).
procedure TWebForm1.btnSaveChanges_Click(sender:
System.Object; e: System.EventArgs);
begin
with (rhRoot.Element.AsObject as Post)
do
begin
Title
:= tbTitle.Text;
Contents := tbContents.Text;
end;
UpdateDatabase;
TRSSWebService.GenerateRSS(EcoSpace);
DataBind
end;
Which produces the RSS that you can access from http://www.drbob42.com/Weblog.xml as
well as http://www.eBob42.com/Weblog.xml.
By the time you read this part, Delphi 2006 is almost
released, and so is the next version of Enterprise Core
Objects (dully called ECO III). There are a number of
enhancements and new capabilities added to ECO III, which
can be used to enhance the existing weblog application.
As an example, the new state machine capabilities of
ECO III can be used for the blog comments: ensuring that
a comment which has just been submitted will be in the “just
submitted” state, and will require at least one
additional reviewing step before it’s in the “approved” state
which will lead to its publication on the web. And that’s
but one of the new features in ECO III, so stay tuned
for next time (and don’t forget to subscribe
to my RSS feed or leave me any comment, and I’ll get
back to you next time with more blogging!
All this and more next time, so stay tuned...
|
Bob
Swart (aka Dr.Bob - www.drbob42.com)
is an author, trainer, developer, consultant and
webmaster for Bob Swart Training & Consultancy
(eBob42) in The Netherlands, who has spoken at
Delphi and Borland Developer Conferences since
1993. Bob has written chapters for seven books,
as well as the Borland Delphi 8 for .NET Essentials and Delphi
8. ASP.NET Essentials courseware manuals licensed
by Borland worldwide, and is selling his updated
Delphi 2005 courseware manuals online at http://www.drbob42.com/training.
Bob received the Spirit of Delphi award at BorCon
in 1999, together with Marco Cantù. |
November 2005 |