Archives

Archives / 2005
  • Must...code...something...

    Gah, I got to code something before I go nuts... What I do most of the days now is write Word docs, Powerpoint slides and a few "conceptual" and "high level" "architecture blueprints" and you know... things you do when you "move on" or "step up" from being a solution architect to program architect. Instead of writing class diagrams and code base classes you draw process chevrons and the closest thing you get to coding is a Word macro for pasting in unformatted text...

    I'll probably code up something at home tonight... Perhaps dig out some of my old mod/mutator skills and hack up some rocket-boots for UnrealTournament :p

  • [Java] Lomboz Stable Build: S-3.1RC2

    From the ObjectWeb Lomboz site. Seems that ObjectWeb has created a better Lomboz install package:

    Lomboz Complete (All-in-one) Installation
    The Lomboz Project focuses on a prodiving an easy install J2EE IDE tool that is prepackaged with all the necessary plugins that you can start using immediately in one package. Due to its content, this package can take a long time to download. It includes eclipse sdk 3.1, emf, gef, jem, wtp and lomboz all together. Dowaload it and you can get started right away.

    http://lomboz.objectweb.org/downloads/drops/S-3.1RC2-200508311616/

  • [Vista] Vista on VPC Cont.

    I forgot to mention that I got Vista installed properly in the end - thanks to a few good blog posts out there. Basically - install Vista and end up with the terrible 4 bit graphics, start the installations of the additions and run it until it hangs near the end, go into the hardware drivers manager and just disable/remove the VGA display driver, restart and voilà - you got beautiful S3 graphics!

    Note though that it is terribly, terribly slow, even if you got a gig of memory and a 3+ Ghz proc. I guess it's faster if you do a real install instead of VPC, but I don't want to ruin my current XP install. I wonder if the MS peep on the PCD were actually running the same Vista bits, coz their Vista-on-VPC where definitely running way faster than anything I've managed to install. 

  • [Vista] Installing Vista on VPC... ZZzzzzzz.....

    It took ages to install Vista on my VPC and som hassle getting past the disk formatting. Now I've tried to install the additions to it, and it has been running for some 3 hours. I wonder if it has stuck somewhere. Perhaps time to look at some of the Vista/VPC blogs out there....

    Also need to do something about the display drivers. The supplied drivers did of course not work. I manged to get win2k drivers working on an older Longhorn installation, so I'll see if I can do the same trick again - download driver installation pack - unpack all the files from the cab/exe and point the driver install wizard at the unpacked files - install, reboot and hope for the best :D

  • [.NET 2.0] Creating your own CatalogPart

    In two earlier posts (here and here), way back in beta1, I showed how to create your own CatalogPart which could display a list of available web parts from a database or something. Some people will be quite happy to add web parts in a declarative way, but I wanted a solution which were more dynamic. In the future I'll try to write some sample code where you create web parts in a separate library and just drop them in a directory somewhere in the portal, and the web parts gets picked up automatically in the CatalogZone without the need to change the code. Just as a proof of concept.

    Anyway, I noticed that the code changed somewhat in beta2 compared to how it used to work back in beta1. There are two ways (at least) you can add your own CatalogPart to a web page:

    1. Create your own CatalogPart and declare it in the CatalogZone, in the aspx-page. This is the simplest way. See sample below.
    2. Create your own CatalogZone and declare it in the aspx-page. This CatalogZone then adds your CatalogZone declaratively or in run-time. A bit more coding and there is no need to do it this way unless you plan on doing something special within your CatalogZone. What that may be, I don't really know :)

    So, sample code for MyCatalogPart:

    using System;

    using System.Web.UI.WebControls;

    using System.Web.UI.WebControls.WebParts;

    using System.Collections;

     

    namespace MyCatalogZones

    {

          public class MyCatalogPart : CatalogPart

          {

                public override WebPartDescriptionCollection GetAvailableWebPartDescriptions()

                {

                      //Generate a list of available web parts that shows 

                      //up in the catalog, each with a unique ID

                      //This sample code just adds one web part

                      WebPartDescription wpDescription = new WebPartDescription("MyWebPartID", "My Web Part", "A description of the web part", null);

                      ArrayList arrWpDescriptions = new ArrayList();

                      arrWpDescriptions.Add(wpDescription);

                      return new WebPartDescriptionCollection(arrWpDescriptions);

                }

     

                public override WebPart GetWebPart(WebPartDescription description)

                {

                      //a bit of "dummy" code to add a specific web part

                      //which maps against "MyWebPartID" from the catalog

                      if (description.ID == "MyWebPartID")

                      {

                            //returning an instance of the selected web part

                            MyWebParts.MyWebPart newPart = new MyWebParts.MyWebPart("My Web Part");

                            return newPart;

                      }

                      return null;

                }

          }

    }

    Note that in the code above, when you create the list, you may want to check if the user has already added an instance of the web part to his page before you add it to the list. This depends on if you want to let the users add a web part several times to the page or not. The web part may be such that it can be configured to show different content depending on its properties, so why not...

    This is some sample code for adding your CatalogPart to a normal CatalogZone in an aspx-page:

    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

    <%@ Register TagPrefix="jd" Namespace="MyCatalogZones" Assembly="MyCatalogZones" %>

     

    <!-- lots of aspx code here... -->

     

        <asp:CatalogZone ID="CatalogZone1" runat="server" >

            <ZoneTemplate>

            <asp:DeclarativeCatalogPart ID="DeclarativeCatalogPart1" runat="server">

              <WebPartsTemplate>

                <asp:label id="aPartInTheCatalog" runat="server" title="Catalog webpart">

          <h2>Dummy part</h2>

          <p>This is a part in the catalog.</p>

        </asp:label>

              </WebPartsTemplate>

            </asp:DeclarativeCatalogPart>

            <asp:PageCatalogPart ID="PageCatalogPart1" runat="server" /> 

            <jd:MyCatalogPart ID="MyCatalogPart1" runat=server Description="My own dynamic catalog part" Title="My Special Parts Catalog" />

          </ZoneTemplate>

        </asp:CatalogZone>

     

    <!-- lots of aspx code here... -->

     

    You don't really have to have the DeclarativeCatalogPart and the PageCatalogPart there, but if you allow users to close your dynamically added parts, you may want to let them add them again, so the PageCatalogPart may be useful :)

     

  • XLINQ blows my mind

    Just spent a couple of hours setting up a couple of VPC images with all "The Goods" we got from PDC'05. Last thing I installed was the LINQ tech preview. I've just spent a few minutes looking at the LINQ samples that comes on disk 4 and I've tried a few DLINQ stuff which was really impressive, but the XLINQ features just blows my mind. The whole style of programming seems so natural compared to the old XmlDocument way of doing things. Let me copy a few lines of code from the RssAggregator sample, a method which pulls Rss from a couple of feeds, extracts the items and returns a list of all these items:

    static IEnumerable<XElement> GetItems() {

    string[] feeds = {

    "http://blogs.msdn.com/cyrusn/rss.aspx",

    "http://blogs.msdn.com/mattwar/rss.aspx",

    "http://blogs.msdn.com/lucabol/rss.aspx",

    "http://www.pluralsight.com/blogs/dbox/rss.aspx",

    "http://blogs.msdn.com/jomo_fisher/rss.aspx"

    };

    foreach (var str in feeds) {

    var feed = XDocument.Load(str);

    var items = feed.Root.Element("channel").Elements("item");

    foreach (var item in items)

    yield return item;

    }

    }

    I love the way you get hold of a specific element in the document with Element("nodename"). Not sure I'm comfortable with the "var" thing though. That's the "implicitly typed local variables" that comes with C# 3.0. I guess it's the way I read code. If you want to read about the new features in C# 3.0, you can download the spec from here.

  • Trying to install Windows Vista on VPC

    As everyone else on the PDC, I got a PDC-version of Windows Vista. I tried to install in on a VPC yesterday, but I never got past the install step where you have to specify on what disk to install to. I tried (within VPC) to delete the virtual partition and format it, but it never got available. I wonder if anyone else experienced the same problem...
  • [PDC'05] Last day at the conference

    Best session yesterday was probably the web services interop session with Simon Guest and Kirill Gavrylyuk. To show reliable messages between .NET and Java, Simon hocked up a heart beat sensor (which sat on his finger) onto a .NET web service. Then he sent data from that sensor over to a Java client, through a pre release of Axis 2.0. Funny (for the audience anyway) was that Simon heart was pounding so rapidly, the scale on the client couldn't handle it. They had set it up to a maximum of 100 bpm, but he was going at around 130 (!) and the annoying beep-beep-beep that was going on all the time didn't make it easier for Simon to calm down. It was hillariously fun. Make sure to watch that webcast if it comes available sometime in the future.

    They showed how to send and receive WS-Security messages as well as Reliable Messages and how to send binary data with MTOM. MTOM stands for Message Transmission Optimization Mechanism.

    Later in the evening I sat down and had a discussion with Kirill about the WS design we have in the project I'm working with back home. We seem to be on track, but there are a few things I could change on the architecture to be a bit better prepared and in line with the future. Should perhaps start looking at sending SAML tokens instead of just a simple proprietary ticket.

    I spent the whole morning (2 sessions in a row) at the Architecture Symposium here, where they discussed a fictionarly SOA case from end to end. It was quite interesting, but the reality isn't always as Microsoft describes it. Setting up Aggregation Services and Entity Services in front of one or more legacy system is the recommended design if you have a heterogenous environment. Maybe stick in BizTalk or similar workflow middlewhere with adapters if you think you need it to talk to bigger systems like SAP, PeopleSoft and such.

    After this last session I'm going to now, it's off to the LAX airport for the lovely trip home...

  • [PDC'05] Did some coding with "indigo"

    Or WCF (Windows Communication Foundation) as it is now called, and it is very, very powerful. I sat down in the lab area where they have everything you need installed, complete with lab instructions, and tried out a few web services and such. Support for contract first coding is better now, where you first create the "contract" as an interface and you then go on and implement this contract with code. You can do the same sort of things on the client. I like that.

    I also had a quick look at the LINQ stuff, and it's pretty cool coding. LINQ style coding will probably replace the ADO.NET code you do in your Data Access Layer if you use such an architecture. It is powerful and intuitive and there is no longer a need for a mapper design pattern where you map a strongly typed data set (or similar datatable object) into a DTO (Data Transfer Object), because you can create the DTOs directly with LINQ. In ADO.NET 2.0 you can continue using the same kind of code and patterns you used in .NET 1.1, but I would suggest everyone to look at the ObjectDataSource stuff and

    Something I would like to look at in more detail is the workflow foundation, but that will have to wait until I get home.

  • [PDC'05] ”I think I may need a bathroom break. Is this possible?”

    Lol, maybe they had as much free coke, coffee, juice and ice cream at the top meeting Mr Bush was attending as we have here at the PDC'05 convention, but if that was the case I understand him. If not, it's another great quote in the never endling list of quotes the most powerful guy in the world has produced, this one in writing... :)

    Anyway, it feels encouraging that he actually can write and that he has normal human needs... there is hope for the world after all. ;)

  • [PDC'05] Impressions from yesterday

    Yesterday was the best day so far at the PDC. Most impressive things was first what will probably become 'ADO.NET 3.0' or something similar - LinkQ or DLinkQ, which is dynamic language integration (or something like that). Eric blogged about that. Next thing which was really, really cool and got lots of applause from the session attendees was the new integrated workflow foundation, which is basically a powerful workflow engine. The Windows Worflow Foundation is a part of the WinFX subsystem and will let you host workflows in lots of different containers, like IIS, a windows forms app, sharepoint, SQL server or even in a console app! It's also dead simple to use. It's as if you ripped out the basic workflow engine from Biz Talk and created a powerful set of APIs to use against it. It will be used ALOT in the future. The graphical workflow designer in Visual Studio was impressive.

    Last night was Universal Studio Theme Park night and it was just great! Me and my friends rushed off to the Jurassic Park and The Mummy rides as fast as we could to get a head of all the other 8000 or so people there. Really cool rides I must say :) If you ever go to Los Angeles, don't miss it. We tried most of the rides in the park, but I think Jurassic and Back to the Future was the best :)

    Off to the next general session now.... *running*

  • [PDC'05] Blogging on Windows Vista

    Just had a great breakfast. Amazingly large selection of weird cereals. We all laughed when my buddy Jan-Erik opened his "TRIX - Fruity Sweetened Corn Puffs!" pack. Looked more like a box of candy than breakfast cereal to me :)

    Atm I'm standing in the Windows Vista Internet Alley, where they have set up hundreds of machines running Windows Vista (build 5219). The user experience is just great, lots of animations and stuff. In the future when coding for the WinFX platform I think you have to be a bit careful to not just add all of these moving and zooming stuff just because it is easy to do it. But it is cool, I must admit. Everything got a kind of soft touch to it. I wonder what kind of graphics hardware you need to run Vista graphics in optimal speed. No wonder ATI is platinum sponsor of the PDC event :D

    One of the things that drew the longest run of applause was probably when Jim Allchin (I think it was) jacked a standard USB memory into the Vista box and it got recognized as internal memory! Time to dig out all my stray USB memory sticks and get them to use, my laptop will look like a christmas tree, USB mems sticking out in every socket ;)

    Off to the next general session now with Eric Rudder and Steven Sinofsky.

    One thing I just noticed what that the rich text editor in .Text doesn't load up in IE 7

  • [PDC'05] Off to meet some other Swedes...

    Shorty we're off to meet with some of the other 100 or so Swedes that are here at the PDC. Microsoft SE has arranged for a get-togeather at a hotel close by. Will be fun to meet a few old friends that I know should be there tonight.

    Tomorrow I'll attend sessions about "Avalon" (now called the Windows Presentation Foundation), another (hopefully better) session about "Indigo" and 2 sessions about CRM solutions and "Atlas". Atlas is functionality for more easily doing AJAX-style programming in ASP.NET 2.0 and later. Looking forward to tomorrow.

  • [PDC'05] .NET "providers" is really interesting

    The concept of pluggable providers in .NET is really interesting and something I want to have a deeper look at when I get back home. The last session I was at today was about providers and how you could (quite easily) replace someting like the default membership and roles provider with your own.

    Stefan Schackow did a very good presentation about providers, which sort of saved the afternoon. I attended the COM202 session about "A lap around the Windows Communications Foundation" and it wasn't very good at all. The speaker jumped right into all the nitty gritty details you could play with in WCF and I bet 80% of the guys in there listening didn't follow him at all.

  • [PDC'05] Hurry, hurry

    The keynote with Bill G and all the others was looooong, and they ran 25 minutes over time so we had to run to grab lunch and in 10 minutes I have to be at the "Indigo" session.

    The keynotes were pretty good, Bill showed a funny video and they demoed a bunch of stuff - some more interesting and impressive than others. Highlight was when 4 of the top MS architects were at the stage at the same time, doing a combined coding demo - Don, Chris An, Anders and Steve Guthrie. That was neat. When I get back to work I'll try to demo a few of the cool things with Windows Vista and the built in RSS-capabilities you there and within Office 12.

    Time to run to the next session!

  • [PDC'05] Switched to another session

    So, originally I had planned to attend the SOA pre-conf session today, but I had a look at the material and realized I've been through most of that already and much of it was pretty basic. So I decided to change to the session about building reusable libraries with Brad Abrams. But again, that session was also pretty basic, and I've been doing coding guidelines for quite some time at work, so it wasn't that much new stuff to me. So again (right after the power failure) I decided to change session - so at the moment I'm sitting at the ASP.NET 2.0 session, where they are talking about new features in ASP.NET.

    So far so good. They've been covering Master pages, Themes and some of the controls like the menues and those. Right now it's about the Gridview and different datasources. It's cool, but I still get chills by all this declarative coding... ;)

  • [PDC'05] And all went black...

    If you've been in the "Big Hall" in the LA Convention Center, you know it IS pretty big. Imagine how dark it gets in there when there is a black out :) I was standing there writing a blog post when everything when black and silent. Power was out for almost an hour, then all started up well and sessions started again with some delay.

  • [PDC'05] Hollywood Boulevard

    So, last night we took the metro up to Hollywood Boulevard and took a walk and a big, fat hamburger. Pretty cool to see the hand- and footprints outside the Chineese Theatre and all the stars on the boulevard. Parts of Hollywood Boulevard is actually pretty run down, I was surprised to see that. The metro works pretty well, except that you had to wait pretty long before a train came along.

    Tonight we will probably walk up to Broadway and just look around.

  • [PDC'05] Pretty good start

    So, the pre-conference session (Building .NET Web Services Today with .NET 2.0 and WSE 3.0)  with Aaron Skonnard just ended, and it was pretty good. It didn't contain so much new stuff or wasn't as deep level as I thought it was going to be, but Aaron is a good speaker and I learned a couple of new things about WSE 3.0.

    Aaron spent almost 2/3 of the day on basic WS stuff that I think most people in the room might already know about, but these are pre-conf sessions so I guess they have to be a bit basic.

    Aaron did lots of coding (yay) and one funny thing was when he was going to create a demo with a web service returning datasets (which he claimed was evil). First he named the web service class 'dataset', then realized that it wasn't a very good name, so he changed it into 'webservice' which isn't that much better :) He just laughed and put a '1' after it and went ahead.

    Now we're off to see some parts of Hollywood I think...

  • [PDC'05] Cute little 9 guy

    Oh, forgot to mention that there was a soft little 9 guy in the bag too :) Like this one:

    Maybe I can put it next to my display and use it as a 'display-brick' when I get mad at something. I usually throw away CDs and DVDs, and they break so easily...

     

  • [PDC'05] Just arrived at the conference center

    Just checked in at the conference center and got my stuff: a bag (not a rucksack this time) which looks pretty good, a pretty simple gray kind of t-shirt with the PDC 'logo' on it and some magazines and stuff. Seems we have to wear these plastic bracelets (nice purple color) for the pre-conference sessions.

    The WLAN seems to working OK, just got connected with my PocketPC at once. Right now I'm standing in the big hall where they got thousands of PC hocked up to the Net, waiting for breakfast being served. I'm hungry!!

    The trip to LA took something like 18 hour all and all. I got up at 4am in Sweden, switched plane in Frankfurt, Germany (2 hours flight) and landed some 11 hours later in LA. Staying at the LA Sheraton Downtown on the 16th floor. The room is large and pretty good. I managed to stay awake til 7 or 8 in the evening before I passed out. Woke up really early, around, and at 6 my wife called :)

    Going to spend the day at the Web Services pre-conference session (Building .NET Web Services Today with .NET 2.0 and WSE 3.0)  with Aaron Skonnard. It's be interesting and great fun I hope. Write about that later.

  • Writing to Oracle CLOB fields using System.Data.OracleClient - shoot me down please

    If you're a .NET coder and does database stuff against Oracle, this is your chance to put things right :) Keep reading and add comments please!

    In one of our current projects we need to write large chunks of text to an Oracle CLOB field from an ASP.NET 1.1 application. We've been using the standard Microsoft System.Data.OracleClient libraries so far (to write smaller amounts of data) and it has worked out quite well for us. I didn't want to introduce another library pack like the Oracle ODP.NET even though the later versions of that pack may work very well.

    In older Java projects I've been writing and reading CLOBS and I know you have to handle those fields a bit different than normal VARCHARs and such. So I sat down and started to search the .NET Dynamic Help and whatever I could find on the Internet, and the funny thing is I found three different ways of doing it; 2 really simple and one not so simple. The weird thing is that I don't think the first two options is supposed to be working - but when I test them they work just fine. I'll publish the 3 ways here, and hopefully someone who knows what he's doing with .NET and Oracle can shoot me down in flames. I just want to do the right thing here.

    To test this, I have created a simple table in Oracle, which consists of 2 fields; ID (INT) and TEXT (CLOB). That's it. I'm reading a simple text-file created with Notepad, which is 499 KB in size.

    NOTE: This is just test-code, nothing you'd want to have in production. If you decide to use some of this code, please add proper error, exception and transaction handling to it.

    So, first way of doing it using an OracleDataAdapter, is (if I understand the Class Library docs) not supposed to be working. But it does:

    public void writeDataWithDA()

    {

               FileInfo fi = new FileInfo("c:/temp/testfile.txt");

               StreamReader sr = new StreamReader(fi.FullName);

               String clob = sr.ReadToEnd();

               sr.Close();

     

               OracleDataAdapter da = new OracleDataAdapter("SELECT ID, TEXT FROM CLOBTEST",ConnectionString);

               DataTable dt = new DataTable();

               // get the schema

               da.FillSchema(dt, SchemaType.Source);

     

               OracleCommandBuilder cb = new OracleCommandBuilder(da);

     

               int id = 2;

     

               // create a row containing the data

               DataRow row = dt.NewRow();

               row["ID"] = id;

               row["TEXT"] = clob;

               dt.Rows.Add(row);

     

               // update the table

               da.Update(dt);

    }

    The second way of doing it uses an OracleCommand and is also not supposed to be working (I think), but it does:

    public void writeDataWithCommand()

    {

               FileInfo fi = new FileInfo("c:/temp/testfile.txt");

               StreamReader sr = new StreamReader(fi.FullName);

               String tempBuff = sr.ReadToEnd();

               sr.Close();

              

               using(OracleConnection conn = new OracleConnection(ConnectionString))

               {

                          conn.Open();

                          Console.WriteLine("Connected...") ;

                          String strSQL = "INSERT INTO CLOBTEST (ID,TEXT) VALUES (1,:TEXT_DATA) ";

     

                          OracleParameter parmData = new OracleParameter();

                          parmData.Direction = ParameterDirection.Input;

                          parmData.OracleType = OracleType.Clob;

                          parmData.ParameterName = "TEXT_DATA";

                          parmData.Value = tempBuff;

     

                          OracleCommand cm = new OracleCommand();

                          cm.Connection = conn;

                          cm.Parameters.Add(parmData);

                          cm.CommandText = strSQL;

                          cm.ExecuteNonQuery();

     

                          conn.Close();

               }

     

               Console.WriteLine("Done!") ;

    }

    Now comes the third way of doing it. This is how the .NET Class Library documentation describes C/BLOB handling, by creating a temporary LOB object in Oracle and then write to that object before inserting in into the table. This also works fine, but it requires a bit more hassle with transactions and stuff:

    public void writeWithTempBlob()

    {

               FileInfo fi = new FileInfo("c:/temp/testfile.txt");

               StreamReader sr = new StreamReader(fi.FullName);

               String tempBuff = sr.ReadToEnd();

               sr.Close();

     

               using(OracleConnection conn = new OracleConnection(ConnectionString))

               {

                          conn.Open();

                          Console.WriteLine("Connected...") ;

                          OracleTransaction tx = conn.BeginTransaction();

     

                          OracleCommand tempcmd = conn.CreateCommand();

                          tempcmd.Transaction = tx;

                          tempcmd.CommandText = "declare xx clob; begin dbms_lob.createtemporary(xx, false, 0); :tempclob := xx; end;";

                          tempcmd.Parameters.Add(new OracleParameter("tempclob",

                                     OracleType.Clob)).Direction = ParameterDirection.Output;

                          tempcmd.ExecuteNonQuery();

     

                          //get the temp lob object

                          OracleLob tempLob = (OracleLob)tempcmd.Parameters[0].Value;

     

                          //transform into byte array

                          System.Text.Encoding enc = Encoding.Unicode;          //MUST be unicode encoded!

                          Byte[] b = enc.GetBytes(tempBuff);

     

                          tempLob.BeginBatch(OracleLobOpenMode.ReadWrite);

                          tempLob.Write(b,0,b.Length);

                          tempLob.EndBatch();

     

                          OracleCommand cmd = conn.CreateCommand();

                          cmd.Transaction = tx;

                          cmd.CommandText = "INSERT INTO CLOBTEST (ID, TEXT) VALUES (:ID, :TEXT)";

                          cmd.Parameters.Add("ID", 3);

                          cmd.Parameters.Add("TEXT", OracleType.Clob).Value = tempLob;           //insert the temp lob

                          cmd.ExecuteNonQuery();

     

                          tx.Commit();

               }

               Console.WriteLine("Done!") ;

    }

    Note that to write to the temporary CLOB, the text must be unicode or you will just get a mess of characters written :)

    So, since all three ways seems to be working just fine for me, what is the right way of doing this? Load your gun and fire away...

  • [.NET 2.0] A must-know for ASP.NET 2.0 developers

    I spent a couple of hours trying to figure out what was wrong with my Master/Details test form, when I ran upon this one. ASP.NET 2.0 doesn't seem to rebind after a PostBack if there is a ViewState available. Even if cache is turned off on the data source. Fritz wrote about this.

    Need to test this a bit more. Amazing how much there is to learn about .NET 2.0. My first thoughts about the new release was "yeah, it'll be cool changes to the IDE, refactoring and all that, then there'll be great additions to the c# language and a cart-load of new controls". But this is a change in ASP.NET programming model, isn't it? A small change, but nevertheless quite important, don't you think?

  • [Java] WSS4J version 1.0

    Sorry for all the Java spamming today, but if anyone interested in the Apache Axis and Web Service Security area, the Apache WSS4J people got a version 1.0 ready for download and use now.

    You can get to their site here. They got some pretty good documentation available now it seems. I believe you should be able to learn from my wss4j tutorial, based on the earlier version of wss4j. I posted a follow-up to that tutorial here.

    I'll download the latest version of wssj4 myself and give it a try now, because we're most probably going to use it in the near future. Jeez, I need to get more time to look at the ASP.NET 2.0 stuff too... Argh...

  • [Java] More about cookies in Vignette Application Portal (VAP)

    Another blog post about cookies in VAP...

    Other ways of setting cookies from a portlet?

    I got this message from Sascha about setting cookies from a VAP portlet:

    Hi, I've read you log about setting cookie in a portlet. First, I thought so too, but there actually _is_ a way to set a cookie from a VAP Portlet: you have to get the original HttpServletRequest from the HttpServletRequestWrapper instance in your JSP:

    public static HttpServletResponse getOriginalResponse(HttpServletResponse response) {
       HttpServletResponse result = response;
       if (response instanceof HttpServletResponseWrapper) {
          response = (HttpServletResponse)((HttpServletResponseWrapper) response).getResponse();
          result = getOriginalResponse( response );
       }
       return result;
    }

    I tried this, but from a JSP page in a JSR168 solution it doesn't seem to work. I think maybe Sascha meant a VAP specific portlet/module and not a JSR168 portlet. As far as I understand it, the HTTP headers have already been written when the portlet starts to execute, so it's too late.

    I got another obvious way of setting cookies from a portlet - do it from a client-side javascript:

    Setting cookie through javascript:

    <script>

    document.cookie = "jscriptcookie=testvalue";

    </script>

    This obviously works fine and you can also get cookies this way, but again, what I'm really after is a way to both get and set cookies from within a portlat class scope, say for example in the processAction() method. Below I have a way of getting a cookie from there, but setting it still seems to be impossible.

    A better way of getting cookies from within a portlet class

    Earlier we've had to grab cookies from a JSP page in a JSR168 portlet solution, but a friend of mine found out that VAP actually adds the "cookie" http header(s) to the PortletRequest properties collection, according to the JSR168 spec, section PLT.11.1.4. So from within a JSR168 portlet it's quite simple to get the cookies from the RenderRequest.getProperties("cookie"). Thank you Vignette, now make sure all future versions of VAP does the same please :)

  • [NET 2.0] Master Pages - Most useful feature in ASP.NET 2.0?

    I've started to read up on the new stuff in .NET 2.0 to prepare myself for the PDC, especially the ASP.NET stuff, and I must say that so far the most useful thing I've looked at is Master Pages. I've used the Wilson Master Pages in previous versions of ASP.NET and it has helped us much, but now in 2.0 with the intergration with the IDE it's just plain beautiful.

    I think the Master Pages feature will be used in a majority of the web sites out there in the future, don't you think?

  • Counting the days to the PDC

    Ah, back from vacation and haven't written anything in my blog for a long time now. Not long now until the PDC. Looks like I'll have 3-4 swedish buddies of mine visiting LA as well - it will be great! Have to sit down and plan my schedule on-line. According to the information on the webby, you should be able to have the schedul on your pocket pc! Have to have that!
  • [Books][.NET 2.0] Recommended .NET 2.0 books?

    I need your help. I'm going to the PDC and I need to read up on various .NET 2.0 stuff before going there, to get most out of it. ASP.NET and Web Services/Interop is my main focus, so if someone out there knows of a really good book to read, please comment. I got a few books that I've read already, like the "A First Look at ASP.NET v2.0" by Homer/Sussman/Howard, but I'm sure there are more updated ones.

    Thanks!

  • [PDC] Booked!

    I did my registration to the PDC today, it will be so interesting and fun going there. There will be so many cool tracks and session, it'll be a tough time figuring out which ones to attend to. When I get back I'll have to do some "awareness sessions" for my mates at work, but I don't mind at all doing that.

    It seems there will be lots of Swedish people going over the pond this time, so LA watch out :) The last time I was at the PDC I didn't get any chance to see anything of LA except for the Convention Center and the hotel. This time I'll try to go there a day or so ahead. 

  • Xml from a directory structure

    I needed to generate an XML document that showed all directories and html files in a certain folder and the folders below that. This is what I quickly wipped up:

    using System;

    using System.IO;

    using System.Xml;

     

    namespace MenuBuilder

    {

               /// <summary>

               /// Summary description for MenuBuilder.

               /// </summary>

               public class MenuBuilder

               {

                          public static void Main(string[] args)

                          {

                                     Console.WriteLine("Starting...");

     

                                     XmlDocument doc = new XmlDocument();

                                     doc.LoadXml("<directory>" +

                                                "</directory>");

     

                                     //recurse through directories and return XmlNodes

                                     XmlElement elem = GetData(@"c:\temp", doc);

                                     doc.DocumentElement.AppendChild(elem);

     

                                     doc.Save(@"c:\temp\dir.xml");

                          }

     

                          private static XmlElement GetData(string dirName, XmlDocument doc)

                          {

                                     //create a new node for this directory

                                     XmlElement elem = doc.CreateElement("dir");

                                     DirectoryInfo di = new DirectoryInfo(dirName);

                                     elem.SetAttribute("name",di.Name);

     

                                     foreach (DirectoryInfo dinf in di.GetDirectories())

                                     {

                                                //Recursively call the directory with all underlying dirs and files

                                                XmlElement elemDir = GetData(dirName + "\\" + dinf.Name, doc);

                                                elem.AppendChild(elemDir);

                                     }

     

                                     //Append the files in this directory to the current xml node

                                     foreach (FileInfo finf in di.GetFiles("*.htm*"))

                                     {

                                                XmlElement elemDir = doc.CreateElement("file");

                                                elemDir.SetAttribute("name",finf.Name);

                                                elem.AppendChild(elemDir);

                                     }

     

                                     return elem;

                          }

               }

    }

     

     

  • Back from vacation

    Back from 2 weeks of vacation in Turkey (Alanya) - probably the best vacation we've had. Great hotel, great staff, great weather. Apart from waking up at 4:30am from the pre-recorded morning prayers they play out from the top of the minaretes at maximum volume every morning I've got nothing to complain on :)

    Just a pile of e-mails to dig through now.... yack.

    I got several mails from my friend Jan-Erik about news in JCP, guess I have to check them out.

  • Looking at WSRP and .NET?

    If you're building portlets and your selected portal platform has WSRP support, I recommend you to have a look at the WSRP toolkit for .NET from NetUnity. We've been evaluating it for a couple of weeks now and it seems to work pretty well. It plugs perfectly into the VisualStudio.NET IDE and the programming model is the same as if you were coding any ASP.NET web page or user control. You just add a few new attributes and have to remember that this is a portlet you're developing. Performance of WSRP is not optimal, considering the amount of data going between the consumer (portal) and the producer (the portlet web service), and remember that a WSRP portlet is not stateless.

    Personally I think the price for the dev license and the run time license it a bit too much, but it may be worth it for you. The WSRP producer also needs a proper database to keep track of state and a few other things (not sure exactly what, but anyway, a DB is a pre-req).

  • I think I got the first and original IBM ThinkPad

    When I got my first job at IBM back in -88 we got small brown notepads with the word 'THINK' printed on the cover. We're talking good old pen and paper stuff here. I would guess that was the original IBM ThinkPad :) I no longer work for IBM, but when I was cleaning out a few old boxes back home I found that "think pad" again.

    I took that old notepad with me to work today and showed it to some of my younger work mates, but they just looked strangely at me. I wonder why...

    I must try to upload an image of it I guess.

    Lol, according to what's printed inside the cover, I can "order refills from the nearest IBM office or write to IBM Corporation, Armonk, New York..." Maybe I should try that, I'm running out of sheets :p

  • Sending events from user controls to the parent containers

    Right, this is how I prefer to do things, instead of the other solution I wrote about earlier. The scenario is as follows (again):

    Imagine having a web form, which has a label control on it and this web form also loads a user control during run-time. The user control has a single button on it and when pressed, the button should set the text value of the label to the current date and time.

    This sample lets the user control raise an event, which the parent page catches. The event contains information about what text the label should be set to.

    First you define the event argument class so you have a way to send data to the “listener”. This sample is really simple, and contains just a single text property, which will contain a string:

     

    public class MyEventArgs : EventArgs

    {

               private readonly string text;

     

               public MyEventArgs(string text)

               {

                          this.text = text;

               }

     

               public string Text

               {

                          get { return text; }

               }

    }

     

    Then you go into your user control class and set up the event handler and code to trigger/raise the event with the correct argument values. One could argue if it's really necessary to have the OnEvent() method, but I prefer that, it feels cleaner. I also define the delegate for the event handler in the same class file, you may want to have this in some other place:

     

    public delegate void MyEventHandler(object sender, MyEventArgs e);

     

    public class WebUserControl1 : System.Web.UI.UserControl

    {

               protected System.Web.UI.WebControls.Button Button1;

               public event MyEventHandler MyEvent;

     

               private void Button1_Click(object sender, System.EventArgs e)

               {

                          OnEvent(new MyEventArgs("Time is " + DateTime.Now));

               }

     

               protected virtual void OnEvent(MyEventArgs e)

               {

                          if (MyEvent != null)

                                     MyEvent(this,e);

               }

    }

     

    Now, the rest of the code goes into the web form class, where you have to add the event listener after you’ve loaded the control. The Page_Load() event loads the control and sets up the event handler for MyEvent, called ctrl_MyEvent(). When the event fires, you just extract the text property from the event arguments and set the label text to that value.

     

    It’s a bit harder if you need to load different kinds of controls dynamically, but in that case I would define the events in a superclass that all user controls inherit from:

     

    public class WebForm1 : System.Web.UI.Page

    {

               protected System.Web.UI.WebControls.Panel Panel1;

               protected System.Web.UI.WebControls.Label Label1;

               protected WebUserControl1 ctrl = null;

     

               private void Page_Load(object sender, System.EventArgs e)

               {

                          ctrl = (WebUserControl1)LoadControl("WebUserControl1.ascx");

                          Panel1.Controls.Add(ctrl);

                          ctrl.MyEvent +=new MyEventHandler(ctrl_MyEvent);

               }

     

               private void ctrl_MyEvent(object sender, MyEventArgs e)

               {

                          Label1.Text = e.Text;

               }

    }

     

  • How ugly is this solution?

    I guess this can be called an anti-pattern, but it sort of works if you have a really small solution and no other coders than yourself :)

     Imagine having a web form, which has a label control on it and this web form also loads a user control during run-time. The user control has a single button on it and when pressed, the button should set the text value of the label to the current date and time.

    Now, there are some good ways and some bad ways to send data from a child control to a parent control, and I usually use events for this. Another way of doing this would be to access a property in the parent directly from the child control, but personally I think this is a really ugly way of doing things. How ugly do you think this is?

    In the web form, add a public property to be able to set the label text:

     

    protected System.Web.UI.WebControls.Panel Panel1;

    protected System.Web.UI.WebControls.Label Label1;

     

    private string mylabel = null;

     

    public string Mylabel

    {

               get { return mylabel; }

               set

               {

                   mylabel = value;

                   this.Label1.Text = mylabel;

               }

    }

     

    In the user control, the Page property of the control (which always points at the parent page, not the parent control) into the correct WebForm class, then set the public label property we define above directly.

     

    private void Button1_Click(object sender, System.EventArgs e)

    {

               if(this.Page is WebForm1)

               {

                          WebForm1 form = (WebForm1)this.Page;

                          form.Mylabel = "Time is " + DateTime.Now;

               }

    }

     

    This code seems to work pretty well, but I bet some of you get a nasty feeling in your gut when seeing code like this, and I agree completely. First of all, in an event driven enviroment, a control should never mess with properties in its parent/container. Secondly, the control assumes that the parent is always of a certain type (WebForm1) with a public property called Mylabel. I'm sure there are other good reasons for not coding like this. Comment please :)

     

  • Set focus on a clicked control in ASP.NET (part 2)

    I got a message from some friends that the code sample I had in my previous post didn't work when the control was located within a repeater or a datalist/datagrid, so I sat down and changed the code somewhat. The problem with controls in a repeater is that the container control eats up the event so the OnBubbleEvent() never fires.

    Now, the trick is to do exactly the same thing, but in the OnItemCommand event fired by the repeater/datalist/datagrid instead.

    So, the ASP-Page could look something like this:

    <%@ Page language="c#" Codebehind="WebForm2.aspx.cs" AutoEventWireup="false" Inherits="ControlTest.WebForm2" %>

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

    <HTML>

               <HEAD>

                          <title>WebForm2</title>

                          <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">

                          <meta name="CODE_LANGUAGE" Content="C#">

                          <meta name="vs_defaultClientScript" content="JavaScript">

                          <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">

               </HEAD>

               <body MS_POSITIONING="FlowLayout">

                          <form id="Form1" method="post" runat="server">

                                     <P>

                                                <table>

                                                          <asp:Repeater id="Repeater1" runat="server" OnItemCommand="Repeater1_ItemCommand">

                                                                     <ItemTemplate>

                                                                                <tr>

                                                                                           <td>

                                                                                                      <ASP:Button ID="someID" CommandName="someCommand" CommandArgument='<%# DataBinder.Eval(Container.DataItem, "id") %>'

                                                                                                      Text='<%# DataBinder.Eval(Container.DataItem, "id") %>' runat="server" />

                                                                                           </td>

                                                                                </tr>

                                                                     </ItemTemplate>

                                                          </asp:Repeater>

                                                </table>

                                     </P>

                          </form>

               </body>

    </HTML>

     

    And the code behind for this simple sample:

    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Text;
    using System.Web.UI.WebControls;

    namespace ControlTest

    {

               public class somedata

               {

                          public somedata(string id, string value)

                          {

                                     this.id = id;

                                     this.value = value;

                          }

     

                          private string id;

                          private string value;

     

                          public string Id

                          {

                                     get { return id; }

                                     set { id = value; }

                          }

     

                          public string Value

                          {

                                     get { return value; }

                                     set { this.value = value; }

                          }

               }

               /// <summary>

               /// Summary description for WebForm2.

               /// </summary>

               public class WebForm2 : System.Web.UI.Page

               {

                          protected System.Web.UI.WebControls.Repeater Repeater1;

                          private ArrayList ht = new ArrayList();

     

                          private void Page_Load(object sender, System.EventArgs e)

                          {

                                     FillArrayList();

     

                                     if(!IsPostBack)

                                                BindData();

                          }

     

                          private void BindData()

                          {

                                     Repeater1.DataSource = this.ht;

                                     Repeater1.DataBind();

                          }

     

                          private void FillArrayList()

                          {

                                     for(int i = 0; i<10; i++)

                                                this.ht.Add(new somedata("id"+i,"value"+i));

                          }

     

     

                          protected void Repeater1_ItemCommand(object sender, RepeaterCommandEventArgs e)

                          {

                                     if(e.CommandSource is WebControl)

                                                SetControlFocus(e.CommandSource);

                          }

     

                          private void SetControlFocus(object sender)

                          {

                                     WebControl webControl = (WebControl)sender;

                                     Debug.WriteLine("Clicked " + webControl.ClientID);

                                     StringBuilder sb = GetFocusScriptBlock(webControl);

                                     RegisterClientScriptBlock("FocusScript", sb.ToString());

                          }

     

                          private static StringBuilder GetFocusScriptBlock(WebControl webControl)

                          {

                                     StringBuilder sb = new StringBuilder(1000);

                                     sb.Append("<script language = \"javascript\">");

                                     sb.Append("function ControlFocus() {");

                                     sb.Append("document.getElementById('" + webControl.ClientID + "').focus();");

                                     sb.Append("}");

                                     sb.Append(String.Concat(Environment.NewLine, Environment.NewLine, "window.onload = ControlFocus;"));

                                     sb.Append("</script>");

                                     return sb;

                          }

     

                          #region Web Form Designer generated code

                          override protected void OnInit(EventArgs e)

                          {

                                     //

                                     // CODEGEN: This call is required by the ASP.NET Web Form Designer.

                                     //

                                     InitializeComponent();

                                     base.OnInit(e);

                          }

                         

                          /// <summary>

                          /// Required method for Designer support - do not modify

                          /// the contents of this method with the code editor.

                          /// </summary>

                          private void InitializeComponent()

                          {   

                                     this.Load += new System.EventHandler(this.Page_Load);

     

                          }

                          #endregion

               }

    }

     

     

     

  • [Off Topic] Finally got my black belt

    I passed my black belt graduation in aikido (aikikai) today! Yay! I've been so nervous the last month you cannot imagine... been dreaming about attacks and throws and swords... Anyway, the graduation went really well - I'm sitting here with a HUGE SMILE on my face!

  • Set focus on a clicked control in ASP.NET

    Jan-Erik just blogged about how to set focus on a clicked control in an ASP.NET page. Sometimes you need to do that if the page you have is very, very long or if you have a list with vertical controls and you want to put focus on the clicked control after a postback. One way of doing it is to set focus in each and every control event (button1_clicked, button2_clicked) and so on. But what Jan-Erik is looking for is to put the focus-code in one place only.

    Jan-Erik thought that his solution was a bit hacky and asked for another way of doing it, and I think I have a pretty simple one. The trick is to override the OnBubbleEvent() and check who the sender is. This seems to work pretty well:

    protected override bool OnBubbleEvent(object sender, EventArgs e)

    {

               if(sender is Button ||

                          sender is ImageButton)

               {

                          WebControl webControl = (WebControl)sender;

                          Debug.WriteLine("Clicked " + webControl.ClientID);

                          StringBuilder sb = GetFocusScriptBlock(webControl);

                          RegisterClientScriptBlock("FocusScript", sb.ToString());

               }

               return base.OnBubbleEvent(sender, e);

    }

     

    private static StringBuilder GetFocusScriptBlock(WebControl webControl)

    {

               StringBuilder sb = new StringBuilder(1000);

               sb.Append("<script language = \"javascript\">");

               sb.Append("function ControlFocus() {");

               sb.Append("document.getElementById('" + webControl.ClientID + "').focus();");

               sb.Append("}");

               sb.Append(String.Concat(Environment.NewLine, Environment.NewLine, "window.onload = ControlFocus;"));

               sb.Append("</script>");

               return sb;

    }

     

    Any comments on this code? You think this is useful?

    It may be good to know in what order the events happen in a Page:

    1. Page_Load()
    2. The specific event for the clicked control (Button1_Click() for example)
    3. OnBubbleEvent()

  • [.NET 2.0] Visual Studio Tools for Office

    I've been attending a few VS.NET 2005 seminars now, and I'm quite curious about the stuff you can do with Visual Studio Tools for Office. It is interesting to use Word and Excel as clients because users know how to use these tools and the Actions Pane is the perfect place for simple controls to help the user fill in and submit a form or similar.

    If you're interested in stuff like this, check out the things available at the home page.

  • The bluetooth worm is spreading

    We used to joke about this a couple of years ago, we all knew it would happen, and now it seems to be spreading - the bluetooth worms. Why anyone would walk around and have his or her mobile set on discoverable mode, I don't know. Even less choose to install something without being sure what it is :)

    You can find out more from any of the bigger anti-virus webbys out there, like F-Secure.

  • TDD: Who is testing the tester?

    I posted earlier about the book "Test Driven Development in Microsoft.NET, and I liked that book very much, it's not that. I've been using JUnit and NUnit in smaller and larger projects, but I've never used it in the way you often see it described in books written by the TDD-gurus and the way it is described in this book. I can't help it, but it feels kind of stupid to create a test the first thing you do, just to see the compile fail, write enough code to make the test compile then see the test fail, then add "dummy code" so that the first test pass but the rest fails... you know the drill :) 

    Then there is this thing about testing every class in every layer in a system or application. Writing that many tests manually would probably make me go nuts, and the quality of the test methods would most certainly drop after a while. The value of unit testing only gets as good as your test code, right? I've seen some tools that try to create test code for you automatically, but I've not been that impressed.

    The way I've been using JUnit is that I've created tests against the facade layer or (web) service layer and then added the tests as a target in my ANT build scripts. That has worked out pretty well. If someone in the team changed something in the database or some other layer, the test fail and you (most of the time) get a pretty good tracktrace which shows where the problem lies. Even so, writing really good tests for that layer only (with scripts for setting up database tables and populate with test data), can take quite some time. Of course, it depends on what kind of system you're building and if the service/facade interface takes complex parameters which makes it hard to test properly, but imagine writing tests for the service layer, business layer, utilties and data access layer too. You would probably spend more time writing test code than anything else :)

    Some would probably say TDD, XP and Refactoring is something invented by a few smart guys so they would get longer contracts and get to spend more time coding and less time documenting ;) I just wish some of my customers believed in those methods when I was a consultant :p

    Personally I'm going to continue to use parts of TDD, XP and refactoring that I like because I think these methods (not that refactoring is a dev method but anyway) have got some really good bits in them. Besides, I love to read the books about them.

  • Some old articles from CodeProject

    I moved or copied a couple of old ASP.NET articles I wrote from CodeProject to my blog, mainly to keep track of them. They're available under my Articles section here. Old ones, but pretty good primers for new ASP.NET developers.
  • [ASP.NET] An XML based (we)blog with RSS feed

    This is an old article I wrote for CodeProject some time ago. I'm putting it here in my blog to keep track of it. The code is avaliable for download from CodeProject -> http://www.codeproject.com/soap/weblog.asp

    Sample Image - weblog.gif

    Introduction

    Since writing weblogs, or blogging as it is also called, has become pretty popular the last year, I thought of constructing my own blog tool. Blog is a shorter word for a web log, an online (most often public) journal where the author writes down his or her thougths, sometimes around a specific topic. This article describes how to write a pretty simple weblog application and a windows program for writing entries sitting in the system tray.

    Some of the techniques used in this application are XML and XML Schema, Web Services, DataSets, Cache and the Calendar Web Control. Oh, and the XML Control too for transforming the XML weblog into RSS.

    The Web Application

    The web application consists of three parts actually; the web page showing the log and a calendar, a password protected web service for accepting entries and finally a second web page, which transforms the internal XML file into a RSS 2.0 feed via XSL transformation.

    The Windows Application

    The windows application (from now on called the client) is fairly simple in functionality and consists of a single dialog where the user can type in a message and send it over to the web site via a Web Service call.

    The client sits in the system tray all the time, and when the user wants to write a message in his or her weblog, a click with the mouse brings up the dialog, ready for use.

    Using the code

    Let’s go over some of the more interesting parts of the code, starting with the XML format for the weblog data.

    The Weblog XML and Schema

    <?xml version="1.0" standalone="yes"?> <weblog> <logentry>     <id>0a8d4ec3-eec1-4b07-b26f-98bb5561f43c</id>     <logtitle>A title</logtitle> <logtime>2003-01-10T13:28:14.2031250+01:00</logtime> <logtimeGMT>Fri, 10 Jan 2003 13:28:14 GMT</logtimeGMT> <logtext>This is an entry in the weblog.</logtext> </logentry> </weblog> 

    And the XML Schema for the weblog:

    <?xml version="1.0" encoding="utf-8" ?> <xs:schema id="weblog" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="weblog" msdata:IsDataSet="true" msdata:Locale="sv-SE"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="logentry"> <xs:complexType> <xs:sequence>               <xs:element name="id" type="xs:string" minOccurs="0" />               <xs:element name="logtitle" type="xs:string" minOccurs="0" /> <xs:element name="logtime" type="xs:date" minOccurs="0" /> <xs:element name="logtimeGMT" type="xs:string" minOccurs="0" /> <xs:element name="logtext" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> 

    As the XML and the schema shows, the weblog consists of a number of log entries containing data for id, logtitle, logtext, logtime and logtimeGMT. The logtimeGMT is for the RSS feed, since it needs to be in RFC 822 format. I couldn’t find any simple way of transforming logtime into GMT with XSLT so I took the lazy path and stored both of them in the XML file. The id tag is a unique id that is given to each new blog entry.

    The weblog web page

    The weblog is presented on the web page by reading the XML file into a DataSet and binding that to a Repeater. I like the Repeater for simple loops like this, why use the more complex DataGrid or DataList when it’s not needed?

    Remember to turn off the ViewState of the Repeater, it’s not needed and will speed up the loading of the page.

    Every call to the page starts by getting the cached DataSet from the XML file. This is done in the Page_Load event.

    Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load 'allways start with getting our cached dataset dsWebLog = XmlHelper.GetDS() If Not IsPostBack Then SetDates() BindList() End If End Sub 

    The XmlHelper class has a few static methods for reading and writing the XML DataSet.

    The location of the XML file is stored in the ASP.NET configuration file, web.config.

    Public Shared Function GetDS() As DataSet 'get DS from cache Dim ds As DataSet = CType(HttpContext.Current.Cache("dsWebLog"), DataSet) If ds Is Nothing Then ds = New DataSet("weblog") ds.ReadXmlSchema(ConfigurationSettings.AppSettings("xmlSchema")) Try ds.ReadXml(ConfigurationSettings.AppSettings("xmlFile")) Catch ex As Exception 'missing an xml file is perfectly fine, this might be the 'first time the app is used End Try 'store in cache with dependency to the xml file HttpContext.Current.Cache.Insert("dsWebLog", ds, _ New Caching.CacheDependency(ConfigurationSettings.AppSettings("xmlFile"))) End If Return ds End Function

    The cache has a dependency to the XML file, so the .NET Cache will automatically flush the cached DataSet if a new message is added to the XML file.

    To be able to select a certain date, I also added the ASP.NET Calendar control to the page. When the page is loaded I loop through all the dates in the weblog XML DataSet and select all the dates in the calendar that has an entry in the weblog. When someone clicks a certain date in the calendar, the DataSet is filtered before it’s bound to the Repeater.

    Private Sub Calendar1_SelectionChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Calendar1.SelectionChanged dateFilter = Calendar1.SelectedDate.AddDays(1).ToString SetDates() BindList() End Sub 

    Before the DataSet is bound to the Repeater, the log entries are sorted and only the top 50 entries are shown. This (as so much else in the sample app) can be set in the web.config file.

     Private Sub BindList() 'get a dataview from a copy of the cached dataset Dim dvWeblog As DataView = dsWebLog.Copy.Tables(0).DefaultView 'filter on date? If dateFilter <> "" Then dvWeblog.RowFilter = "logtime < '" & dateFilter & "'" End If 'sort it by date and time dvWeblog.Sort = "logtime desc" 'copy maximum nr of rows to show Dim dtWeblog As DataTable = XmlHelper.GetTopRows(dvWeblog, ConfigurationSettings.AppSettings("maxrows")) 'bind the sorted and stripped log to the repeater weblogList.DataSource = dtWeblog weblogList.DataBind() End Sub 

    The DataSet is filtered by setting the RowFilter property of the DataView. The .NET Cache has a pointer to our cached DataSet, and the cached DataSet has a pointer to the DataView, so if we don’t take a copy of the DataSet, the RowFilter property will be the same for other users of the cached DataSet. Something I discovered the hard way...

     'get a dataview from a copy of the cached dataset Dim dvWeblog As DataView = dsWebLog.Copy.Tables(0).DefaultView 

    The method called GetTopRows is also located in the XmlHelper class, and it copies a specific number of rows from the log to be displayed in the page.

     Public Shared Function GetTopRows(ByVal dv As DataView, _ ByVal Rows As Integer) As DataTable Dim dtReturn As DataTable Dim cRow As Integer Dim maxRows As Integer maxRows = dv.Count dtReturn = dv.Table.Clone() For cRow = 0 To (Rows - 1) If cRow = maxRows Then Exit For dtReturn.ImportRow(dv(cRow).Row) Next Return dtReturn End Function

    The weblog client

    The client is made up from a single dialog, which starts up minimized to the system tray, i.e. as an icon in the status area of the desktop. The dialog has a TextBox for the title, RichTextBoxfor the body text and a couple of buttons for sending the log entry to the Web Service and for hiding or closing the program.

    So, to post some text to the weblog Web Service, the user types some text in the title textbox and in the body textbox, then presses the Send-button. I thought the Web Service should have some way of protection, so therefore the call is authenticated with a password sent in the SOAP Header. The password is stored in a config file, and I use the built in .NET ConfigurationSettings file (WeblogClient.exe.config) for this.

    Update: To be able to type in formatted text with different colors and fonts, and also to be able to type in HTML or XML tags, the text in the RichTextBox is first converted to HTML (RichTextBoxUtil.ConvertToHTML()). You can have a look at the utility class called RichTextBoxUtil.vb to see how it is done. Note that the utility doesn't handle links yet.

     Private Sub Send_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles SendButton.Click 'Send message to the Weblog via SOAP/Webservice Dim wsWeblog As New Weblog.Weblog() 'get password from the config file (WeblogClient.exe.config) Dim password As String = ConfigurationSettings.AppSettings("password") If password Is Nothing Then ConfigError("password") End If 'this is our SOAP Header class Dim authentication As New Weblog.AuthHeader() authentication.password = password wsWeblog.AuthHeaderValue = authentication 'get the Web Service URL from the config file Dim URL As String = ConfigurationSettings.AppSettings("wsPostURL") If URL Is Nothing Then ConfigError("URL") End If 'set the correct URL for the Web Service wsWeblog.Url = URL         'send HTML converted text to the weblog webservice         wsWeblog.PostMessage(TextBox1.Text, _ RichTextBoxUtil.ConvertToHTML(RichTextBox1)) WindowState = FormWindowState.Minimized HideMe() 'clear out the textbox Me.RichTextBox1.Clear()         Me.TextBox1.Clear() End Sub 

    The URL for the Web Service is also stored in the config file (WebLogClient.exe.config), which must be located in the same directory as the weblog client.

    The Web Service

    The Web Service method for receiving and storing the posted message is quite small. It's one simple method, and it first checks the SOAP Header and compares the password, then it stores the posted message to the weblog.

    <WebMethod(Description:="Post a message to the weblog. An authentication SOAP header is mandatory."), SoapHeader("authentication")> _ Public Function PostMessage(ByVal title As String, ByVal message As String) As Integer If authentication.password = ConfigurationSettings.AppSettings("password")_ Then 'password is ok, stor message in the XML file XmlHelper.AddMessage(title, message) Else Throw New Exception("Invalid password") End If End Function

    The password is (as so much else) stored in the web.config file.

    The AddMessage() method just adds a new DataRow in the weblog DataSet and saves it back to XML. The method also creates a unique id for this posting. The new DataRow is added at the top of the DataSet. The XML file is stored at the location specified by the web.config file (default is at c:\weblog.xml).

     Public Shared Sub AddMessage(ByVal title As String, _ ByVal message As String) Dim dsWebLog As DataSet = XmlHelper.GetDS Dim drNew As DataRow drNew = dsWebLog.Tables(0).NewRow         drNew.Item("id") = Guid.NewGuid.ToString         drNew.Item("logtitle") = title drNew.Item("logtime") = Now drNew.Item("logtimeGMT") = Format(Now, "r") 'RFC 822 format  drNew.Item("logtext") = message dsWebLog.Tables(0).Rows.InsertAt(drNew, 0) 'insert it at beginning 'store xml again dsWebLog.WriteXml(ConfigurationSettings.AppSettings("xmlFile")) End Sub 

    The weblog RSS 2.0 feed

    More and more of the weblogs on the Internet provide an RSS feed of it’s content. I've seen different explanations about what RSS stands for. This was taken from the RSS specification:

    “RSS is a Web content syndication format. Its name is an acronym for Really Simple Syndication. RSS is dialect of XML.”

    But some people say "RDF Site Summary", and RDF stands for Resource Description Framework, which is a foundation for processing metadata. It really doesn't matter, it's a great way to publish content in a simple XML way.

    RSS has been around since 1999 and I’ve tried to create a very simple RSS feed by reading the RSS 2.0 Specification located at http://backend.userland.com/rss

    Just for the “fun” of it, I tried to use XSL Transformation to turn the weblog XML file into the correct RSS format. So, I created a new WebForm ASPX page, and removed everything except the Page header from it, and added a ContentType attribute to it for text/xml.

    <%@ Page contenttype="text/xml" Language="vb" AutoEventWireup="false" Codebehind="rss20.aspx.vb" Inherits="Weblog.rss20"%> 

    Then I drag/dropped an ASP.NET XML Control to the page and added some code in code-behind to point out the XML file and the XSL file.

    Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim doc As XmlDocument = New XmlDocument() doc.Load(ConfigurationSettings.AppSettings("xmlFile")) Dim trans As XslTransform = New XslTransform() trans.Load(ConfigurationSettings.AppSettings("RSSxslFile")) Xml1.Document = doc Xml1.Transform = trans End Sub 

    This is the XSL file used to transform the XML file:

    <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> <xsl:template match="/"> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>My weblog</title> <link>http://localhost/weblog/</link> <description>Just another weblog...</description> <managingEditor>someone@somewhere.com (My Name)</managingEditor> <language>en-us</language> <xsl:for-each select='weblog/logentry'> <item> <link>http://localhost/weblog/Item.aspx?id=<xsl:value-of select='id'/></link>             <guid isPermaLink="false"><xsl:value-of select='id'/></guid>             <title><xsl:value-of select='logtitle'/></title>             <description><xsl:value-of select='logtext'/></description>             <pubDate><xsl:value-of select='logtimeGMT'/></pubDate> </item> </xsl:for-each> </channel> </rss> </xsl:template> </xsl:stylesheet> 

    The XSL file loops through each log-entry and writes them out within description and pubDate tags. Publication date needs to be in RFC 822 format (GMT-format) according to the RSS spec, that’s why I use that field in the XML file.

    Update: The XSL file has been updated now so is also writes out a title, guid and a link to the blog entry.

    One bad thing with this page is that it will write out every record in the weblog, something I took care of in the web page. It shouldn’t be too hard to sort and filter out the top 50 records or so in the way it’s done in the web page, but I leave that for later updates.

    Points of interest

    I could have created the RSS feed in a number of different ways, but I’ve always wanted to try out the ASP.NET XML Control, so that’s why I went for that. I found out that you can do a number of things with XSL Transformation, but wow, it’s pretty complicated.

    As I wrote earlier in the article, it’s easy to forget that the .NET Cache keeps pointers to reference type objects and if you change data in objects you get from the Cache, you are actually changing the object kept in the .NET Cache. Remember that when using the Cache object, you might mess things up for other visitors to the webby. As long as you store value types in the Cache you don’t have to worry.

    Updates

    Update 1 - I added a title to the blog entry, mostly because it looks best i different RSS readers if there's a title for each blog entry. For the sake of RSS, I also added a guid for each entry. I also added code to transform some of the formatted text in the RichTextBox into HTML. It shouldn't be any problems to cut and paste colored and indented source code or HTML/XML into the RichTextBox. It looks pretty good on the HTML page. Note that it doesn't handle hyperlinks yet.

  • [ASP.NET] Extending the Label Control

    This is an old article I wrote for CodeProject once. I'm putting it on my blog just to keep track of it. The code is available for download at CodeProject -> http://www.codeproject.com/aspnet/textcontrolext.asp

    Introduction

    This small article shows how to extend the ASP.NET Label Control, so it reads resource strings from an XML file, which is stored in memory as a Cached DataSet with cache-dependency to the XML-file. Each web page has its own XML-file where texts are stored.

    A control like this might come in handy if you need to translate your web application into other languages, or if the owner of the web site wants to change the “static” texts in the web pages by just editing the XML-files.

    The program could easily be extended so the resource texts are read from another source, like a database, Web Service or a resource file, but I wanted to keep this article fairly small and easy, just to show the general idea.

    I’ve used this technique in production, but since we use a web farm in production, having the resource strings in files isn’t that convenient, so instead I keep all strings in a database. Its also a good idea to give the web application owner, a nice interface for changing the texts. I might write another article to show you how to do this.

    Some of the .NET features used in the sample project:

    • Custom controls
    • DataSet
    • XML
    • Cache

    Code overview

    The Control is called TextControl because it handles texts from a resource of strings. Perhaps a name like Text or StringResource is better, but TextControl will have to do. Feel free to change it :)

    The central part of the code is made up from a single custom control, which inherits from, and extends the ASP.NET Label control. The control also uses a helper class called XMLResource, which will be explained later.

    [DefaultProperty("Key"), ToolboxData("<{0}:TextControl runat=server></{0}:TextControl>")] public class TextControl : System.Web.UI.WebControls.Label 

    The reason I wanted to inherit from the Label control instead of the base Control class is that I want to use the rendering functions that are already implemented in the Label control for different fonts, colors and stuff. I’m lazy ;)

    The TextControl needs to know what text string it should get from its resource of texts, so therefore I added a new property to it called Key. I might as well have re-used the inherited Text property that is already in there, but I had better use for that Text property (shown later).

    private string key; [Bindable(true), Category("Appearance"), DefaultValue(""), Description("ID/Key of the resource text to get")] public string Key { get { return key; } set { key = value; } } 

    There really isn’t much to say about the code snippet above. Its important to have the right attributes or the property might not show up in the property dialog in VisualStudio.NET.

    The final (and interesting part) of the control is the code that renders the output in both run-time and design-time. To do this you must override the Render() method of the Label class. When the ASPX page renders content, the page calls this Render() method for each control on the page.

    protected override void Render(HtmlTextWriter output) { try { //get the text string from the resource //the Text property is used as "default" value Text = new XMLResource().GetValueFromKey(Key, Text); //call base class for rendering base.Render(output); } catch //catch design-time exceptions  { //render the Text property with brackets String tmpText = Text; Text = "[" + Text + "]"; //call base class for rendering base.Render(output); //put back the default text without brackets Text = tmpText; } } 

    As you can see in the code above, I’m using a try/catch block to handle errors, that might happen when we’re getting the text from the resource. I’m getting the texts from an XML-file with the same name as the ASPX file (as described in the introduction), so if the TextControl sits in an ASPX page called Default.aspx, the resource XML file for that page is called Default.xml and located in the same directory as the ASPX page.

    To get the name of the ASPX file, I use the CurrentExecutionFilePath property of the Request object. The Request object is accessed via the current HttpContext, which isn’t available at design-time, so therefore I catch the exception thrown and show the default text within square brackets. You might as well prevent the exception in the XMLResource class (shown below) but this works fine for me.

    If the requested string (based on the Key) isn’t found or if an exception occurs, I show the default text, which I get from the re-used Text property of the parent Label control.

    So, how do we get the text from the resource file? All is done in the XMLResource class, which I’ll try to explain below. The XMLResource class has two methods; one public to get the requested text string and one private, which get the XML file from a cached DataSet. First the public method:

    public String GetValueFromKey(String key, String defaultValue) { DataSet ds = GetResourceDS(); //filter out everything except our resource string in a dataview DataView dv = ds.Tables[0].DefaultView; dv.RowFilter = "key = '" + key + "'"; if(dv.Count > 0) //key found, show the value return dv[0]["value"].ToString(); else //key not found return default value return defaultValue; } 

    It’s pretty simple. Get a DataSet, which contains all the resource strings for this ASPX page. Then filter out our text string based on the key. If the key isn’t found, the default value is returned.

    private DataSet GetResourceDS() { //Try to get the DataSet from the Cache first //Note that this one bombs at design time, (we have no HttpContext) //but this exception is caught in the custom control DataSet ds = (DataSet)HttpContext.Current.Cache[HttpContext.Current. Request.CurrentExecutionFilePath]; if(ds == null) { //no DataSet in the Cache, get a new one and store it in the cache //get the text-id from the resource file for our web-page ds = new DataSet("resourceStrings"); //build the name of the xml file from the name of the web // page we're sitting in String fileName = HttpContext.Current. Request.CurrentExecutionFilePath; fileName = fileName.Substring(0, fileName.LastIndexOf(".")) + ".xml"; try { //read ds from the xml file ds.ReadXml(HttpContext.Current.Server.MapPath(fileName)); } catch (System.IO.FileNotFoundException) { //xml file not found, we will return the empty DataSet } //put in Cache with dependency to the xml file HttpContext.Current.Cache.Insert(HttpContext.Current. Request.CurrentExecutionFilePath, ds, new CacheDependency(HttpContext.Current. Server.MapPath(fileName))); } return ds; } 

    The GetResourceDS method is longer, but not too complicated. First it tries to get the DataSet from the built in .NET Cache object, which is very cool. As a Cache-key I use the name of the ASPX page. If the DataSet wasn’t in the Cache, I create a new DataSet and read in the content from the XML file, which should have the same name as the ASPX page where the TextControl is used (as explained earlier).

    The cached DataSet should expire whenever someone updates the XML file, right? So, therefore I add a cache-dependency, which points at the XML file. So now, when the file is updated, the Cache object detects this and removes the cached DataSet from its memory.

    The ASP.NET Cache might be even more useful if you decide to put all your texts into one big, single XML file. There is no reason to read that XML file every time a TextControl is rendered, right?

    Using the code

    To use the TextControl from VisualStudio.NET, you need to add the assembly containing the control to the toolbox. Load up VisualStudio.NET and create a new ASP.NET application. Then add a WebForm and bring it up in design view. Now right click in the Toolbox containing all the other built-in server controls and select “Customize Toolbox...” from the popup menu. Now select the tab named “.NET Framework Components” and use the “Browse...” button to find the DLL containing the TextControl. The sample project has the TextControl in the MyControls.dll assembly.

    VS.NET will automatically find and activate the TextControl inside the MyControls DLL. You can put the control on any of the different tabs in the Toolbox, but I prefer to have it together with the other server controls.

    The TextControl is now ready for use on a web page. Create a WebForm called WebForm1.aspx and drag/drop the TextControl from the Toolbox on it in design view. Select the TextControl on the ASPX page and look at the property dialog. Change the Key property to “key1” (for example) and optionally put some text in the Text property. It should look similar to this in the HTML view:

    <form id="Form1" method="post" runat="server"> <cc1:TextControl id="TextControl1" runat="server" Key="key1"> </cc1:TextControl> </form> 

    Note that VS.NET automatically added a reference to MyControls in the project view and also added code at the top of the file for registering the custom control:

    <%@ Register TagPrefix="cc1" Namespace="MyControls" Assembly="MyControls" %>

    The only thing to do now is to create the XML file containing the resource strings. Just add a new XML file called WebForm1.xml in the same directory as your newly created ASPX page. The XML file is pretty simple and looks like this:

    <?xml version="1.0" encoding="utf-8" ?> <resourceStrings> <string> <key>key1</key> <value>sampleValue 1</value> </string> <string> <key>key2</key> <value>sampleValue 2</value> </string> </resourceStrings> 

    Each string node in the XML file makes up a key/value pair in the resource file.

    The web page is ready for testing and should show a plain web page with the text “sampleValue 1” on it. If you change the text “sampleValue 1” in the XML file into something else and reload the page, it should show the new value at once.

    The XMLResource class can also be used from code behind to get and set texts at run-time. Just call the public method GetValueFromKey() from the code behind:

    //set the text of the label in run-time Label1.Text = new MyControls.XMLResource().GetValueFromKey("key2", Label1.Text); 

    Conclusion

    As I wrote earlier, there are lots of things you could (and should) do with this code to use it in production, but I hope this little article can get some of you into making your own controls, which is very fun and really, really useful. I recommend doing similar controls for Buttons, Hyperlinks and so on, the owner of the web site might want to change the texts of all the submit buttons to SAVE or DO IT...

    Some things that I’ve added to this control in a production site:

    • XML Schema for validating the XML resource file (never trust a customer to write perfect XML ;)
    • Password protected web form for updating the XML texts
  • Must-have tools for .NET developers

    This is a pretty old one, but still, the tools on this list are just great. I still have to look at CodeSmith and Regulator.

    The tools on the list are these:

    • NUnit to write unit tests
    • NDoc to create code documentation
    • NAnt to build your solutions
    • CodeSmith to generate code
    • FxCop to police your code
    • Snippet Compiler to compile small bits of code
    • Two different switcher tools, the ASP.NET Version Switcher and the Visual Studio .NET Project Converter
    • Regulator to build regular expressions
    • .NET Reflector to examine assemblies

    I would like to add ReSharper (from the JetBrain people) to the list, a quite good refactoring tool.

  • [Java] Setting WSS4J properties for Username Token at run-time

    I managed to dynamically set the username and the other properties that normally goes into the client WSDD file. Again, many thanks to the nice people in the WSS4J mailing list.

    First you have to modify the client WSDD file from the tutorial and comment out the properties. If you leave them in there, I believe those values will be used. So, the client_deploy.wsdd looks something like this:

    <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
     <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
      <globalConfiguration >
       <requestFlow >
        <handler type="java:org.apache.ws.axis.security.WSDoAllSender" >
        <!-- parameters removed -->
        </handler>
       </requestFlow >
      </globalConfiguration >
    </deployment>

    Then you have to modify the StockServiceClient.java code somewhat to be able to set the handler properties

    package samples.stock.client;

    import java.rmi.RemoteException;
    import javax.xml.rpc.ServiceException;
    import org.apache.axis.EngineConfiguration;
    import org.apache.axis.configuration.FileProvider;
    import org.apache.axis.client.Stub;
    import java.rmi.Remote;
    import org.apache.ws.security.handler.WSHandlerConstants;
    import org.apache.ws.security.WSConstants;
    import org.apache.ws.security.message.token.UsernameToken;

    public class StockServiceClient
    {
        public StockServiceClient()
        {
        }

        public static void main(String[] args) throws ServiceException,
                RemoteException
        {
            if (args.length == 0)
            {
                System.out.println("Usage:\njava StockServiceClient [symbol]");
                return;
            }
            
            //modify the path to the client_deploy.wsdd
            EngineConfiguration config = new FileProvider("client_deploy.wsdd");
            StockQuoteServiceService locator = new StockQuoteServiceServiceLocator(config);
           
            Remote remote = locator.getPort(StockQuoteService.class);
            Stub axisPort = (Stub)remote;
            axisPort._setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
            axisPort._setProperty(UsernameToken.PASSWORD_TYPE, WSConstants.PASSWORD_DIGEST);
            axisPort._setProperty(WSHandlerConstants.USER, "wss4j");
            axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, "samples.stock.client.PWCallback");

            //possible to pass a callback instance by ref instead of a class as above
            //PWCallback pwCallback = new PWCallback();
            //axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_REF, pwCallback);

            //Need to cast 
            StockQuoteService service = (StockWss01SoapBindingStub)axisPort;

            System.out.println("Calling service...");
            float quote = service.getQuote(args[0]);
            System.out.println("stock quote service returned " + args[0] + ": "
                    + quote);
        }
    }

    Note that there are several ways to read the WSDD file dynamically, you can also define "axis.ClientConfigFile" as a system property.

    In this sample you're still using a client WSDD file, specifying the handler (WSDoAllSender) Axis should be calling when the SOAP request is sent. It's possible to code so that you don't need the WSDD file at all, but I'll try that at a later state.

    I'm going to be quite busy travelling the next week, but I'll try to make a simple Java Axis/Wss4j client call a ASP.NET service with a UsernameToken added. It'll be fun.

  • [Java] Simple WSS4J with Axis Tutorial

    This is a simple tutorial for getting started with WSS4J. It’s based on a tutorial posted on the WSS4J mailing list by Rami Jaamour, but I’ve added a few clarifications and variants of the code samples. Rami should get all credits for this tutorial; I’m just a newbie trying to learn how to use this tool!

    Updates

    2006-03-29 - If you get an exception like this one below, it is recommended to look at which Axis version your are using and consider Axis version 1.2:

    Exception in thread "main" java.lang.IllegalAccessError: tried to access method org.apache.axis.SOAPPart.setCurrentMessage(Ljava/lang/Object;I)V from class org.apache.ws.axis.security.WSDoAllSender
    at org.apache.ws.axis.security.WSDoAllSender.invoke(WSDoAllSender.java:365)
    at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:71)

    2005-01-29 - This "article" has been updated a couple of times now. Both with information regarding the xalan.jar problems, and how to set the UserName Token dynamically.

    Credits

    As I wrote above, all cred should go to Rami Jaamour because most of the stuff below is written by him. My thanks to the nice guys in the WSS4J mailing list - Ashok Shah, Werner Dittmann, Yves Langisch and others.

    The Future

    I've added a few things myself to this tutorial, and I'll keep adding things as I learn more. I'll also connect this tutorial with a Username Token service written in ASP.NET as soon as possible. After that we'll see what happens. I'd like to encrypt and sign the stuff too in the future...

    Introduction

    WSS4J can be used for securing web services deployed in virtually any application server, but it includes special support for Axis. WSS4J ships with handlers that can be used in Axis-based web services for an easy integration. These handlers can be added to the service deployment descriptor (wsdd file) to add a WS-Security layer to the web service. This is a step by step tutorial for deploying a simple service with Username Token.

    Prereqs

    To run this tutorial, you must install a JDK (of course). I suggest JDK 1.4.2_04 or 1.5.0. Then you need an application server. I’ve personally used version jakarta-tomcat-4.1.31. Then you need to download and install Axis (version 1.2) and WSS4J. Getting hold of WSS4J and the other jars you may need can be quite tricky. One way is to download Maven and checkout and build WSS4J through it. That’s what I did (not without problems though).

    If you have problems getting the needed jar files let me know and I'll try to add them to this space for download. I've compiled the wss4j.jar package and made it available for download here.

    You don’t really need a Java code editor, but it helps. Personally I use Eclipse and Lomboz (a J2EE plug-in for Eclipse).

    Installing WSS4J

    1. Download the WSS4J binaries or build it from sources
    2. Copy the contents (the jar files) of the WSS4J lib directory to your Axis WEB-INF/lib directory. Many jar files will already exist. Most of them will already exist there but you can just overwrite them all.
    3. You may need to restart Tomcat unless you have automatic deployment/class loading turned on. Check the Axis Happiness Page (typically at http://localhost:8080/axis), make sure that the XML Security (xmlsec.jar) is listed under the "Optional Components" section.

    Creating the service

    1. This tutorial will secure the StockQuoteService which ships with the sample code with Axis. If you deploy the sample web apps that ships with Axis you don’t need to do anything more. Look at the Axis docs on how to install it properly. Unless you have one already, create a deployment descriptor (deploy.wsdd) file with the following contents:


    <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
     <service name="stock-wss-01" provider="java:RPC" style="document" use="literal">
      <parameter name="className" value="samples.stock.StockQuoteService"/>
      <parameter name="allowedMethods" value="getQuote"/>
      <parameter name="scope" value="application"/>
     </service>
    </deployment>

    It doesn’t matter where you put this file.

    1. deploy the service (using AxisAdmin):

    java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService deploy.wsdd

    The AdminClient class depends on a load of jar-files, so to deploy this I created a bat-file that looked like this:

    setlocal

    set CLASSPATH=%CLASSPATH%;C:\axis-1_2RC2\lib\axis.jar;C:\axis-1_2RC2\lib\jaxrpc.jar;C:\axis-1_2RC2\lib\commons-logging.jar;C:\axis-1_2RC2\lib\commons-discovery.jar;C:\axis-1_2RC2\lib\saaj.jar;

    java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService test-deploy.wsdd

    endlocal

    You have to change the bat-file to reflect where you’ve put your axis jar files naturally.

    Creating the Client

    1. Use WSDL2Java to generate the client service bindings (a number of soap client classes):

      java org.apache.axis.wsdl.WSDL2Java -o . -Nhttp://fox:8080/axis/services/stock-wss-01 samples.stock.client http://fox:8080/axis/services/stock-wss-01?wsdl

      Again, the wsdl2java needs a number of jar files to work properly, so I created a new bat-file to help out with that. The bat-file looks like this:

      setlocal

      set CLASSPATH=%CLASSPATH%;C:\axis-1_2RC2\lib\axis.jar;C:\axis-1_2RC2\lib\jaxrpc.jar;C:\axis-1_2RC2\lib\commons-logging.jar;C:\axis-1_2RC2\lib\commons-discovery.jar;C:\axis-1_2RC2\lib\saaj.jar;C:\axis-1_2RC2\lib\wsdl4j.jar;

      java org.apache.axis.wsdl.WSDL2Java -o . -Nhttp://localhost:8080/axis/services/stock-wss-01 samples.stock.client http://localhost:8080/axis/services/stock-wss-01?wsdl

      endlocal

      A bunch of java classes will be created under samples/stock/client, including the StockQuoteServiceServiceLocator.
    2. Write a simple java console application that uses the generated service locator. For example:

      package samples.stock.client;

      import java.rmi.RemoteException;
      import javax.xml.rpc.ServiceException;

      public class StockServiceClient {
          public StockServiceClient() {
          }
          public static void main(String[] args) throws ServiceException, RemoteException {
              if (args.length == 0) {
                  System.out.println("Usage:\njava StockServiceClient [symbol]");
                  return;
              }
              StockQuoteServiceService locator = new StockQuoteServiceServiceLocator();
              StockQuoteService service = locator.getStockWss01();
              float quote = service.getQuote(args[0]);
              System.out.println("stock quote service returned " + args[0] + ": " + quote);
          }
      }
    3. run the client:

      java samples.stock.client.StockServiceClient XXX

      If all went well, you should get the result:

      stock quote service returned IBM: 55.25

    When using "XXX" as parameter, the service won't try to go out on the Internet to get the real quotes, but just returns a float with the value of 55.25.

    What you’ve created so far is a very simple web service with a simple client that calls it. WSS4J has not been used yet, so this web service call is unprotected. Now it’s time to add a Username Token to the soap call.

    Configuring the Service for Username Token

    1. Modify the deployment descriptor you created above to look like this:

      <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
       <service name="stock-wss-01" provider="java:RPC" style="document" use="literal">
        <requestFlow>
         <handler type="java:org.apache.ws.axis.security.WSDoAllReceiver">
          <parameter name="passwordCallbackClass" value="PWCallback"/>
          <parameter name="action" value="UsernameToken"/>
         </handler>
        </requestFlow>

        <parameter name="className" value="samples.stock.StockQuoteService"/>
        <parameter name="allowedMethods" value="getQuote"/>
        <parameter name="scope" value="application"/>
       </service>
      </deployment>

      WSDoAllReceiver is an Axis handler located in wss4j.jar package. This is the standard way to deploy an Axis handler. For more details please refer to the Axis handler for WSS4J documentation.
    2. Create a class named PWCallback.java and compile it and put the resulting PWCallback.class file into your Axis WEB-INF/classes directory. In this example I used the default package for simplicity, but you might need to use the fully qualified class name (be consistent with the deployment descriptor).

      The following code snippet shows a simple password callback class:

      import java.io.IOException;
      import javax.security.auth.callback.Callback;
      import javax.security.auth.callback.CallbackHandler;
      import javax.security.auth.callback.UnsupportedCallbackException;
      import org.apache.ws.security.WSPasswordCallback;

      public class PWCallback implements CallbackHandler {
          public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
              for (int i = 0; i < callbacks.length; i++) {
                  if (callbacks[i] instanceof WSPasswordCallback) {
                      WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
                      // set the password given a username
                      if ("wss4j".equals(pc.getIdentifer())) {
                          pc.setPassword("security");
                      }
                  } else {
                      throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
                  }
              }
          }
      }


    3. Redeploy the service using the bat file you created earlier. Your service should now be expecting a WSS Username Token in the incoming soap request, and clients should send the username "wss4j" and password "security" to get through.

    Configuring the Client for Username Token

    1. run the client we created again:

      java samples.stock.client.StockServiceClient IBM

      You should now get an error:

      Exception in thread "main" AxisFault
       faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.generalException
       faultSubcode:
       faultString: WSDoAllReceiver: Request does not contain required Security header

      This is because your client is not configured to send a Username Token yet, so the service is rejecting the request. To fix this, you need to create a callback class in the client, which adds the Username Token to the outgoing soap request.
    2. Create a deployment descriptor file (client_deploy.wsdd) for the client:

      <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
       <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
        <globalConfiguration >
         <requestFlow >
          <handler type="java:org.apache.ws.axis.security.WSDoAllSender" >
           <parameter name="action" value="UsernameToken"/>
           <parameter name="user" value="wss4j"/>
           <parameter name="passwordCallbackClass" value="samples.stock.client.PWCallback"/>
           <parameter name="passwordType" value="PasswordDigest"/>
          </handler>
         </requestFlow >
        </globalConfiguration >
      </deployment>
    3. Create the samples.stock.client.PWCallback class:

      package samples.stock.client;

      import java.io.IOException;
      import javax.security.auth.callback.Callback;
      import javax.security.auth.callback.CallbackHandler;
      import javax.security.auth.callback.UnsupportedCallbackException;
      import org.apache.ws.security.WSPasswordCallback;

      /**
       * PWCallback for the Client
       */
      public class PWCallback implements CallbackHandler {

          /**
           * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
           */
          public void handle(Callback[] callbacks) throws IOException,
                          UnsupportedCallbackException {
              for (int i = 0; i < callbacks.length; i++) {
                  if (callbacks[i] instanceof WSPasswordCallback) {
                      WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
                      // set the password given a username
                      if ("wss4j".equals(pc.getIdentifer())) {
                          pc.setPassword("security");
                      }
                  } else {
                      throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
                  }
              }
          }
      }
    4. Define the system property axis.ClientConfigFile for your client:

      java -Daxis.ClientConfigFile=client_deploy.wsdd -classpath $AXISCLASSPATH samples.stock.client.StockServiceClient

      Make sure that your CLASSPATH includes the jar files under WEB-INF/lib.

      Another way to do this is to specify the wsdd file in your StockServiceClient to the service locator programmatically:

      ...
      import org.apache.axis.EngineConfiguration;
      import org.apache.axis.configuration.FileProvider;
      ...

      EngineConfiguration config = new FileProvider("client_deploy.wsdd");
      StockQuoteServiceService locator = new StockQuoteServiceServiceLocator(config);
      ...
    5. Run the client, you should get no errors:

      stock quote service returned XXX: 55.25

      Your client is now sending a Username Token in the wsse request header with the username "wss4j" (see client_deploy.wsdd) and password "security" (see the PWCallback implementation).

      Another way to do this is to have the client application set the username and CallbackHandler implementation programmatically instead of using the client_deploy.wsdd file:

      ...
      import org.apache.axis.client.Stub;
      ...

      Remote remote = locator.getPort(StockQuoteService.class);
      Stub axisPort = (Stub)remote;
      axisPort._setProperty(UsernameToken.PASSWORD_TYPE, WSConstants.PASSWORD_DIGEST);
      axisPort._setProperty(WSHandlerConstants.USER, "wss4j");
      axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_REF, pwCallback);

      where "pwCallback" is a reference to a PWCallback implementation. See the Axis handler for WSS4J documentation for more details on this.

      UPDATE: I've tried to set the callback using the techinque above, but without much success. I'll continue trying, and when I get it working I'll update this section again :)


      UPDATE 2: After some testing and teaking and good ideas from people, I got the thing above working. It's all explained in another blog post.
    6. Try modifying your client's PWCallback to return the wrong password, or send the wrong username. The service should reject your requests.

    Known problems

    When I first ran this tutorial myself, I got a stacktrace error that didn’t interrupt the program, but printed out a warning about the xalan.jar package. It looks something like this:

    - Unable to patch xalan function table.
           java.lang.NoSuchFieldException: m_functions
                  at java.lang.Class.getField(Unknown Source)
                  at org.apache.xml.security.Init.registerHereFunction(Init.java:429)
                  at org.apache.xml.security.Init.init(Init.java:124)… (and so on)

    This may have to do with how the xalan.jar package is deployed on your system and what version of xalan you use and the version of JDK. I got the tip from Ashok Shah to make sure I use Java version 1.4.2_04 instead of 1.4.2_06 that I used. I’ve not tried this yet, but I will do.

    UPDATE: I tried to put the xalan.jar in the JAVA_HOME/lib/endorsed/ directory, but it didn't work much better. So I updated to JDK 5.0 and made sure that the xalan.jar package from the WSS4J distribution was available to tomcat and to my client, and behold - it works :)

    UPDATE 2: I got a tip from Martin Stemplinger that the xalan.jar should probably go into the JAVA_HOME/jre/lib/endorsed/ directory. I've not tested it myself, but it sounds right.

     

  • [Java][Interop] Testing WSS4J

    Long time since I wrote anything here, but I've actively started to look at what AXIS and WSS4J has to offer when it comes to do WS-Security in Java and how it interops with web services in .NET using WSE 2.0. It's taken me a while to get the Java environment up and running at home because I had to reinstall the whole box. What I've done now is I've installed Virtual PC 2004 and created an XP image with a complete Java environment

    I've downloaded and installed (so far):

    j2sdk 1.4.2_06
    tomcat
    ant
    axis
    maven (to checkout and built wss4j)
    wss4j
    cvs
    junit
    eclipse (java IDE)
    lomboz (j2ee plugin for eclipse)

    It takes a while to configure maven and all the stuff you need for it (cvs, junit and so on) and get it running and in the end I had to go into the ant script and remove the junit testing :( Biggest problem when working with Axis and Wss4j is the lack of documentation. What I'm following now when I do my experiments are some basic documentation about how to do usernametokens with wss4j written by a kind fellow in the wss4j maillinglist.

    I've just got the basic stuff working with a Java client using Axis calling a web service in tomcat using Axis too. That's the easy part, I'll now try to plug in wss4j and do usernametoken... I'll blog the results here before I go to bed :)