Monday, May 14, 2012

Create multiple Server Farms in one IIS ARR (Application Request Routing)

Most people use ARR to increase web applications' scalability and reliability by leveraging rule-based routing, load balancing, shared hosting and disk caching, etc. In my case, I found it a very good solution for medium sized corporations to expose their data via RESTful, OData or SOAP based web services if they already have the infrastructure in place and do not intend to go Cloud at the moment.

In our business scenario, there are a few implicit requirements:
  1. We should be able to use existing infrastructure without adding new hardware/servers.
  2. We should be able to expose all services using only one endpoint, which is the gateway. Furthermore, we expose the services as standard HTTP port 80 while the actual ports for our internal services are not disclosed.
  3. We must have the least number of holes in firewall for all services, ideally the number would be one.
  4. We should have a consistent and clean URL pattern for all services. For example: http://greatplace.org/Services/thisService?param1=x and http://greatplace.org/Services/thatService/, etc.
  5. We may also use this gateway to expose other resources than web services, e.g. reports.
  6. The server farms can support different authentications, e.g. reports farm does not allow anonymous access.
  7. The solution must be reliable and easy to maintain though load balancing and redundancy are not big concerns at present.
A simple practical design is like this:

For big enterprises, multiple ARRs can be utilised for routing specific types of requests to different server farms, for example one ARR for reporting server farm and one for general web services, etc. But what I am sharing here is how to use one ARR to dispatch requests to multiple server farms based on rule-based routing.

So first we create two server farms, add servers (and port mappings if any) in each farm:
Then we add the URL Rewrite rules

For the Reports Farm, we would like the original URL http://internal.server.ip:port/Reports/Pages/Report.aspx?ItemPath=SomePath to be routed to the servers in the reports farm with the exposed URL being http://external.server.ip/Reports/Pages/Report.aspx?ItemPath=SomePath, so the rule is like:

For the Services Farm, we would like the original URL, e.g. http://internal.server.ip:port/Services/Service1/Odata.svc/Products(5) to be routed to the servers in the services farm with the exposed URL being http://external.server.ip/Service1/Odata.svc/Products(5), so the rule is like:

And there we go. We can then configure the load balance and caching stuff as usual. It's awesome that ARR provides such great flexibility and capability in IIS.

Ref:
  1. ARR
  2. Using the URL Rewrite Module

Tuesday, July 26, 2011

Dynamic Dependency Map using ExtJS 3 and WCF RESTful

Fleetingly time passed. When I was cleaning my archive recently, an interesting project I did last year just reminded me of that. Anyway, that project was aimed to create a dynamic map to maintain and show the dependencies of our applications. Here is a short demo:
Design diagram:
Screenshots if vedio is too slow:

Thursday, July 7, 2011

Extend jQuery.highlight to highlight regular expressions

The jquery.highlight is a succinct and easy-to-use jQuery plugin to highlight texts. The current version can only highlight literary words case insensitively, but I extended it to highlight regular expressions so that you can highlight things like "foo*bar" as well. jquery.myhighlight-3.js:
/*

highlight v3 - Modified by Marshal (beatgates@gmail.com) to add regexp highlight, 2011-6-24

Highlights arbitrary terms.

<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>

MIT license.

Johann Burkard
<http://johannburkard.de>
<mailto:jb@eaio.com>

*/

jQuery.fn.highlight = function(pattern) {
    var regex = typeof(pattern) === "string" ? new RegExp(pattern, "i") : pattern; // assume very LOOSELY pattern is regexp if not string
    function innerHighlight(node, pattern) {
        var skip = 0;
        if (node.nodeType === 3) { // 3 - Text node
            var pos = node.data.search(regex);
            if (pos >= 0 && node.data.length > 0) { // .* matching "" causes infinite loop
                var match = node.data.match(regex); // get the match(es), but we would only handle the 1st one, hence /g is not recommended
                var spanNode = document.createElement('span');
                spanNode.className = 'highlight'; // set css
                var middleBit = node.splitText(pos); // split to 2 nodes, node contains the pre-pos text, middleBit has the post-pos
                var endBit = middleBit.splitText(match[0].length); // similarly split middleBit to 2 nodes
                var middleClone = middleBit.cloneNode(true);
                spanNode.appendChild(middleClone);
                // parentNode ie. node, now has 3 nodes by 2 splitText()s, replace the middle with the highlighted spanNode:
                middleBit.parentNode.replaceChild(spanNode, middleBit); 
                skip = 1; // skip this middleBit, but still need to check endBit
            }
        } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { // 1 - Element node
            for (var i = 0; i < node.childNodes.length; i++) { // highlight all children
                i += innerHighlight(node.childNodes[i], pattern); // skip highlighted ones
            }
        }
        return skip;
    }
    
    return this.each(function() {
        innerHighlight(this, pattern);
    });
};

jQuery.fn.removeHighlight = function() {
    return this.find("span.highlight").each(function() {
        this.parentNode.firstChild.nodeName;
        with (this.parentNode) {
            replaceChild(this.firstChild, this);
            normalize();
        }
    }).end();
};
Here is a test file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<script type="text/javascript" src="lib/jquery-1.6.js"></script>
<script type="text/javascript" src="lib/jquery.myhighlight-3.js"></script>
<style>
.highlight {
    background-color: yellow;
}
</style>
</head>
<body>

<div id="row">
    <div>
        <a class="fullname" href="#"><span class="lastname">Goog</span>, <span class="firstname">Billy</span></a>
        <div id="remove">ServIcE: (click me to remove all highlight)</div>
        <div><span class="section">Pig &amp; Sheep Serv</span>, <span class="department">Cow Administration &amp; Management</span></div>
        <div class="org">Trade Administration, Administration Services</div>
        <div id="email">
            <a class="email" href="mailto:a@abc.com">a@abc.com</a>
        </div>
    </div>
</div>

<script type="text/javascript">
    $(function() {
        $("#row").highlight("service");
        $(".lastname").highlight("o");
        $(".section").highlight(/g.*ep/i);
        $(".department").highlight(/.*/i);
        $(".org").highlight(/adm.*(?=, )/i);
        $("#email").highlight(/a.c\./);
        $("#remove").click(function() {$("#row").removeHighlight();});
    });
</script>

</body>
</html>
And the result:

Here are the links for the source code:
  1. jquery.myhighlight-3.js
  2. jquery.myhighlight-3.min.js

Wednesday, May 25, 2011

Type-as-you-go Ajax People Search Web Part using SharePoint 2007 Search Services - Part II

After prototyped and compared both solutions, I found that Content Editor Web Part(CEWP) + Javascript seems more suitable for our needs. It's not only more lightweight, interactive and flexible, but also less administration overhead. The product looks like this:

Here is the design diagram:

Pure Javascript and HTML stuff wrapped in a SharePoint CEWP. The core component here is MyXslt transformer, a small Javascript library I released to transform the search service result (XML) into HTML elements. Below is a list of libraries I used:

Javascript libraries

jQuery1.5+
MyXsltXSLT transformer that transforms the search service result XML directly into HTML elements
jQuery UIUI for dialogs
QTipjQuery plugin for tip style people's details
HighlightjQuery plugin for keyword highlighting
MyImgScalejQuery plugin for Image scaling

The solution layout (for commercial reasons, the full source code is not published at this stage):
PeopleSearch
    +---.hg
    +---clientSideSolution
    |   +---cewp
    |   +---release
    |   +---standalone
    |   +---unitTest
    |   \---_layouts
    |       \---1033
    |           +---images
    |           +---js
    |           +---styles
    |           \---xslt
    +---PeopleSearchWebpart
    +---data
    +---script
    +---UnitTest
    +---UserControlTest
    +---WebTest
    +---LoadTest
    \---TestResults

Another thing worth mention is that the load testing results (see above, using Visual Studio 2010's built-in load testing tool) showed the application was very responsive too.

Tuesday, May 24, 2011

Type-as-you-go Ajax People Search Web Part using SharePoint 2007 Search Services - Part I

In my organization, the SharePoint 2007's built-in People Search web part does not meet our needs:
  • Very limited in customizing both the query and the presentation of the results
  • No wild card search out of the box
  • Not very interactive/attractive: no type-as-you-go, no ajax, slow response, etc.
To overcome those limitations and create a more user-friendly and powerful people search solution, there are generally two options: 1) build your own web part (business logic at server-side) or 2) use the shipped Content Editor Web Part with your customized content (processing at client-side). Here are the pros and cons of the two solutions:

Comparison between Web Part Solutions

Server-side Processing Client-side Processing
Pros
  1. Easier server-side code development with C#, eg. SharePoint search API, XML query packet handling, etc
  2. Easier testing and debuging with Visual Studio
  3. Logging and exception handling ability

  1. Assembly free, easier to deploy and administrate
  2. Processing in client, less load/performance impacts
  3. Built-in Ajax
Cons
  1. More work in deployment and administration
  2. Harder Ajax, have to mix ASP.NET user control and jQuery
  3. Processing in server, potential load/performance impacts

  1. Javascripts harder to test, debug and maintain
  2. Harder logging and exception handling

The traditional server-side SharePoint 2007 web part development is actually easy, if you are using the WSPBuilder with Visual Studio 2010. There are quite a few good articles about how to do that, eg. Developing SharePoint WebParts using User Controls and Web Applications by ggalipeau. Some of the contents are a little outdated, but the steps are still applicable. The new layout in Visual Studio now looks like this:

Be careful there are a few catches though, especially the WSPBuilder 1.0.6 generated codes contain some defects. Eg. in xxxReceiver.cs, you have to comment out all contents in FeatureActivated() as SPFeatureReceiver doesn't have FeatureActivated() method. The first line of FeatureDeactivating() this.FeatureDeactivating(properties); must also be removed as this recursive calls will bring you a stack overflow.

Other than those small bugs, WSPBuilder really helps you a lot in focusing on your business logic, and takes off the pain of generating .wsp web part, packaging and deployment, etc. However, as indicated in the above comparison table, the ASP.NET user control was not born with Ajax functionality and the prerequisite of a local SharePoint instance for integration with the development tools is too heavy and dulls the web part development and testing. Well, if you were a Java developer, probably this would remind you of those similar/painful EJB 2.1 days :-)

Tuesday, May 3, 2011

MyXslt v0.2 released

In my previous blog, I provided a temporary fix for jQuery XSLT plugin. However, it is neither complete nor correct due to the plugin's lacking of callback interface during its asynchronous XSLT processing. From David Flanagan's excellent book "JavaScript: The Definitive Guide (5th Edition)", I found a pure Javascript solution and decided to enhance it to suit my needs.

The potential usage of this small Javascript library is calling any web services that return XML then transforming it directly to HTML elements by XSLT. In my case, I created a customized People Search web part in SharePoint 2007 as a replacement for the built-in one, and made complicated search easier, more flexible and interactive. This web part doesn't need any server side programming as it's pure HTML + Javascript contained in SharePoint's provided Content Editor Web Part. For more information about this web part, please stay tuned.

More information here, downloads and source code.

Sunday, February 20, 2011

Fix jQuery plugin xslt.js not working in Firefox 3.x

First of all, thanks Johann for his excellent jQuery XSLT plugin here.

Unfortunately, this one doesn't work with jQuery 1.5 in Firefox 3. One of the reasons is the readyState was changed to 0 after $.ajax loaded the xml and xslt files. Below is my fix by replacing line 84-118 in jquery.xslt.js with the following:
var change = function() {
    if (xm.readyState == xs.readyState && !transformed) {
        var processor = new XSLTProcessor();
        if ($.isFunction(processor.transformDocument)) {
            // obsolete Mozilla interface
            resultDoc = document.implementation.createDocument("", "", null);
            processor.transformDocument(xm.responseXML, xs.responseXML, resultDoc, null);
            target.html(new XMLSerializer().serializeToString(resultDoc));
        }
        else {
            processor.importStylesheet(xs.responseXML);
            resultDoc = processor.transformToFragment(xm.responseXML, document);
            target.empty().append(resultDoc);
        }
        transformed = true;
    }
};

if (str.test(xml)) {
    xm.responseXML = new DOMParser().parseFromString(xml, "text/xml");
}
else {
    xm = $.ajax({ dataType: "xml", url: xml});
}

if (str.test(xslt)) {
    xs.responseXML = new DOMParser().parseFromString(xslt, "text/xml");
}
else {
    xs = $.ajax({ dataType: "xml", url: xslt});
}
change();
return this;
Will contact the author and update the post later...
(Update: the author hasn't responded, but I created a better solution here.)

Ref: xslt.js version 3.2

Thursday, February 17, 2011

jQuery "Object Expected" error in IE

I encountered this problem when prototyping some SharePoint webparts. It actually works well in Firefox and Chrome. Here's the snippet for illustration:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<script type="text/javascript" src="../js/jquery-1.5.min.js"></script>

<script type="text/javascript">

    $(function() { // <=> $(document).ready(function() {
...
By adding one line of code in line 7 before $(function(),
alert(typeof $);
it prompts "undefined", meaning jQuery is not loaded by IE. The solution is quite simple, IE->Tools->Internet Options->Security, by default it is High, either change it to Medium, or use custom level then enable Scripting->Active scripting (IE8). Now it alerts "function" and jQuery is working nifty then.

Ref: jQuery $(document).ready() failing in IE6

Monday, December 13, 2010

JB Renamer v0.3.36 released

JB Renamer (Java Batch Renamer) is my open source (GNU LGPL v3) tool to batch rename files.

Main features:
--------------
1. Add sequence to file name
2. Re-sequence the file name, eg. change the digits of existing sequences
3. Replace part of the file name either literally or by regular expression (Java style)
4. Move part of the file name
5. Test renaming without the real name change
6. Unlimited undo/redo
7. Save file names in a directory to a text file
8. Pre-defined renaming: load a file list to rename

System Requirements:
--------------------
Java 1.6


Download it here.
Website and Source code

Monday, March 8, 2010

GridView in GridView (2) - LINQ Eager Loading

In this blog, I provided a solution for nested GridViews. The problem with that, like other declarative data source bindings, is you have little control of the data source itself hence the database operations might not be optimal.
For example, as we have to use 2 separate data sources, one for each GridView, the ASP.NET engine will first query session data for the outer grid then query transaction logs for the inner grid.However, this actually can be done in one go. Following the pattern I mentioned in this blog to manually control data source object and using eager loading for LINQ, we can have far few SQL queries to the database hence improve the performance.
In .aspx, remove the data sources. As a result of not setting the DataSourceID property of the GridView, we have to add the OnPageIndexChanging event handler for paging.
<asp:GridView ID="gvSession" CssClass="datagrid" runat="server" 
  AllowPaging="True" PageSize="6" AutoGenerateColumns="False" 
  OnPageIndexChanging="gvSession_PageIndexChanging"
  OnRowDataBound="gvSession_RowDataBound" Width="680px">
  <Columns>
    <asp:BoundField DataField="StaffName" HeaderText="Staff Name" />
    <asp:BoundField DataField="SessionId" HeaderText="Session Id" />
    <asp:BoundField DataField="SessionStartTime" HeaderText="Session Start Time" />
    <asp:BoundField DataField="SessionDuration" HeaderText="Session Duration" />
    <asp:TemplateField HeaderText="Tx Logs">
      <ItemTemplate >
        <asp:GridView ID="gvTxLog" runat="server" AutoGenerateColumns="False" Width="100%">
          <Columns>
            <asp:BoundField DataField="Text" HeaderText="Tx Type" ItemStyle-Width="70%" />
            <asp:BoundField DataField="Value" HeaderText="Duration" />
          </Columns>
        </asp:GridView>
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>
In .aspx.cs, bind outer GridView in Page_load, set the inner GridView's data source in outer grid's OnRowDataBound event handler, and implement the OnPageIndexChanging event handler:
ReportDAO _reportDao;

protected void Page_Load(object sender, EventArgs e)
{
    ...
    if (reportTabs.ActiveTab == txViewTab)
    {
        _reportDao = _reportDao ?? new ReportDAO();
        gvSession.DataSource = _reportDao.GetSessions(txtStartDate.Text, txtEndDate.Text,
            int.Parse(ddlStaffs.SelectedValue));
        gvSession.DataBind();
    }
    ...
}

protected void gvSession_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        GridView subView = (GridView)e.Row.FindControl("gvTxLog");
        subView.DataSource = _reportDao._subTables[
            gvSession.PageIndex * gvSession.PageSize + e.Row.RowIndex];
        subView.DataBind();
    }
}

protected void gvSession_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    gvSession.PageIndex = e.NewPageIndex;
    gvSession.DataBind();
}
The Data Access Object is rather simple. Using ListItem for inner GridView is because I only need 2 values: transaction type name and duration, corresponding to Text and Value DataField of inner GridView's BoundField in .aspx. You can change that to DataTable if more columns required.
public List<List<ListItem>> _subTables = new List<List<ListItem>>();
...
[DataObjectMethod(DataObjectMethodType.Select, false)]
public DataTable GetSessions(string start, string end, int staffId)
{
    if (staffId < 0) // None
        return null;

    ToDate(start, end, out _startDate, out _endDate);
    using (ImportTxDataContext dataContext = new ImportTxDataContext())
    {
        dataContext.EagerLoad();

        // staffId == 0 means all sessions, -1 means none
        var sessions = (from session in dataContext.Sessions
                        where session.StartTime > _startDate && session.EndTime < _endDate &&
                            (session.StaffId == staffId || staffId == 0)
                        select session).ToList();

        DataTable table = new DataTable();
        table.Columns.Add(new DataColumn("StaffName", typeof(string)));
        table.Columns.Add(new DataColumn("SessionId", typeof(int)));
        table.Columns.Add(new DataColumn("SessionStartTime", typeof(string)));
        table.Columns.Add(new DataColumn("SessionDuration", typeof(string)));
        
        foreach (Session session in sessions) // each row
        {
            DataRow row = table.NewRow();
            row["StaffName"] = session.Staff.Name;
            row["SessionId"] = session.Id;
            row["SessionStartTime"] = session.StartTime.ToString(Util.DATE_TIME_FORMAT);
            row["SessionDuration"] = Util.DurationAsString(session.EndTime - session.StartTime);
            table.Rows.Add(row);
            
            List<ListItem> subTable = new List<ListItem>();
            foreach (TxLog log in session.TxLogs)
            {
                subTable.Add(new ListItem(
                    log.TxType.Name, Util.DurationAsString(log.Duration, false)));
            }
            _subTables.Add(subTable);
        }

        return table;
    }
}
EagerLoad() is an extension method for the trick of LINQ eager loading:
public static void EagerLoad(this ImportTxDataContext dataContext)
{
    DataLoadOptions options = new DataLoadOptions();
    options.LoadWith<Session>(r => r.Staff);
    options.LoadWith<Session>(r => r.TxLogs);
    options.LoadWith<ImportTx.DAO.TxLog>(r => r.TxType);
    dataContext.LoadOptions = options;
}
The LINQ generated SQL is like this:
exec sp_executesql N'SELECT [t0].[Id], [t0].[StaffId], [t0].[LocationId], [t0].[StartTime], [t0].[EndTime], [t2].[SessionId], [t2].[TxTypeId], [t2].[Duration], [t3].[Id] AS [Id2], [t3].[Name], [t3].[Description], [t3].[Active], (
    SELECT COUNT(*)
    FROM [dbo].[TxLog] AS [t4]
    INNER JOIN [dbo].[TxType] AS [t5] ON [t5].[Id] = [t4].[TxTypeId]
    WHERE [t4].[SessionId] = [t0].[Id]
    ) AS [value], [t1].[Id] AS [Id3], [t1].[Name] AS [Name2], [t1].[NetworkName], [t1].[ModifiedBy], [t1].[LastModified], [t1].[Active] AS [Active2], [t1].[IsAdmin]
FROM [dbo].[Session] AS [t0]
INNER JOIN [dbo].[Staff] AS [t1] ON [t1].[Id] = [t0].[StaffId]
LEFT OUTER JOIN ([dbo].[TxLog] AS [t2]
    INNER JOIN [dbo].[TxType] AS [t3] ON [t3].[Id] = [t2].[TxTypeId]) ON [t2].[SessionId] = [t0].[Id]
WHERE ([t0].[StartTime] > @p0) AND ([t0].[EndTime] < @p1)
ORDER BY [t0].[Id], [t1].[Id], [t2].[TxTypeId]',N'@p0 datetime,@p1 datetime',@p0='2009-03-01 00:00:00',@p1='2010-03-09 00:00:00'
Without eager loading, it'll be 4 times more queires.
Ref:

Sunday, March 7, 2010

GridView in GridView

There are many RAD controls on the market that can do very fancy presentation stuff. But even with the plain old ASP.NET controls, you can still exploit their interesting potentials. Embeding a GridView inside another GridView is not really difficult, but those examples I searched from Internet couldn't do me the favor. So here comes my solution.

The outer grid represents a session table of a user and each session contains a few transaction logs associated with the outer sessionId. The .aspx is straightforward: a TemplateField column is used to embed the inner GridView.
<asp:GridView ID="gvSession" CssClass="datagrid" runat="server" 
  AllowPaging="True" PageSize="6" AutoGenerateColumns="False" 
  DataSourceID="ldsSession" OnRowDataBound="gvSession_RowDataBound" Width="680px">
  <Columns>
    <asp:BoundField DataField="StaffName" HeaderText="Staff Name" />
    <asp:BoundField DataField="SessionId" HeaderText="Session Id" />
    <asp:BoundField DataField="SessionStartTime" HeaderText="Session Start Time" />
    <asp:BoundField DataField="SessionDuration" HeaderText="Session Duration" />
    <asp:TemplateField HeaderText="Tx Logs">
      <ItemTemplate >
        <asp:GridView ID="gvTxLog" runat="server" DataSourceID="ldsTxLog" AutoGenerateColumns="False" Width="100%">
          <Columns>
            <asp:BoundField DataField="TxType" HeaderText="Tx Type" ItemStyle-Width="70%" />
            <asp:BoundField DataField="Duration" HeaderText="Duration" />
          </Columns>
        </asp:GridView>
        <asp:ObjectDataSource ID="ldsTxLog" runat="server" SelectMethod="GetTxLogs" TypeName="DAO.ReportDAO">
          <SelectParameters>
            <asp:ControlParameter ControlID="txtStartDate" Name="start" PropertyName="Text" Type="String" />
            <asp:ControlParameter ControlID="txtEndDate" Name="end" PropertyName="Text" Type="String" />
            <asp:ControlParameter ControlID="ddlStaffs" Name="staffId" PropertyName="SelectedValue" Type="Int32" />
            <asp:Parameter Name="sessionId" Type="Int32" />
          </SelectParameters>
        </asp:ObjectDataSource>
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ldsSession" runat="server" SelectMethod="GetSessions" TypeName="DAO.ReportDAO">
  <SelectParameters>
    <asp:ControlParameter ControlID="txtStartDate" Name="start" PropertyName="Text" />
    <asp:ControlParameter ControlID="txtEndDate" Name="end" PropertyName="Text" />
    <asp:ControlParameter ControlID="ddlStaffs" Name="staffId" PropertyName="SelectedValue" Type="Int32" />
  </SelectParameters>
</asp:ObjectDataSource>

To make it simple, the outer grid just uses declarative data binding. While the inner grid, although appears to be the same, has to be initialized manually because, like I mentioned earlier, it relies on the sessionId from the outer grid after outer's data binding. So here is the trick:
protected void gvSession_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType .DataRow)
    {
        ObjectDataSource s = (ObjectDataSource )e.Row.FindControl("ldsTxLog" );
        s.SelectParameters["sessionId" ].DefaultValue = e.Row.Cells[1].Text;
        GridView subView = (GridView )e.Row.FindControl("gvTxLog" );
        subView.DataBind();
    }
}

This resolution is not perfect though, because you have to use 2 separate data sources, one for each GridView. And that brings the N+1 problem. But there is a better solution coming...

Control and access the binding ObjectDataSource instance programatically

The declarative data source provides a simple and transparent way of data binding for GridView. However, sometimes we need finer control of the ObjectDataSource instance that a GridView binds to. For example, it's usually easier to generate the footer in the data source rather than doing it in data bound event, like I did in this example. By accessing the ObjectDataSource instance directly, we can control when and how the binding happens as we desire.
In .aspx, we are not specifing the ObjectDataSource in GridView
<ajaxTool:TabPanel runat="server" HeaderText="Transaction Time" ID="serviceTab">
<ContentTemplate>
<asp:GridView ID="gvServiceTime" CssClass="datagrid" runat="server" ShowFooter="True"
          OnRowDataBound="gvServiceTime_RowDataBound" FooterStyle-CssClass="tfoot">
</asp:GridView>
</ContentTemplate>
</ajaxTool:TabPanel>
In .aspx.cs Page_Load, instantiate the DAO object, make the query and then assign it to GridView's DataSource. Then use this DAO object in the OnRowDataBound event to feed the footer. Prefer OnRowDataBound over OnDataBound because I want to customize the data row display and highlight some cells.
public partial class Report : System.Web.UI.Page
{
    ReportDAO _reportDao;

    protected void Page_Load(object sender, EventArgs e)
    {
        ...
        if (reportTabs.ActiveTab == serviceTab)
        {
            _reportDao = _reportDao ?? new ReportDAO();
            gvServiceTime.DataSource = _reportDao.GetServiceTimes(txtStartDate.Text, txtEndDate.Text);
            gvServiceTime.DataBind();
        }
        ...
    }

    /**
     * 1. Format each cell to mm:ss using Util.DurationAsString() rather than raw seconds
     * 2. Generate gvServiceTime footer
     */
    protected void gvServiceTime_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        e.Row.HorizontalAlign = HorizontalAlign.Right;
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            int max = 0, maxsIndex = 0;
            for (int i = 1; i < e.Row.Cells.Count; i++) // i from 1 to skip the 1st col
            {   
                int duration = (int)_reportDao._report.Rows[e.Row.DataItemIndex][i];
                if (max < duration)
                {
                    max = duration;
                    maxsIndex = i;
                }
                e.Row.Cells[i].Text = Util.DurationAsString(duration);
            }
            if (maxsIndex > 0) // highlight the max cell in the row
                e.Row.Cells[maxsIndex].BackColor = System.Drawing.Color.Beige;
        }
        else if (e.Row.RowType == DataControlRowType.Footer)
        {
            e.Row.Cells[0].Text = "Average";
            for (int i = 1; i < _reportDao._footer.Length + 1; i++)
            {
                e.Row.Cells[i].Text = Util.DurationAsString(_reportDao._footer[i - 1]);
            }
        }
    }

    ...
}
This article also shows another way of adding dynamic columns in a table by using DataTable, which is far more flexible than arrays. Here is some snippet from DAO:
DataTable table = new DataTable();
table.Columns.Add(new DataColumn("TransactionType", typeof(string)));
foreach (string staff in staffs)
{
    table.Columns.Add(new DataColumn(staff, typeof(int)));
}
table.Columns.Add(new DataColumn("Average", typeof(int)));

foreach (TransactionType transactionType in _transactionTypes) // each row
{
    DataRow row = table.NewRow();
    row["TransactionType"] = transactionType.Name;
    int total = 0;
    int n = 0;
    foreach (string staff in staffs) // each col
    {
        ServiceTime serviceTime = counts[transactionType.Name]
            .Where(r => r.Staff == staff).SingleOrDefault();
        row[staff] = serviceTime == null ? 0 : serviceTime.Count;
        total += serviceTime == null ? 0 : serviceTime.Count;
        n += serviceTime == null ? 0 : 1;
    }
    row["Average"] = (int)(total / (n == 0 ? 1 : n)); // last col is average
    table.Rows.Add(row);
}

Thursday, March 4, 2010

Dynamic column in GridView binding an array

Recently I've done some manual reporting using ASP.NET. Well, orginally I developed it easily using SQL Server 2008 Reporting Service, which is not in our production environment yet, but the client was keen to have the application deployed.
Dynamic columns is actually easy in GridView because you can bind the GridView to whatever data source with just a little more work than the simple declarative binding. For example, you can bind it to a DataTable like this. In my case, the report has fixed number of rows each of which represents a transaction type, while the columns are dynamic depending on another database table for zones. To feed this report grid, an OjbectDataSource is created which returns an array of ReportRow.
public class ReportRow
{
    public string CallType { get; set; }
    public int[] Counts { get; set; } // Counts array for each location + the last is total
}
In .aspx file, we must set AutoGenerateColumns to false. The gvImportCounts binds to an ObjectDataSource with 2 parameters for the reporting period. The OnDataBound event is to add a footer in the report.
<ajaxTool:TabPanel runat="server" ID="importTab">
  <HeaderTemplate>Import Counts</HeaderTemplate>
  <ContentTemplate>
    <asp:GridView ID="gvImportCounts" CssClass="datagrid" runat="server" AutoGenerateColumns="False"
         DataSourceID="odsImportCounts" ShowFooter="True" Width="658px"
         OnDataBound="gvImportCounts_DataBound">
      <FooterStyle CssClass="tfoot" />
    </asp:GridView>

    <asp:ObjectDataSource ID="odsImportCounts" runat="server" SelectMethod="GetImportCounts"
        TypeName="DAO.ReportDAO">
      <SelectParameters>
        <asp:ControlParameter ControlID="txtStartDate" Name="start" PropertyName="Text" />
        <asp:ControlParameter ControlID="txtEndDate" Name="end" PropertyName="Text" />
      </SelectParameters>
    </asp:ObjectDataSource>
  </ContentTemplate>
</ajaxTool:TabPanel>
The difficulty of binding to array elements, like Frdrik pointed out in his blog (Ref 1), is that the ASP.NET's standard BoundField does not provide this functionality. So one of the elegant ways is to extend it by ourselves.
In aspx.cs, add this subclass that enables us to evaluate array elements:
/**
* Extend the BoundField to bind column to composite field even array element
*/
public class CompositeBoundField : BoundField
{
    protected override object GetValue(Control controlContainer)
    {
        object item = DataBinder.GetDataItem(controlContainer);
        return DataBinder.Eval(item, this.DataField);
    }
}
The rest important code in aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack) // first load
    {   // Dynamically generate columns for Import report:
        BoundField bf = new BoundField();
        bf.DataField = "Import";
        bf.HeaderText = "Transaction Type";
        gvImportCounts.Columns.Add(bf);

        var zones = ReportDAO._zones; // get zones from DAO

        for (int i = 0; i &lt; zones.Count; i++)
        {
            bf = new CompositeBoundField();
            //Initalize the DataField and HeaderText field value:
            bf.DataField = "Counts[" + i + "]"; // bind to array element
            bf.HeaderText = zones[i].Name;

            //Add the newly created bound field to the GridView:
            gvImportCounts.Columns.Add(bf);
        }
        // Add the Total column
        bf = new CompositeBoundField();
        bf.DataField = "Counts[" + zones.Count + "]";
        bf.HeaderText = "Total";
        gvImportCounts.Columns.Add(bf);
    }
}

/**
* Generate gvImportCounts footer
*/
protected void gvImportCounts_DataBound(object sender, EventArgs e)
{
    GridView grid = (GridView)sender;
    GridViewRow footer = grid.FooterRow;
    if (footer != null) // footer defined, so fill it
    {
        footer.Cells[0].Text = "Total";
        for (int i = 1; i &lt; grid.Columns.Count; i++)
        {
            int total = 0;
            foreach (GridViewRow row in grid.Rows)
            {
                if (row.RowType == DataControlRowType.DataRow)
                    total += int.Parse(row.Cells[i].Text);
            }
            footer.Cells[i].Text = total.ToString();
        }
    }
}
Here is the result: Ref:
  1. Fixing BoundField Support for Composite Objects
  2. GridView and dynamic data sources

Wednesday, February 17, 2010

Deletes files older than a number of days in a specified directory

It's not quite easy to achieve that functionality with Windows batch file. However with built-in command wscript.exe or cscript.exe, we can write some jscript:
/**
* Deletes files older than a number of days in a specified directory
*/

function deleteFilesInFolder(folder) {
  WScript.echo(folder);
  for (var it = new Enumerator(folder.Files); !it.atEnd(); it.moveNext()) {
    var file = it.item();
    if (file.DateLastModified < nDaysAgo) {
      if (testing)
        WScript.echo("  " + file.name);
      else
        file.Delete(true);
    }
  }

  if (recursive)
    for (var it = new Enumerator(folder.SubFolders); !it.atEnd(); it.moveNext()) {
      deleteFilesInFolder(it.item());
    }
}

var usage = "Usage: cscript|wscript delOldFiles.js [-t] [-r] [-h|-?] [dir] [#Days]\n" +
    "-h|-?: This usage message\n" +
    "   -t: Test mode, no real deletion, just show files to be deleted\n" +
    "   -r: Recursive, file in subdirectories will be deleted as well\n" +
    "  dir: The specified directory. Default is current dir\n" +
    "#Days: Delete files older than this # of days. Default is 7";

// arguments handling
var testing = false;
var recursive = false;
var dir;
var nDays;

for (i = 0; i < WScript.arguments.length; i++) {
  arg = WScript.arguments(i);

  if (arg == "-h" || arg == "-?") {
    WScript.echo(usage);
    WScript.quit();
  }
  else if (arg == "-t")
    testing = true;
  else if (arg == "-r")
    recursive = true;
  else if (isNaN(parseInt(arg)))
    dir = arg;
  else 
    nDays = arg;
}
nDays = nDays == null ? 7 : nDays;
dir = dir == null ? "." : dir;
//WScript.echo("-t=" + testing + ", -r=" + recursive + ", dir=" + dir + ", day=" + nDays);

// Compute date
var nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - nDays);

// Delete files in the dir
deleteFilesInFolder(WScript.CreateObject("Scripting.FileSystemObject").GetFolder(dir));
Ref: Vincent Robert's example

Thursday, December 17, 2009

Sculpture generated Silverlight on IIS and Async_Exception

Sculpture is a .NET open source MDD (Model-Driven Development) code generation framework. With the same model, it can generate various UIs such as Silverlight, ASP.NET MVC and WPF for you. This article talks about tricks of Silverlight, not necessarily Sculpture related, but if people encounter the same problem that I found using Sculpture, they might found it useful.

It is quite easy to run the Silverlight application from within Visual Studio 2008, being it created by Sculpture or yourself. However, to host that application on IIS 6, there are some configuration work to do. Here are the brief steps:

1. Start->Run->inetmgr
2. Right click Default Web Site->Properties->Home Directory->Configuration->find .aspx->Edit->Executable, change to C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll
3. Under Internet Information Services, right click the Server node->Properties->MIME Types->New, add:
Extension: .xap, MIME type: application/x-silverlight
Extension: .xaml, MIME type: application/xaml+xml
4. Restart IIS service


For details, see reference 1.

Now your Silverlight baby should be beautifully running and it's time for dealing with the unhandled exceptions if you use WCF in it. With normal silverlight installed, the typical error is like:
Message: Unhandled Error in Silverlight Application [Async_ExceptionOccurred]
Arguments:
Debugging resource strings are unavailable. Often the key and arguments provide sufficient information to diagnose the problem. See http://go.microsoft.com/fwlink/?linkid=106663&Version=3.0.40818.0&File=System.dll&Key=Async_ExceptionOccurred   at System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary()
at App.Infrastructure.ServiceContracts.DatabaseServerToExcludeCrudInfoCompletedEventArgs.get_Result()
at App.Shell.ServiceReferences.DatabaseServerToExcludeCrudService.DatabaseServerToExcludeCrudService_GetAllCompleted(Object sender, DatabaseServerToExcludeCrudInfoCompletedEventArgs e)
at App.Shell.ServiceReferences.DatabaseServerToExcludeCrudService.OnGetAllCompleted(Object state)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
Line: 1
Char: 1
Code: 0
URI: http://192.1.1.50/App/Silverlight.js
With silverlight_developer version installed, you could see a bit more:
Message: Unhandled Error in Silverlight Application An exception occurred during the operation, making the result invalid.  Check InnerException for exception details.   at System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary()
at App.Infrastructure.ServiceContracts.DatabaseServerToExcludeCrudInfoCompletedEventArgs.get_Result()
at App.Shell.ServiceReferences.DatabaseServerToExcludeCrudService.DatabaseServerToExcludeCrudService_GetAllCompleted(Object sender, DatabaseServerToExcludeCrudInfoCompletedEventArgs e)
at App.Shell.ServiceReferences.DatabaseServerToExcludeCrudService.OnGetAllCompleted(Object state)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)...

There are at least 2 things you might have to check/fix:

1. Do you have a clientaccesspolicy.xml or crossdomain.xml under the root of your application domain where the service is hosted? Either file will set rules for your Silverlight control to access a WCF/web service in another domain.

An example of allowing any client is like this:
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>
For more details of cross-domain access, see ref 3.

2. Have you set the Endpoint Address of WCF service in Silverlight client correctly?

There are several ways of doing that. For example, generate/edit ServiceReferences.ClientConfig. From Visual Studio, you can right click your service node's Service References->Add Service Reference... to generate the client configuration.

For Sculpture generated code, you can override the GetEndPointAddress() method in ServiceConfiguration.cs and set the correct URL.

There seemed to be no big difference by configuration or programming as finally they will be compiled into Silverlight binary and neither has the flexibility of modification on the fly.

Ref:
1. Developing and deploying a Visual WebGui Silverlight application
2. Configuring IIS for Silverlight Applications
3. Using Silverlight 2.0 clientaccesspolicy.xml vs. crossdomain.xml for Web-Service cross-domain access
4. Network Security Access Restrictions in Silverlight
5. Making a Service Available Across Domain Boundaries

Monday, November 23, 2009

Install Silverlight 3 Tools Offline

It seems quite stupid that the Silverlight 3 Tools installation will always look to download from internet even you have installed the Silverlight SDK by yourself. More absurdly, the download and installation will fail even you have connection to internet but by using a proxy, which is common in an office. The trick here is to extract them manually into the same folder:
  1. In cmd window, uncompress Silverlight SDK: silverlight_sdk.exe /x
  2. Uncompress Silverlight tools to the above chosen directory: Silverlight3_Tools.exe /x
  3. In the extracted folder, run VS_SilverlightTools_Setup.exe
  4. In the same folder, run VS90SP1-KB967143-enu.msp as well as VS90SP1-KB967144-enu.msp (ignore this failure)
The last 2 steps take a while, but eventually you should be able to open silverlight projects in Visual studio then.

Ref: Installing SilverLight 3 Visual Studio Tools Offline

Sunday, November 8, 2009

Simple way to invalidate SqlDataSource cache programmatically

EnableCaching and CacheDuration are the normal cache-related properties of the data source controls, but they don't give you the convenience to invalidate the cache on-demand. Some people use the SqlCacheDependency, where ASP.NET worker process will poll for changes in the SqlCacheTablesForChangeNotification when a change in monitored table triggers the "notification". This measure is somewhat clumsy in that:
  1. You have to use aspnet_regsql command line to enable notifications for the database;
  2. ASP.NET uses a polling mechanism, and you have to both write code and alter your web.config
There is a simpler way though: use CacheKeyDependency, where you make the data source cache dependent on another item in the data cache (CacheKeyDependency). Details follow:
  • Add CacheKeyDependency in aspx:
<asp:SqlDataSource ID="x" EnableCaching="True" CacheKeyDependency="MyCacheDependency" />
  • Add some code in aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{ // Or somewhere else before the DataBind() takes place
  if (!IsPostBack)
  {
      ...
      if (Cache["MyCacheDependency"] == null)
      {
        Cache["MyCacheDependency"] = DateTime.Now;
      }
  }
}
  • Where you make changes to the database table and want to invalidate cache so that next time a data binding will see the changes:
// Evict cache items with an update in dependent cache:
Cache["MyCacheDependency"] = DateTime.Now;
You can use any value instead of DateTime.Now as long as they are different to trigger the refresh.
Ref: 1. ASP.NET Caching: SQL Cache Dependency With SQL Server 2000
2. Accessing and Updating Data in ASP.NET 2.0: Declaratively Caching Data

Sunday, November 1, 2009

Prevent IIS Session Timeout in ASP .NET

There are scenarios that a user may want to keep a long session alive. For example, a help desk operator logs into a web application and takes phone calls and in between submits changes to the backend systems. The phone call may last over an hour and the operator may stay in one web page and need that session to be valid when she submits the changes.
In ASP.NET, there are several common simple solutions for that. One of them is to set the session timeout attribute (minutes) in web.config.
<sessionState mode ="InProc" timeout="xxx"/>
Some people are confused at the timeout setting in web.config and another in IIS and ask which overwrites which. A simple experiment shows that the setting in web.config always overwrites the setting in IIS.
However, this is not complete for ASP .NET 2.0+. Open IIS->Application Pools->Select the Application pool for your application->Properties->Performance, set the idle timeout for "Shutdown worker processes after being idle for xxx minutes".
Otherwise, the worker process is still stopped after 20 minutes(default) and your session state will be lost.
Another way, if we do not want to rely on the settings in IIS, is to keep the session alive by ourselves in the application. Some people use an invisible frame and set auto refresh in http header, but if you are using a master page, that behavior will be propagated to all ASP pages. With .NET 2.0+ and a little bit Ajax, we have a simpler solution - add a timer in UpdatePanel. The timer will trigger a partial postback periodically, thus prods the session to be alive.
<asp:Content ID="ct" ContentPlaceHolderID="cphMain" runat="Server">
  <asp:ScriptManager ID="scriptMgr" runat="server" />

  <%-- Your content here --%>

  <%-- Heartbeat every 15min to keep session alive --%>
  <asp:UpdatePanel ID="updatePanel" runat="server">
    <ContentTemplate>
      <asp:Timer ID="timerPing" runat="server" Interval="900000">
      </asp:Timer>
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>
Ref:
1. Managing Session TimeOut using Web.Config
2. Preventing Session Timeouts in C# ASP .NET

Sunday, September 6, 2009

Install Cygwin SSH on Win2K3

There has been an excellent post on how to install Cygwin SSH on Win2K3 . However, there are still some tricks to get that working properly. Luckily, we could alwasy refer to Window's EventViewer to get the details of what happened.
  • mkpasswd does not generate correct group number for domain users
mkpasswd -d <domain-name> -p /home -u <username> >> /etc/passwd
This will add one line of the domain user in /etc/passwd. But be careful the group number may not be correspondent with /etc/group. So the following error will likely be seen when ssh -v localhost:
sshd: PID 2584: fatal: initgroups: <username> : Invalid argument.
After manually correcting the group number in /etc/passwd, we should be able to use ssh locally.
  • hosts.allow - enable remote users
The default hosts.allow after ssh-host-config looks like:
ALL : localhost 127.0.0.1/32 : allow
ALL : PARANOID : deny
sshd: ALL
However, this won't get us through if we connect from another machine.
sshd: PID 3904: refused connect from 10.8.8.8.
We could modify hosts.allow and make it look like:
sshd: ALL : spawn (echo "Attempt from %h %a to %d at `date` by %u" | tee -a /var/log/sshd.log)
This will allow connections and add 1 line in sshd.log for each connection.
or simply:
sshd: ALL : allow
or more restrictive:
sshd: 10.8.0.0/255.255.0.0 : allow
  • /etc/hosts - if sshd cannot verify hostname
We still may see a warning "sshd: PID 3904: warning: /etc/hosts.allow, line 11: can't verify hostname: gethostbyname(<hostname>) failed."
This slows SSH client's connection. One work around is to add the host in /etc/hosts file which speeds connection up:
10.8.8.8     <hostname>

Monday, April 27, 2009

Run Glassfish v2 EJB3 example on JBoss 5

  • JBoss 5 with Derby as DataSource
There are some really good blogs about how to use derby as JBoss DataSource like Alistair Israel's, but still things to be noticed.
After copy ${JBOSS_HOME}/docs/examples/jca/derby-ds.xml to ${JBOSS_HOME}/server/default/deploy, several items should be adjusted according to the sample, like:
<jndi-name>jdbc/sample</jndi-name>
<connection-url>jdbc:derby://localhost:1527/sample</connection-url>
<driver-class>org.apache.derby.jdbc.ClientXADataSource</driver-class>
And the following to specify the database:
<mbean code="org.jboss.jdbc.DerbyDatabase" name="jboss:service=Derby">
<attribute name="Database">sample</attribute>
</mbean>
Then, Copy ${JBOSS_HOME}/docs/examples/varia/derby-plugin.jar and the ${DERBY_HOME}/lib/derby.jar and derbyclient.jar to ${JBOSS_HOME}/server/default/lib.

Next is workaround of JBoss JMX if using JBoss 5.01GA(fixed in 5.1.0GA), add -Djboss.platform.mbeanserver at the end of line in ${JBOSS_HOME}/bin/run.bat(or run.sh):
set JAVA_OPTS=...
  • JBossXBRuntimeException
You won't have it in this example, but say if you try to run the entity demo, you probably get:
DEPLOYMENTS IN ERROR:
Deployment "vfszip:.../server/default/deploy/lab-entity-demo4.jar/" is in error due to the following reason(s): org.jboss.xb.binding.JBossXBRuntimeException: Failed to resolve schema nsURI= location=persistence

This is due to the persistence.xml missing schema namespaces:
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="lab-entity-demo4">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>

To correct it, the following should be put in persistence element:
version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
  • org.jboss.deployers.spi.DeploymentException:
Required config property RequiredConfigPropertyMetaData@f1770a[name=destination descriptions=[DescriptionMetaData@7f0b4e[language=en]]] for messagingType 'javax.jms.MessageListener' not found in activation config [ActivationConfigProperty(destinationType=javax.jms.Queue)] ra=jboss.jca:service=RARDeployment,name='jms-ra.rar'

Need to add "destination" property in ActivationConfigProperty. See next step.
  • Modify MessageBean, add JBoss specific annotation:
import org.jboss.ejb3.annotation.Depends;

@MessageDriven(mappedName = "jms/NewsMsg", activationConfig = {
// JBoss by default uses AUTO_ACKNOWLEDGE:
// @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="jms/NewsMsg")
})
@Depends ("jboss.mq.destination:service=Queue,name=ejbfoo")
public class NewsMessageBean implements MessageListener {
...

  • Add ejbfoo-queue-service.xml in src/conf. Netbeans will pack this file in META-INF when build the jar fiel.
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.mq.server.jmx.Queue"
name="jboss.mq.destination:service=Queue,name=ejbfoo">
<attribute name="JNDIName">jms/NewsMsg</attribute>
<depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
</mbean>
</server>
  • Modify persistence.xml as following:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="EjbFoo-ejbPU" transaction-type="JTA">
<!-- Toplink is default for Glassfish
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/sample</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="toplink.ddl-generation" value="create-tables"/>
</properties>
-->

<!-- Hibernate for JBoss -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/jdbc/sample</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>

  • Modify PostNews servlet, which is the messageProducer and send the message through queue:
public class PostNews extends HttpServlet {
// For Glassfish:
//@Resource(mappedName = "jms/NewsMsgFactory")
// For JBoss:
@Resource(mappedName = "ConnectionFactory")
private ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/NewsMsg")
private Queue queue;
...
  • Deploy and run as you would under Glassfish.