Attention: We are retiring the ASP.NET Community Blogs. Learn more >

Contents tagged with Walkthroughs amp; Tutorials

  • Secure ASP.NET Web API with Windows Azure AD

    Note that the APIs, tools and methods change quickly in this area, this blog post will get old and die eventually…

    Many organizations now look at the Azure environment to host their websites and web APIs, and some decide to move their mail and Active Directory too. Now with the latest updates and previews in Azure, you’re able to secure your web APIs with Azure AD. Vittorio Bertocci wrote an article for MSDN Magazine about Secure ASP.NET Web API with Windows Azure AD and Microsoft OWIN Components and it worked fine up until a couple of weeks ago when things moved around in these parts of Azure Smile

    I will try to describe in detail how to secure your web API with Azure Active Directory now, using Visual Studio 2013 and the preview of ADAL (Active Directory Authentication Library) package.

    Create a web API project

    Fire up Visual Studio and create a new ASP.NET Web Application. I’m calling mine “johandanforth”, I’m selecting “Web API” and click the “Change Authentication”

    image

    In the next dialog, click “Organizational Account” and enter the domain of your Azure AD tenant, in my case it’s “irm.se”:

    image

    After you press “OK” you’ll be asked to login with your Azure AD account, then “OK” again and Visual Studio will create a web application resource in your Azure AD. Now look it up in the Azure Management application. Note that you may have to log out and in again or restart the Azure management web app to see the newly created application.

    In my case the application has been named “johandanforth” and is for securing your development project on localhost. The SIGN-ON URL (also called the APP URL in the management application) is https://localhost:44310/ which can be seen both on the applications overview and if you click on the application and go to the CONFIGURE “tab”:

    image

    The sign-on url should be the same as where the web API is hosted, in this case the same localhost-settings as you got in the development project web-settings.

    Open up web.config of your web API project and have a look at these settings:

        <add key="ida:Audience" value="https://irm.se/johandanforth" />
        <add key="ida:ClientID" value="d169beb7-34bc-441b-8b93-87e3181a1132" />

    The “ida:Audience” in web.config correspond to the value of APP ID URI of the application resource in the management portal and the “ida:ClientID” correspond to the CLIENT ID in the portal.

    image

    Update the web application manifest

    Before you any application can access this resource, we must update the “manifest”. Still on the CONFIGURE tab for your web API resource, there should be a MANAGE MANIFEST menu option down at the bottom toolbar. Click it and select “Download Manifest”. It’s a json-text-file, and should save it somewhere where you know is, because we will add a section to it and then upload it again using the same menu option:

    image

    Open the json-manifest and replace the "appPermissions": [], section with this:

    "appPermissions":[
          {
             "claimValue":"user_impersonation",
             "description":"Allow the application full access to the service on behalf of the signed-in user",
             "directAccessGrantTypes":[
    
             ],
             "displayName":"Have full access to the service",
             "impersonationAccessGrantTypes":[
                {
                   "impersonated":"User",
                   "impersonator":"Application"
                }
             ],
             "isDisabled":false,
             "origin":"Application",
             "permissionId":"b69ee3c9-c40d-4f2a-ac80-961cd1534e40",
             "resourceScopeType":"Personal",
             "userConsentDescription":"Allow the application full access to the service on your behalf",
             "userConsentDisplayName":"Have full access to the service"
          }
       ],
     
    At the moment, there doesn’t seem to much in way of documentation on this manifest file, but it should work without modifications. I’ll try to write something about this manifest file as soon as I get some more docs on it.
     
    Next thing you do is upload the modified manifest file. Note that the portal may give you an error message during upload, but things seems to work anyway! It may look like this:
    image

    Create a client

    Now let’s try and access the sample ValuesController Web API in your development environment using a web browser, Fiddler or similar. In my case it’s https://localhost:44309/api/values and you should get a HTTP/1.1 401 Unauthorized error back. To be able to access the protected resource, you must add a client resource in the AD and configure it to access the web API.

    If you are not there already, in the Azure management portal, go to Active Directory, click on your domain name and select the APPLICATIONS tab. Then click the ADD-icon down the center of the bottom toolbar. Then go ahead and select “Add an application my organization is developing”:

    image

    Enter a name for your client (I’m going with “johandanforth-client”, and make sure you select NATIVE CLIENT APPLICATION because we’re going to write a simple Windows WPF client to call our web API:

    image

    On the second page, type in a redirect url for your client – it can be anything as long as it is a valid url. I’m going with https://irm.se/johandanforth-client.

    image

    Your client is now created, but the last step to do is to give permission to our web API. Scroll down to the bottom of the client application CONFIGURE page and look at the “permissions to other applications (preview)” section. In the dropdown marked “select application” you should be able to see and select your web API. You must also select the “Delegated permissions” dropdown and mark the only option available for you. As you can see, the description matches the text in the manifest file:

    image

    Remember to SAVE!

    Almost there, hang on…

    Create a Windows WPF application for testing

    Add a new Windows WPF Application to your solution, I’m calling it “johandanforth.client”, and create a button with a click-event or something to hold the code that will authenticate and get values from your API. Bring up NuGet and search for “ADAL”, make sure you have “Include Prerelease” selected:

    image

    Install the prerelease package from February 2014 (it will probably be updated soon), then paste this code into the click-event of that button you created earlier:

    using System;
    using System.Net.Http;
    using System.Windows;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;
     
    namespace johandanforth.client
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
     
            private async void Button_Click(object sender, RoutedEventArgs e)
            {
                //this is to accept dev certificates - do not use in production!
                System.Net.ServicePointManager.ServerCertificateValidationCallback = ((a, b, c, d) => true);
     
                var ac = new AuthenticationContext("https://login.windows.net/irm.se");   //ad domain = irm.se
     
                var ar1 = ac.AcquireToken("https://irm.se/johandanforth",   //app id uri of the web api
                  "91b4bc31-92c2-4699-86f5-fa84a718da30",                   //the client id
                  new Uri("https://irm.se/johandanforth-client"));          //the client redirect uri
     
                var authHeader = ar1.CreateAuthorizationHeader();
     
                var client = new HttpClient();
                var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44310/api/values");
                request.Headers.TryAddWithoutValidation("Authorization", authHeader);
                var response = await client.SendAsync(request);
                var responseString = await response.Content.ReadAsStringAsync();
                MessageBox.Show(responseString);
            }
        }
    }
    

    There are a few lines that must be change to work with your sample code. First change the AuthenticaitonContext to match your Azure AD domain/tenant name:

    var ac = new AuthenticationContext("https://login.windows.net/irm.se");
    

    Next look at the code part which acquires a token – this is when the login dialog pops up and asks the user to log in using his or hers organizational account. This line corresponds to the web API uri, which is the same as the “ida:Audience” in your web.config file, so update it to match that value:

    var ar1 = ac.AcquireToken("https://irm.se/johandanforth",  

    The next line is the client id of the application client you created in Azure AD, look it up and change the id accordingly:

    "91b4bc31-92c2-4699-86f5-fa84a718da30",

    The last line is the redirect-uri of the client, look it up on the same page in the Azure management portal:

    new Uri("https://irm.se/johandanforth-client"));

                  

    You must also modify the line with the call to the web API method to match your web server localhost settings:

    var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44310/api/values");

    Now, start the web API program in debug mode and wait until you see the Home Page, then right click and start debug of the WPF client. Click the button and you should be prompted to log in with your organizational account (this is the look of it in Swedish):

    image

    After a successful login, the code continues and you should be greeted with this:

    image

    Still with me?

    Deploy to Azure

    This is all well and good (hopefully), but we want to deploy our web API to Azure and run our clients against it in the cloud. To do that we need to:

    1) Create a web site to host our web API in Azure

    2) Publish our code to the site

    3) Create an Azure AD resource for the web API (VS does this for you)

    4) Modify the manifest for the web API (like before)

    5) Give the client permission to the new resource (like before)

    6) Update web.config and the client code to match the IDs and URIs of the new resources (ida:Audience == APP ID URI == the resource you want to access)

    7) Publish the code again (to get the updated web.config uploaded)

    8) Run!

    Here’s some quick shots of some of the steps. First create a web site in Azure by selecting “WEB SITES” in the left hand menu of the Azure Portal, press the big plus-sign down in the corner and create a custom web site. I’m calling mine “johandanforth” (ignore the error message Winking smile )

    image

    Now go back to Visual Studio, right click the web API project and select “Publish…”.

    image

    Press the “Import…” button to download and import the publishing profile of the web site you created, and select the newly created Web Site in the dropdown. Click OK.

    image

    You have to authenticate one or two times to get past these steps, but should finally get to the last step of the Publish process:

    image

    When Visual Studio is done publishing, a browser page will open up with the Home Page showing:

    image

    Visual Studio should now have created a new application resource in the Azure Active Directory, so get back to the portal and have a look at the list of AD applications. A resource named “WebApp-xxxxxxxxx.azurewebsites.net” should be listed there. Note – you may have to sign out and sign in to the portal to show the web api resource. This has happened to me a couple of times!

    image

    Click on the resource and look at the details in the CONFIGURE tab.

    image

    Copy the name of the “APP ID URI” and past it into the “ida:Audience” value in the web.config file of your web API project in Visual Studio:

        <add key="ida:Audience" value="https://irm.se/WebApp-johandanforth.azurewebsites.net" />

    The same value must also be updated in the WPF client:

    var ar1 = ac.AcquireToken(https://irm.se/WebApp-johandanforth.azurewebsites.net,  

    You must also (of course) update the call to the web API method to point at the web site in the cloud:

    var request = new HttpRequestMessage(HttpMethod.Get, "https://johandanforth.azurewebsites.net/api/values");

    We’re not done yet though… now you have to do the same steps you did for the localhost resource earlier - download and update the manifest file for the web API resource and give the client resource permission to use the service.

    Finally (yes finally) – publish the web API project again to get the updated web.config file to the cloud site!

    Once the code is published, you should be able to run the client successfully.

    Please give feedback if you have problems with any step/s of this walkthrough. Other questions, bugs and such is better asked on the Windows Azure Active Directory forum. I’m still learning these things and trying to keep up with the changes Smile

  • A .NET Guy Looking at Some Ruby (Part 2)

    This is me continuing scribbling down notes as I go along relearning lost Ruby skills and at the same time comparing the Ruby syntax and way of writing code to .NET. In my previous post I very briefly went through the basics of variables, loops, iterators, conditionals and basic sorting. This post will look at classes, methods, inheritance and how to reuse Ruby code with include (require) statements.

    Methods

    Method or function declaration in Ruby is simple, perhaps even simpler than how VB does it:

    def my_method(name)
        puts "Hello " + name.to_s
    end

    my_method "johan"
    my_method("johan")

    As you can see, you may call the method with or without parenthesis, to Ruby it doesn’t matter. I’ll have to read some more Ruby code to see if there are any exceptions or aesthetic rules to using parenthesis or not, but it seems that many Rubians use parenthesis when calling functions or methods on objects/classes, but leaving it when calling simple methods like puts and similar:

    Oh, and Ruby doesn’t support method overloading! There are ways around this with default method parameters, add-ons to the language and so on, but basically – no method overloading. I’ll show some sample code later on when I get to class initialization.

    Classes, Constructors and Properties

    Ruby is of course object oriented, and very much so (without going into the “where everything is an object” speech). So, of course you can declare objects with properties and methods on them. Declaring a class is familiar:

    class MyClass
    end

    You instantiate a class using a built in static “new” method on the class:

    a_class = MyClass.new

    Adding a (instance) method to the class is straight forward:

    class MyClass
      def hello(name)
        puts name
      end
    end

    Static methods, or class methods, are declared by prefixing the method with “self.”, like this:

      def self.reverse_upcase(name)
        name.reverse.upcase
      end 

    Adding a constructor or initializer to the class is done by declaring an initialize function:

      def initialize(name)
        @name = name
      end

    As constructor overloading is not possible, you’ll have to add a default value to the parameter, making it optional in a VB-style of way:

      def initialize(name = "Anonymous")
        @name = name
      end

    The @name variable is called an object variable and it is distinguished by the use of the ‘@’ sign. There is no need to declare the object variable in any other way, but to access the object variable (or property or attribute or…) you have to add property accessor code to the class:

      def name
        @name
      end

      def name=(name)
        @name = name
      end 

    Now, class properties is used so much that there is a so called attribute accessor keyword available for you in Ruby, making things a bit simple to type:

    attr_accessor :name

    There are also other attribute keywords like attr_reader and attr_writer available for your pleasure.

    Inheritance

    Last thing to say about the basics around classes in Ruby is how to handle inheritance. The syntax is similar to c#, but instead of a colon (:), use the less than (<) operator. A very simple example of where the class Employee inherits from Person could look like this:

    class Person
      attr_accessor :name

      def initialize(name)
        @name = name
        end
    end

    class Employee < Person
      attr_accessor :number

      def initialize(name, number)
        @name = name
        @number = number
      end

      def to_s
        @name + " (emp# " + @number + ")"
      end

    end

    e = Employee.new("Johan","123")

    puts e
    puts
    e.is_a?(Person)
    puts
    e.is_a?(Employee)

    Would print:

    Johan (emp# 123)
    true
    true

    Class Reuse

    If you want to “use” or “include” Ruby code you’ve already written? Save your code (class) into a MyClass.rb file (as an example) and add a “require” statement to the top of your code where you want to reuse the MyClass class:

    require "MyClass"

    and classes in the MyClass.rb file will become available for you.

    Wrapping Up

    I guess that’s enough for now. I think this will be enough to be able to understand some Ruby code if you come across it, unless it contains too many weird class methods like Array#flatten and such J. Then there are a bunch of modules available to Ruby for doing a number of things. I may get into that in a later blog post, I’ll see.

    All in all I think Ruby is cool. I’ve used dynamic languages and scripting languages before, but nothing as sleek as Ruby. Ruby is minimal code, fast to type and there’s a flow to the code which makes it quite easy to read.

    Next

    Next I’ll blog about downloading and building IronRuby and using it to call on .NET libraries, WPF and perhaps Silverlight (which seems to be all the buzz right now). I think I’ll look at exceptions and error handling after that and see if I can get some time to look at Rails again.

  • A .NET Guy Looking at Some Ruby

    I’m thinking it would be nice to relearn the basics of Ruby (which seems to gain more and more popularity) and compare it to some of the dynamic features .NET 3.5 has been blessed with. I’m also interested in where the Microsoft version of the language, IronRuby, is now and how I can use the .NET libraries from it. I installed and looked at Rails some (long) time ago now, but I never got the chance to use it and my minimal Ruby skills have now faded to nil again. So this is me taking it up again, hoping I can use it for something real, perhaps for unit testing or just test driving some .NET code with the help of IronRuby.

    What you’re reading is just me scratching down notes as I go along, it’s not an article or so, this is just for me to have something to get back to later on, so bear with me. I will probably write down things that are not right or make wrong assumptions, but I’m happy to get comments if you see something awfully wrong.

    Edit: I had to change the section on line continuation, because I was wrong about what I first wrote.

    Getting Started

    So how do I get started? First I need a Ruby implementation for the platform I’m running on, in my case Windows Vista. On RubyForge there is a Windows installer for Ruby which contains everything you need to get going. It’s available on http://rubyforge.org/projects/rubyinstaller/ and I’m going for it. Later on I’m also going to download and build IronRuby and .NET applications using the Ruby language, but you will still need Ruby installed to build IronRuby. The IronRuby website www.ironruby.net has some notes on all this, but I’ll blog on that later I think.

    The installer will give you the Ruby language, runtime and an editor called SciTE. It also includes a samples directory which may come in handy. With Ruby you got several options to create and test small console apps. After the installation completes, just open up a command prompt and type “irb” and you’ll get an interactive Ruby command prompt, which is very handy for both testing and learning. Another option is to go to the Start menu and use “fxri”, which is a graphical interface to the Ruby documentation and also includes an interactive Ruby console. I’ve used both today. Fxri is great when you’re looking at someone else’s Ruby code and you see a command or class you don’t recognize.

    The Ruby installer also comes with an electronic version of the book “Programming Ruby - The Pragmatic Programmer's Guide” by Dave Thomas – which I can tell is also a great source for knowledge. I’ve not read it yet, but I’ve used it to look up certain things.

    Tutorials

    Next I need some getting started articles or such, and there are tons of it on the Internet. You could use the book above, but I’m looking for something which will get me into the dirt quickly.

    I found a “Learning Ruby” website by Daniel Carrera which looks promising, and I’m using it to learn the very basics. You’ll find it at http://www.math.umd.edu/~dcarrera/ruby/0.3/index.html  and it’s using the “irb” console in its first steps, which works perfect with IronRuby. Just open up a command prompt and type:

    irb --simple-prompt

    You'll be greeted with a prompt like this…

    >>

    …and off you go. Just type your code and press enter. There are other great intros to Ruby on the Internet, and this one by Steve Litt looks good, http://www.troubleshooters.com/codecorn/ruby/basictutorial.htm

    Variables and Strings

    Most tutorials start with numbers, strings and variables. There’s not much to say about Ruby variables – you don’t have to declare them, just initialize the variable with a value and start using it:

    i = 1

    Exactly what I’m expecting from a dynamic language, and strings are very easy to work with:

    name = "Johan"
    name = name + " Danforth"
    puts name

    The string class in Ruby has a number of powerful methods you can use to work with, same as with the string class in .NET, but the Ruby string class has more than 100 methods on it (from counting the methods in fxri). The .NET string class has around 70 methods, but 45 of these are extension methods that are new to .NET 3.5, and I think many of them were inspired by dynamic languages just like Ruby. I have to say though that some of the methods in Ruby do the same things sort of and some methods are quite funny. Like squeeze, chomp, chop and such :) Methods for parsing, encoding, converting and such are also included in the Ruby string class, functionality that may be baked into other libraries in the .NET framework.

    Running a Ruby Program File

    Running a Ruby program from a file is dead easy. From the console type “ruby myprogram.rb” and press enter. The installer also associates the .rb file extension with the Ruby executable, so it’s possible to just type “myprogram.rb” or click the file from Windows Explorer.

    A good editor for your .rb programs seems to be SciTE which also comes with the Ruby installer. Just open up the editor, type the program and press F5 to run it (make sure you have saved it with the .rb file type). The results will be shown in the right pane of the SciTE editor. Nifty.

    Type Conversion

    What strikes me as odd is that Ruby is a dynamic language, but automatic type conversion is sometimes not as good as I would think, and something as simple as this will give you an error:

    sum = 1 + 2
    puts "The sum is " + sum

    test.rb:2:in `+': can't convert Fixnum into String (TypeError)

    So, in a print statement like the above, Ruby won’t convert the number variable into a string like c# or VB does:

    Dim sum = 1 + 2
    Console.WriteLine("The sum is " & sum)

    You’ll have to convert the sum variable to a string like this:

    puts "The sum is " + sum.to_s

    Aesthetic Code

    Ruby seems to be much about the aesthetics of the code you write. Most Ruby tutorials seem to bring up naming of variables, classes and methods in an early state. What is funny is that the casing of a variable matters to the interpreter, because if you uppercase a variable, it’s treated as a constant. You can change the value of a constant, but the runtime will give you a warning:

    PI = 3.14
    puts PI
    PI = 3.14159265
    puts PI

    test.rb:3: warning: already initialized constant PI
    3.14
    3.14159265

    But what about code statements that are too long? In c# you can make a line break after almost any statement, and it's the same thing in Ruby. I was wrong about this at first, because in Ruby (as in VB) you can put a backslash (underscore in VB) at the end of the line to do break up a line of code: 

     puts "this is a " + "very " +
      "long statement"

    Be careful not to put a space after the backslash, or you’ll get a strange looking error:

    test2.rb:1: syntax error, unexpected $undefined
    puts "this is a " + "very " + \
                                   ^

    I’m not fond of the line continuation underscore thing in VB, and I’m thinking the same of the backslash in Ruby, so you won't see me use it.

    Loops

    Ruby seems to be good at this, and the most common syntax for-loops is easy to read and pretty nice:

    i = 1
    4.times do 
      puts "pass " + i.to_s 
      i += 1
    end

    Well, this is just like any other for-loop out there, but the syntax is nicer. There are other ways to type the same thing in Ruby but I’ll get back to that later. I know people have tried to come up with a similar syntax using .NET 3.5 method extension and lambda expressions, but I think it’s hard to make it as compact as in Ruby. My 10 minute feeble attempt at making a c# version of the Ruby times-method would probably be an extension method like this:

            public static void times(this int number, Action codeblock) 
           
                while (number-- > 0) 
                    codeblock(); 
            }

    And I could call it with a one-liner lambda expression like this:

                var i = 1; 
                4.times(() => Console.WriteLine("hi! " + i++));

    Or with a delegate (with some richer code inside) like this:

                var i = 1; 
                4.times(delegate(){ 
                    Console.WriteLine("pass " + i); 
                    i += 1; 
                });

    Not as nice as the Ruby variant, but close :) The System.Action Delegate class in .NET 3.5 is pretty awesome btw, and you got a generic version of it as well if you need to pass arguments on to the delegate.

    Of course Ruby has the traditional VB-like “for” statement as well, with a couple of variations. A 1-5 for-loop looks like this:

    for x in 1..5 
      puts x
    end

    But a 1-4 for-loop looks like this, see the difference?

    for x in 1...5 
      puts x
    end

    2 periods mean as long as the variable is less than or equal to the “to” value (inclusive), 3 periods mean as long as the variable is less than the “to” value (exclusive). I’m sure this is a common coding mistake, as it’s easy to miss. And as you can see, no braces/brackets like in c#, so I guess VB programmers feel a bit more comfortable with this syntax, but the for-loop statement in Ruby is, I believe, syntax sugar for this statement, which involves braces/brackets but equally valid:

    (1...5).each {|i| puts i }

    Conditional Statements

    Conditional statements, in Ruby are a bit of a mix between VB and c#. Like in c#, “=” assigns values and “==” compares. I like that. For example:

    a = 1
    if a == 1 then 
      puts "true" 
      else 
        puts "false" 
      end

    Arrays and Lists

    Arrays in Ruby have much in common with the same in c# and VB.NET, especially with the new features in .NET 3.5. Ruby is zero based, so you address a specific item in the array with [] , and it’s possible to initialize arrays in a similar way in all languages. In Ruby:

      numbers = [ "zero", "one", "two" ]
      addresses = [ [ 10, "Kings Rd" ], [ 25, "Queens Rd" ] ]

    Initializing a simple string array in c# 3.5 is quite similar:

     var numbers = new[] { "zero", "one", "two" };

    The “addresses” array can be solved in many different ways in .NET, but I think the c# equivalent would be: 

    var addresses = new[]

               new object[] {10, "Kings Rd"}, 
               new object[] {25, "Queens Rd"}
    };

    As you can see, c# requires a bit more typing. Personally I prefer to create an array of anonymous types in c#, as it is much easier to code against:

    var addresses = new[] { 
               new  { Number=10, Street="Kings Rd" }, 
               new  { Number=25, Street="Queens Rd" }
    };

    The VB.NET equivalent of a simple string array and array of anonymous types is something like this I think (I’m not a VB-guy):

            Dim numbers() = {"zero", "one", "two"
            Dim addresses() = {New With {.Number = 10, .Street = "Kings Rd"}, _ 
                               New With {.Number = 25, .Street = "Queens Rd"}}

    Oh, the number of things you can do with arrays in Ruby, and in a very intuitive way too. Reverse, sort, add, remove and other methods we recognize from -.NET List and List<> types, but like the Ruby string class there’s a number of unusual but powerful methods on Ruby arrays like flatten (sample taken from the docs):

      s = [ 1, 2, 3 ]            #=> [1, 2, 3] 
      t = [ 4, 5, 6, [7, 8] ]  #=> [4, 5, 6, [7, 8]] 
      a = [ s, t, 9, 10 ]       #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] 
      a.flatten                   #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10

    If I coded Ruby for 10 years, I’m not sure I would ever use that method :) It flattens an array of variable length arrays into a one-dimensional array recursively. Cool to have this method built into the Ruby language from scratch.

    But other than a few methods like this, it feels like you can do about the same things with the .NET lists and arrays. You may have to turn your array into a list or vice versa, so again we’re talking a little bit of more code than Ruby, but not too much.

    Iterators

    Related to arrays are of course iterators. A common way to iterate through a Ruby array is to use do – end like some of the samples above. But there is a cool way to use a similar syntax, each – do – end  and pass the current item of the array into a block of code. This seems to be a common style of coding in Ruby:

    friends = ["Melissa", "Jeff", "Ashley", "Rob"]
    friends.each do |friend| 
         puts "I have a friend called " + friend 
       end 
      

       5.times do |i| 
         puts "pass " + i.to_s 
       end

    It’s quite possible to use a similar style of coding in c#, by using a foreach loops, lambda expressions or delegates:

    var friends = new[] {"Melissa", "Jeff", "Ashley", "Rob"};

    foreach(var friend in friends) 
               Console.WriteLine("I have a friend called " + friend);

    5.times(ii => Console.WriteLine("pass " + ii));

     

    public static void times(this int number, Action<int> block)

        int i = 0; 
        while (i < number) 
       
            block(i); 
            i++; 
        }
    }

    As you can see, I’m using a method extension to create my own times() method, using a lambda expression to pass a block of code to the times() method with the help of the generic Action delegate. It’s a variation of the times() method I used earlier.

    Sometimes when the loop just contains one line of code some Ruby coders seem to replace the do – end with curly braces like this:

    6.times { |i| puts "pass " + i.to_s }

    As I mentioned earlier when I wrote about loops, this is just another way to type it. If the compiler turns the do-end into braces or vice versa I don’t know – it just works.

    The “each” keyword in Ruby seems to be pretty powerful and lets you iterate through different sorts of lists in many ways. It is also used to handle hash-tables. A hash-table in Ruby is declared and initialized like this:

    fruits = { 
     "123" => "Apple", 
     "456"  => "Banana", 
     "789"    => "Orange"
    }

    As you can see, we’re using squiggly/curly braces/brackets instead of squares. Not sure why it is like this and I would have preferred to use squares here to as for arrays, but this apparently is the way… To access a particular value, just address it with the key of course:

    puts fruits["123"]

    And iterating through the table with the each – do – end syntax is starting to feel natural now:

    fruits.each do |key, value| 
     puts key + " => " + value
    end

    Of course you don’t need to use the names “key” and “value”.

    It’s quite similar to the way most other languages handle hash-tables. Hash-table declaration and initialization in c# is quite similar thanks to the new language features in c# 3.5:

    var h = new Hashtable
               {"123", "Apple"}, 
               {"456","Banana"}, 
               {"789","Orange"}};

    Sorting Your Own Way

    In c# lists and arrays there are ways to do your own custom sorting, but in Ruby it’s done in a different way. You write your own sorting in-line in a sort – do – end loop like this for example:

    friends = [ 
        [ "Johan", "Danforth" ], 
        [ "Eric", "Quist"],
        [ "Björn", "Poppe" ]
    ]

    friends = friends.sort do |a,b| 
      a[1] <=> b[1]   # compare last names
    end

    Nice thing here is that is seems to be quite simple to do custom sorting of arrays with complex objects in them. The <=> operator is something which is built into Ruby because sorting and comparing is so often done. Basically a <=> b,

    - returns -1 if a is less than b

    - returns 0 if a is equal to b

    - returns 1 if a is greater than b

    The operator is able to work with strings and numbers.

    That’s Enough for Now

    I think this is enough for now. I’ll post a follow up about methods, classes, inheritance and such later on.

  • [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
  • [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] Installing Eclipse and Lomboz

    About

    This is a walkthrough, getting started, tutorial or just something I wrote down mostly for myself. Someone else might find it useful too so I decided to post it on my blog more or less as it is. Most of the information is gathered from other sources on the Internet, and I've tried to list the resources. If you find a missing resource or error in the text, please send me a note.

    Much of the information in this "article" is gathered from the ObjectLearn website, which has a number of great walkthroughs for Lomboz.

    Note! Please read the changes section below, it may contain important information.

    Changes

    2005-10-31 Important note! I got a message that the ObjectWeb people now have repackaged Lomboz into something a little easier to install and get started. Have a look at their website before you dig into this (perhaps now obsolete) article -> http://lomboz.objectweb.org/downloads/drops/S-3.1RC2-200508311616/

    2004-07-07 (a little bit later) Ok, article is now updated. I use Eclipse 3.0 and Lomboz 3rc2, and I've also added a short "getting started" section at the end.

    2004-07-07 I just realized that both Eclipse and Lomboz had been updated with newer versions (version 3.0 of Eclipse for example) and I have to update this article again! :)

    2004-07-06 Updated the Lomboz installation steps. A few things were very wrong. Also updated a few typos in the text. Thanks for the feedback.

    Purpose

    This document is intended to explain how to install the Java IDE Eclipse and the Lomboz plug-in. Some of the information in this article is taken from the Eclipse and Lomboz (ObjectLearn) websites.

    General Information

    Eclipse is a kind of universal tool platform - an open extensible IDE for anything and nothing in particular. Eclipse is an open platform for tool integration built by an open community of tool providers. Operating under an open source paradigm, with a common public license that provides royalty free source code and worldwide redistribution rights, the eclipse platform provides tool developers with high flexibility and control over their software technology.

    Lomboz is a free eclipse plug-in for the J2EE developers, developed by Objectlearn. It is a tool with a simple philosphy: "No magic tricks". Lomboz is integrated with many popular open source J2EE tools such as: Jasper, XDoclet, Axis and Ant. Lomboz is available as open source under LGPL license from at the ObjectWeb Forge.

    Resources

    Eclipse http://www.eclipse.org/
    Lomboz http://www.objectlearn.com/
    Eclipse plug-ins http://eclipse-plugins.2y.net/

    Lomboz v3.0 uses models based on eclipse EMF project. If you do not have it installed already, You will need to download and install EMF runtime builds release 2.0.0 or later. EMF MUST BE INSTALLED PRIOR TO USING LOMBOZ.

    Installation of Eclipse

    How to install and configure Eclipse.

    1) First of all, download and install a Java 2 SDK, Eclipse needs version 1.3 or higher, but Lomboz needs 1.4, so use J2SDK 1.4.2 from Sun http://java.sun.com/j2se/1.4.2/download.html.
    2) Download Eclipse from http://www.eclipse.org/, preferably the latest 3.0 version.
    3) Unzip the file to the root of the drive, for example C:\, this will give you a C:\eclipse\ directory where you’ll find the eclipse.exe binary.
    4) Start eclipse by executing the eclipse.exe file.
    5) Go to Window->Preferences then look at the settings for Java->Installed JREs and make sure that your JSDK is used. If it is not, add it to the list of installed JREs and check it. When I change JRE, I usually exit Eclipse and restart it... just to be sure.

    Installation of EMF

    How to install and configure EMF, a required J2EE plug-in for Eclipse if you want to use Lomboz.

    1) Download EMF (in my case, version 2.0.0) from the EMF site.
    2) Unzip it so it fits nicely into the existing Eclipse directory, in my case I unzip it to the C:\ because that's where I unzipped Eclipse (default).

    Installation of Lomboz

    How to install and configure Lomboz, a J2EE plug-in for Eclipse.

    PRE-REQ: Lomboz v3.0 uses models based on eclipse EMF project. If you do not have it installed already, You will need to download and install EMF runtime builds release 2.0.0 or later. Download the "ALL" package.

    1) First, download a version of Lomboz that runs well with the version of Eclipse that you use. In my case I use Lomboz version 3 RC 2 available from http://forge.objectweb.org/project/showfiles.php?group_id=97
    2) Exit Eclipse if it is running.
    3) The plug-in is distributed in ZIP format, therefore, using your ZIP file utility, unzip lomboz.zip file into the <Eclipse_Home> directory. This will create a couple new plug-in directories named com.objectlearn.jdt.j2ee and com.objectlearn.jdt.j2ee.editors. Make sure that Lomboz is extracted properly into the C:\eclipse\plugins directory.
    4) Start Eclipse.
    5) Select menu "Window>Customize Perspective...", in the "Shortcuts" tab choose the "New" submenu and check "Java->Lomboz J2EE Wizards".
    6) Select menu "Window>Customize Perspective...", in the "Shortcuts" tab choose the "Show View" submenu and check "Lomboz J2EE".
    7) Select menu "Window>Customize Perspective...", in the "Command"s tab in the available commands tab, check Lomboz Actions.
    8) Close the "Customize Perspective" dialog by presseing the OK button.
    9) Select menu "Window>Preferences", in the "Workbench->Label Decorations", check "Lomboz J2EE Decorators".
    10) Once you confirmed your selections, you will see the newly added toolbar button in the Eclipse toolbar and the different Lomboz wizards will be available to you from the “File->New ” menu.

    Configure Lomboz

    Some settings really MUST be properly configured for Lomboz to work. Follow these instructions to the point.

    Java Settings

    You must make sure that Java projects have separate source and binary folders. Open the preferences dialog from "Window->Preferences" menu and expand the "Java->Build Path->New Project" settings. Make sure you set it up with this values:

      • Check the "folders" radio button.
      • Source folder name should be "src".
      • Output folder name should be "bin".
      • Select "JRE_LIB variable" in the drop-down list for JRE libraries.

    Lomboz Settings

    Most application servers use the standard Java compiler (javac) to compile JSP files.  Javac is found in the tools.jar distributed with standard Java JSDKs (NOT JREs), that is why a proper JSDK must be installed for Lomboz (see above). You must select the tools.jar that will be used by the application servers here. It is normally found inside the “lib” folder under the JSDK installation.

    Open the preferences dialog from "Window->Preferences" menu and expand the Lomboz settings. Then enter the path to the tools.jar file, something like: C:\j2sdk1.4.2_03\lib\tools.jar (depending on where you installed your JSDK). Leave the default values, but if Tomcat is used, you might want to check the setting for restarting server after deploy.

    Open the preferences dialog from "Window->Preferences" menu and expand the "Lomboz->Server Definitions" settings. This is where you enter the correct settings for your selected application server. If you for example are using Tomcat 5.0.x, it might look like this:

      • Application Server directory: C:\jakarta-tomcat-5.0.19
      • Address: localhost
      • Port: 8080
      • Classpath variable name: TOMCAT_HOME
      • Classpath variable: C:\jakarta-tomcat-5.0.19

    NOTE: It is VERY important that you click Apply after you have changed the server settings. Step through the other tabs to make sure that the different CLASSPATH settings are correct.

    Files with server definitions are stored in a folder named "servers" below the Lomboz plug-in directory. Lomboz scans the folder every time it needs a definition. To add a new server type, just add anotherone of these files into this folder.

    In order to use (activate) any of these definitions you must ALWAYS visit the Lomboz preferences.

    Creating and Testing a J2EE Application

    Now Eclipse and Lomboz is ready for testing. Just to get you started:

    1) Select "File->New->Project..."
    2) Expand "Java" and select "Lomboz J2EE Wizards->Lomboz J2EE Project". (There are many ways to get here, just make sure you end up selecting "Lomboz J2EE Project" in the end :)
    3) Continue the wizard by clicking "Next" to the "Project Settings" dialog.
    4) Give it a project name, something like "My Lomboz Project". Press "Next".
    5) In the "Java Settings" dialog, just press "Next" button again, no need to add any things here unless you know you will need some cool java libraries in your project.
    6) In the "Create J2EE Module" dialog, you must add a "Web Module". Click the little "Add..." button and enter a module name called "myweb" or something.
    7) Select the "Targeted Servers" tab, find your selected application server in the "Type:" dropdown (in my case Apache Tomcat v5.0.x) and press the little "Add..." button to add it to the list of targeted servers. I know, these dialogs are a bit strange, but you get used to them eventually...
    8) If you added one (or more) servers, the "Finish" button activates, and you can click it.
    9) The "Package Explorer" window should now show you the newly created project. Expand it and see "myweb" there. It should contain 2 jsp-files created for you.
    10) To test the stuff:
        a) Find the "Lomboz J2EE View" window and expand "My Lomboz Project".
        b) Right click "myweb" and select "Deploy" and messages should start showing up in your "Console" window.
        c) Start the application server (unless it's already started). If it's Tomcat, you can start it from the "Lomboz J2EE View" by right clicking the server item below "myweb" and select "Run Server" or "Debug Server".
        d) Test the web page, in my case I run against http://localhost:8080/myweb/

    Something like that should get you started, but it depends a bit on what kind of application server you've got. Good luck!

    Other Tutorials

    Visit and walk through the following great tutorial pages on the ObjectLearn website:

    Creating Your First J2EE Project - http://www.objectlearn.com/support/docs/creatingFirstJ2EEProject.jsp

    Adding a new J2EE Module to a Project - http://www.objectlearn.com/support/docs/addingNewContainer.jsp

    Your First Web Application - http://www.objectlearn.com/support/docs/firstWebApplication.jsp

    The same web site contains a number of other good tutorials, but the ones above are needed to get a grip of the IDE.