Sunday, December 18, 2011

Re-size and re-center Telerik MVC modal window after ajaxRequest

Telerik MVC modal window can auto-size to fit its initial content. After ajaxRequest, however, the window size stays the same, not adjusting to the new content. Here is how to re-size and re-center a modal window after ajaxRequest. The trick is to remove css "width" and "height" properties from the .t-window-content div.

@{Html.Telerik().Window()
 .Name("Popup")
 .Modal(true)
 .Visible(false)
 .ClientEvents(events => events
  .OnRefresh("resizeOnRefresh")
  .OnResize("centerOnResize"))
 .Render();
}


Saturday, December 17, 2011

Set Min- and Max-Sizes on Telerik ASP.NET MVC Windows

Telerik ASP.NET MVC window (as of 2011.Q3) does not directly support min-length, min-width, max-length or max-width. However, you can set these css properites on its child .t-window-content div. For example,

var winContent = $('#MyWindow .t-window-content');
winContent.css("min-width", "100px");
winContent.css("max-width", "500px");
winContent.css("min-height", "50px");
winContent.css("max-height", "500px");

Thursday, November 17, 2011

OBIEE XMLViewSerive.executeXMLQuery does not accept filters in ReportParams.filterExpressions

OBIEE version:

11.1.1.5.0

Symptom:

I need to evoke XMLViewService.executeXMLQuery() with different saved filters. I tried to set the filter in ReportParams.filterExpressions. The server returned the query result without any complaints. But the results were always the same as the result without any filters.

Work-around:

Set filters directly in ReportRef.

Sunday, November 6, 2011

Bind Telerik MVC TreeView to XElement

In order to bind Telerik MVC TreeView to an XElement, you need to define a recursive function, not a helper. For example,

@using Telerik.Web.Mvc.UI.Fluent;
@using System.Xml.Linq;
@model XElement

@functions {
 void BindXElement(TreeViewItemFactory item, XElement elem){
  var node = item.Add().Text(elem.Name.LocalName);
  foreach (var e in elem.Elements()) {
   node.Items(subItem => BindXElement(subItem, e));
  }
 }
}

@(Html.Telerik().TreeView()
  .Name("TreeView")
  .Items(item => BindXElement(item, Model))
)

Monday, August 8, 2011

Get SQL Expression out of WhereClause

Introduction

Several years ago I posted a method to get SQL expression out of a WhereClause. I did not dig deep enough, so it was practically unusable because of Null Object Exception. This post is a long overdue correction.

Implementation

Put the following code in a .cs file under App_Code (website) or Shared (web application) folder.
using BaseClasses;
using BaseClasses.Data;
using BaseClasses.Data.SqlProvider;

namespace DingJing {
	public static class ExtensionWhereClause {
		public static string GetSQL(this WhereClause wc, BaseTable tbl) {
			var f = new CompoundFilterExt(wc.GetFilter() as CompoundFilter);
			return f.GetSQL(tbl.DataAdapter);
		}
	}

	class CompoundFilterExt : CompoundFilter {
		public CompoundFilterExt(CompoundFilter cf)
			: base(cf.CompoundingOperator, cf.GetFilters()) { }

		public string GetSQL(IRelationalDataAdapter adapter) {
			var arg = new SqlGenerationArgs() {
				Adapter = adapter,
				Encoder = new SqlFragmentEncoder()
			};
			
			var tjl = new TableJoinList();
			var s = ToSql(arg, ref tjl);
			return s.Expression;
		}
	} 
}

The above code adds an extension method GetSQL() to WhereClause. To get the SQL expression, you only need 1 line of code. For example,
public override WhereClause CreateWhereClause() {
	var wc = base.CreateWhereClause();
	if (wc != null)
		SQLClause.Text = wc.GetSQL(OrdersTable.Instance);

	return wc;
}

Friday, July 29, 2011

DataStage Shared Container Parameters

Shared containers can have parameters. They are passed literally. Single quotes must be escaped with "\" character.


(v8.1)

Friday, July 1, 2011

Smart Search Filter

Introduction

In Iron Speed Designer, it is very easy to add a search filter above a table control. It is also very easy to config the filter to work in Equals, StartsWith, EndsWith or Contains mode. At design time, I always set search filters in Contains mode to get the most search power. At run-time, however, Contains mode sometimes returns too many hits. These are the times I wish I could double-quote the search text, and the filter would automatically switch to Equals mode.

This turns out to be a pretty straightforward customization.

Code customization

Use the following convention to define a search filter's operator:
TextOperator
"search term"Equals
"search termStartsWith
search term"EndsWith
search termContains

Override the table control's CreateWhereClause() method. Find the code block that defines the search behavior. Insert the following code right before the WhereClause is declared.

var searchOp = BaseFilter.ComparisonOperator.Contains;
if (formatedSearchText.StartsWith("\"") && formatedSearchText.EndsWith("\"")) {
  searchOp = BaseFilter.ComparisonOperator.EqualsTo;
  formatedSearchText = formatedSearchText.Substring(1, formatedSearchText.Length - 2);
} else if (formatedSearchText.StartsWith("\"")) {
  searchOp = BaseFilter.ComparisonOperator.Starts_With;
  formatedSearchText = formatedSearchText.Substring(1);
} else if (formatedSearchText.EndsWith("\"")) {
  searchOp = BaseFilter.ComparisonOperator.Ends_With;
  formatedSearchText = formatedSearchText.Substring(0, formatedSearchText.Length - 1);
}

Then replace the search operator in the WhereClause:
WhereClause search = new WhereClause();
search.iOR(CustomersTable.CustomerID, searchOp, formatedSearchText, true, false);
search.iOR(CustomersTable.CompanyName, searchOp, formatedSearchText, true, false);
search.iOR(CustomersTable.EmailAddress, searchOp, formatedSearchText, true, false);
search.iOR(CustomersTable.City, searchOp, formatedSearchText, true, false);
search.iOR(CustomersTable.Region, searchOp, formatedSearchText, true, false);
search.iOR(CustomersTable.PostalCode, searchOp, formatedSearchText, true, false);


Conclusion

Now you have a smart search filter.

Saturday, May 21, 2011

DataStage Custom Routine to Get a File Size

Below is a DataStage custom transform routine to get the size of a file. The full path of the file is passed in as a parameter called "Filename".

CMD = "ls -la " : Filename : " | awk '{print $5}'"
CALL DSExecute("UNIX",CMD,Output,SystemReturnCode)
size = Group(Output, @FM, 1)
Ans = If Num(size) Then size Else -1

If the file doesn't exist, -1 is returned.

Monday, May 16, 2011

Securing ELMAH with Independent HTTP Authentication

Introduction

ELMAH is an open source, plug-&-play solution for logging and reporting unhandled errors in ASP.NET web applications. By deploying it to GAC (global assembly cache) and configuring at server level (machine.config or root web.config), you can achieve zero footprints at application level. Existing and future applications automatically get error logging and reporting capability without a single line of code modification or configuration change. This is extremely attractive to IT shops with a lot of home-grown ASP.NET web applications. ELMAH's inadequate security features, however, might limit its wide adoption within enterprises. In this article, I will show how to secure ELMAH with independent HTTP authentication.

Inadequate security in ELMAH

ELMAH's built-in security can only turn remote access on/off. In other words, it uses server accessibility as a security defense. This is inadequate because the persons who want to use ELMAH may or may not have server access. It is application developers who are most interested in using ELMAH, which provides important clues to help them reproduce, identify and fix bugs. The persons who can logon servers are server administrators. In open source projects or small IT shops, developers are usually also administrators. So the built-in security may be sufficient. In enterprises, however, the two roles are almost always separated. Even if the developers have direct access to servers, they might prefer read ELMAH logs remotely on their development machines rather than locally on the servers. Once remote access is turned on, there is nothing within ELMAH to prevent anyone, from innocent surfers to malicious hackers, from reading error logs.

Phil Haack proposed an enhancement on his blog using a "location" tag in web.config to allow access only for authenticated users, as shown below.
The access can be further restricted to a subset of users, e.g. developers.

Basically, the enhancement secures ELMAH as a virtual sub directory taking advantage of host web application's security. In other words, ELMAH's security is outsourced to individual host web applications. This is a genius work-around, however, with the following limitations.
  1. You cannot do zero-footprint plug-&-play by putting location tags in machine.config or root web.config. Although location tags are allowed in high level configure files, you can only point one to a specific application (e.g. /DefaultSite/MyApplication/admin/elmah.axd). You cannot point one tag to all applications (/*/*/admin/elmah.axd is invalid). Therefore, each application requires a location tag, placed either individually in application level web.config or collectively in a higher level configure file. Put it another way. Since we outsource ELMAH's security to individual host applications, we have to sign separate contracts with every applications.
  2. The contracts are dependent on host applications' authentication mechanisms. Some applications may not require authentication at all. While these applications are open to general or internal (intranet) public, it does not mean that you want to open their ELMAH logs as well.
  3. They are also dependent on host applications' authorization roles. Some applications assign roles based on business functions, such as Director, Manager, and Staff etc. Developers are out of the picture.
In order to make ELMAH more appealing to enterprises, we need to keep its zero-footprint plug-&-play ability while adding an authentication and authorization mechanism independent to host web applications'.

Solution part I: HTTP authentication

First of all, in order to authenticate and authorize an ELMAH request, we have to know who is making the request. As explained above, we are not able to use ASP.NET authentication mechanisms (i.e. Windows or form authentication), since they may be different from application to application. We will resort to HTTP authentication.

HTTP authentication is a sequence of communications between a web server and a client browser based on 401 (Unauthorized) HTTP status code. If the server needs additional authentication information (username and password) for an incoming anonymous request, it responds with a 401 status code along with a WWW-Authenticate response header. Also included in the header is the authentication scheme name (Basic or Digest), indicating how the browser should send in the username and password. For Basic scheme, they should be concatenated with a colon (username:password) and Base64-encoded. And for Digest scheme, they should be encrypted (MD5 cryptographic hashing). Upon receiving the 401 response, the browser pops up a login dialog, allowing the user to enter the username and password. The username and password (encoded or encrypted according to the scheme) are inserted into the original request as an Authentication request header; and the request is then resent to the server. Once the username and password are recovered at the server, there are many options to authenticate and authorize the user.

Solution part II: Don’t call me (I’ll call you)

The next question is when and where we initiate the HTTP authentication so that it does not hurt ELMAH's zero-footprint plug-&-play capability. Since ELMAH requests are handled by ErrorLogPageFactory, it makes sense to have a look over there first. After poking around in ELMAH source code and a couple of Google searches, I found a discussion about an undocumented feature in ELMAH for implementing custom authorization. Below are the main points in the discussion.
  • Register an HttpModule that implements interface IRequestAuthorizationHandler.
  • The interface defines only one method: bool Authorize(HttpContext context).
  • The method is called everytime ELMAH request is made via ErrorLogPageFactory.
  • In order for ErrorLogPageFactory to discover the module under medium trust environment, it should inherit from HttpModuleBase and override SupportDiscoverability to return true.
  • They ran into an issue while trying to authorize a user with credentials stored in session. The Authorize method is called way too earlier before ASP.NET engine associates session state to a request.
The session availability issue they discussed confirmed the validity of independent HTTP authentication approach described in the previous section. The first several bullet points answered the above when and where question. What we need to do is deriving a class from HttpModuleBase, implementing interface IRequestAuthorizationHandler, waiting for a call from ErrorLogPageFactory, and then initiating HTTP authentication from within Authorize method.

It is, however, putting the cart before the donkey. Being an HttpModule, the authorization class has the privilege to examine all incoming requests BEFORE any HttpHandlers or HttpHandlerFactories. Instead of waiting passively for a call from ErrorLogPageFactory, the module can actively intercept ELMAH requests, initiate HTTP authentication, and pass authorized requests to ErrorLogPageFactory. Don't call me, I'll call you.
By taking the active approach, the authorization module does not have to inherit from HttpModuleBase, nor implement interface IRequestAuthorizationHandler. It is completely decoupled from ELMAH, not relying on any ELMAH API (documented or undocumented). So upgrading ELMAH to newer versions won't cause any compatibility issues. It also won't affect how ELMAH is deployed or configured, keeping its zero-footprint plug-&-play capability intact.
NoteThe authorization module intercepts ELMAH requests if incoming requests' URLs contain "elmah.axd". This is the only requirement on ELMAH's configuration.

Example implementation

Below is an example implementation of the authorization module.

In order to illustrate the main points of the solution and minimize the distraction from implementation details, I simplified the implementation by hard-coding Basic HTTP authentication scheme and Active Directory authentication/authorization service. Although simplified, it is still good enough for practical uses, as long as Basic scheme and AD service fit your environment. To use the example, simply drop the AuthModule.cs into your website's app_code folder or the SecurElmah.dll into bin folder, and register it in "httpModules" section in web.config.


Also add the following 2 entries in "appSettings" section to setup AD server and ELMAH authorized role.


Of course, SecurElmah can be deployed and configured globally just like ELMAH to achieve zero-footprint plug-&-play.

If Basic scheme doesn't fit your environment, it is fairly straightforward to switch to the more secure Digest scheme. It is also possible to implement both schemes, and make it configurable through web.config. Another place for improvement is authentication and authorization service. You can use ASP.NET MembershipProvider and RoleProvider to make it more flexible and configurable.

Summary

Combining the convenience of zero-footprint plug-&-play with the security of an independent authentication and authorization mechanism, ELMAH is enterprise-ready.

Sunday, April 3, 2011

Reorder Table Control Rows with "Drag and Drop"

Introduction

Suppose you need to design a page for users to order a list of items, for example, assigning priorities to a list of tasks, or ordering questions in a questionnaire. ISD's out-of-box solution is an edit table control, in which a textbox is for entering each item's order. An obvious drawback of the solution is that users have to manually maintain the integrity of all orders, and  a change in one row may result in changes in other rows.

A slightly better design is to replace the textbox with a pair of up and down arrows. You can use custom code in the arrows' event handlers to switch order between the clicked item and its previous or next one. When the page is regenerated, the items are sorted on their new orders. This design guarantees order integrity, but is inefficient. In order to move an item from bottom to top of the list, it requires quite some clicks.

Wouldn't it be nice that user simply drag and drop an item to its desired position and its new order, as well as other affected items', is reassigned automatically? This post will show you how to implement it in ISD.

Solution

There are two candidate implementations for drag-n-drop ordering, AjaxToolkit's ReorderList and a jQuery plugin Table Drag and Drop (TableDnD). ReorderList is a data-bound server control. It asks for a list of items as data source, and takes full control of data-binding and rendering. ISD has little help to offer. On the other hand, TableDnD is a pure client-side solution, leaving ISD with full control at server-side. Therefore, I will use TableDnD in this implementation.

Implementation

Step 1: Create a regular EditTable page
Create a plain vanilla EditTable page as shown below. Nothing fancy.


Step 2: Add jQuery and TableDnD libraries
In the page's prologue, insert 2 script references (lines 1 and 2):
Step 3: Configure HTML  table for DnD
Since ISD-generated pages are heavily loaded with HTML tables, it could be a little bit tricky to find the right one to configure. It is the inner most Fields table, as shown below.


First, give the table an unique ID. TableDnD will use the ID to locate the table and attach DnD behavior.


Then, add 2 classes (nodrag and nodrop) to the header row. This notifies TableDnD not to attach DnD behavior to the header row.

Step 4: Add CssClass to order field
Add CssClass (orderbox) to the textbox that holds the order field.

Step 5: Customize background color for the row in DnD
Add the following css class in Styles.css:
tr.tDnD_whileDrag td
{
  background-color: #CCCCCC;
}                                               
Step 6: Attach DnD behavior
Finally, insert the following script into the page's epilogue:

Conclusion

It seems a lot of steps. But your users' uh-ah reaction makes it all worth the trouble. You can download the demo here.

Sunday, March 27, 2011

Open ISD Pages in Popup Windows: A Comprehensive Guide

Introduction

It is sometimes necessary to open a page in a popup window, for example, a printable version of an invoice page. ISD does this itself on large-list-selector (LLS) and quick-add pages. As a developer, when you make a page to be opened in a popup window (instead of redirecting in the main window), you explicitly or implicitly make the following decisions:
  • Where do you open the popup? Client-side or server-side?
  • How do you pass parameters from the main window to the popup?
  • How do you send information back to the main window from the popup?
Different scenarios need different combination of the decisions. This guide will help you make informed decisions.

Note: A theme button named "PopButton" will be used as the trigger to open the popup throughout the guide.

Primer: window.open()

Before we examine the above three decisions, let's first review window.open, for those who are not familiar with the JavaScript function.

No matter where and how you open a popup, you need to use the JavaScript function:
window.open(url, windowName, windowFeatures)
Theoretically, all three parameters are optional. In practice, however, the first parameter url is usually provided. It is meaningless to open a blank popup without an url, unless you are trying to annoy your users.

The second parameter windowName is optional. It assigns the name to the new popup window. If there is already an open window with that name, the function will not popup a new window. In stead, it just opens the url in the existing window. You can use this parameter to control multiple pages poping up in a single window or multiple windows.

The third optional parameter, windowFeatures, controls the popup's features, such as size, position, status bar, address bar, scroll bar, toolbar, etc.

With the basics out of the way, let us get back to the first decision, whether to open the popup at client-side or server-side.

Client- vs. server-side popup

To popup at client-side, call window.open() in PopButton's client-side OnClick event handler. This can be done within ISD through PopButton's Custom properties. For example,
PropertyValue
Button-HtmlAttributes-OnClickwindow.open('MyPage.aspx'); return false;
Button-OnClientClick
The two properties are synonymous, so they can be used interchangeably. Don't forget "return false" statement after window.open. This is to prevent the button from triggering postback.

Alternatively, it can be setup in code, especially if you need to formulate the URL at run-time. For example,
string url = ...; // Formulate url
PopButton.Button.OnClientClick = 
  string.Format("window.open('{0}'); return false;", url);
Or
string url = ...; // Formulate url
PopButton.Button.Attributes["OnClick"] = 
  string.Format("window.open('{0}'); return false;", url);

To popup at server-side, register window.open() in PopButton's server-side OnClick event handler. For example,
public override void PopButton_OnClick(object sender, EventArgs e){
  string url = ...; // Formulate url
  string script = string.Format("window.open('{0}');",, url);
  ScriptManager.RegisterStartupScript(Page, GetType(), "OpenWindow", script, true);
}

From coding point of view, there is little difference between client- and server-side popup. From efficiency and performance point of view, however, there are huge differences. Client-side popup does not pose any overhead on the server and the network; and you get instant response. On the other hand, server-side popup triggers a round trip between the server and the client; and the server undergoes a full page life cycle. Therefore, client-side popup is almost always favored over server-side popup.

Send information to popup window

More often than not, you need to pass some information from the main window to the popup. For example, the invoice number to be printed, or the lookup table name in an LLS. There are two ways to pass the information, URL parameters and session variables. URL parameters are suitable for small number of primitive values, such as strings, integers, etc. For complex data (e.g. arrays, lists, records, etc.), session variables work better. Session variable also has the advantage to avoid sensitive information to be seen in address bar.

Send information back to main window

It is not always necessary to send information back to the main window. For example, after a user prints his invoice in a popup, he simply close the popup and continue shopping in the main window. If you do need to send information back to the main window, there are again two ways to do that, from client-side or server-side.

ISD's built-in LLS is a typical example for the client-side mechanism. When a value is selected within the LLS, it is sent back to a Textbox or DropdownList in the main window via client-side JavaScript. The key in the communication is the receiving control's ClientID. It is sent to the popup window as a URL parameter. The popup uses the ID to find the control in the main window and manipulate its value. Below is the built-in JavaScript function (in ApplicationWebForm.js) to update a control in the main window.
function updateTarget(targetName, selectedValue, selectedDisplayValue){
    if (window.opener && !window.opener.closed) {
        var target = window.opener.document.getElementById(targetName);
        
        var bSuccess = false;
        if (!bSuccess){
            if (Fev_ReplaceLastListControlOption(target, selectedValue, selectedDisplayValue) ){
                //try setting the selection again
                bSuccess = Fev_SetFormControlValue(target, selectedValue);
            }
        }
        
        if (bSuccess){
            if(target != null) {
                if (navigator.appName == "Netscape") {
                    var myevent = document.createEvent("HTMLEvents")
                    myevent.initEvent("change", true, true)
                    target.dispatchEvent(myevent);
                }
                else { // IE
                    target.fireEvent('onchange');
                }
            }
        }           

        window.close();
    }
}

Here is the scenario you may want to try the server-side mechanism. On a typical ShowTable page, there is an New ImageButton, which redirects to an AddRecord page. Instead of redirect, you make the New button to open the AddRecord page in a popup. When a new record is saved in the popup and the popup is closed, you would like the main page refreshed to show the newly added record. This is not an issue in the redirect scenario. After redirecting back from the AddRecord page, the ShowTable page is automatically refreshed. In the popup scenario, however, the main window won't automatically refresh when the popup is closed. You have to trigger a postback in the main window from the popup. Below are the steps to implement this mechanism.

Step 1: Set New button's OnClientClick to open popup

In ShowTable page's TableControl DataBind() method, insert the following 2 lines of code:
string url = "AddRecord.aspx?TargetID=" + NewButton.Button.UniqueID; // Formulate url
NewButton.Button.OnClientClick = 
  string.Format("window.open('{0}'); return false;", url);
Applying the above-mentioned decision making process, it is a no-brainer to popup at client-side and pass URL parameters. The AddRecord page itself does not need any parameters. We pass the NewButton's UniqueID so that the popup can use it to trigger postback. Please note it is the UniqueID that triggers server-side postback, not the ClientID for client-side manipulation.

Step 2: Change New button's action from redirect to refresh

Now the New button's server-side function has been changed. We no longer need it to redirect to AddRecord page. We need it to refresh the current page. This can be easily configured in ISD. Please don't forget to change "Redirect and Send Email Actions" to "Stay on the current page."

Step 3: Set popup's Save button to trigger postback in main window

Insert the following lines into AddRecord Save button's Click handler:
public void SaveButton_Click(object sender, EventArgs args) {

 // Click handler for SaveButton.
 // Customize by adding code before the call or replace the call to the Base function with your own code.
 SaveButton_Click_Base(sender, args);
 // NOTE: If the Base function redirects to another page, any code here will not be executed.

 if (!ErrorOnPage) {
  string script = string.Format("window.opener.__doPostBack('{0}', '');window.close();", Request.QueryString["TargetID"]);
  ScriptManager.RegisterStartupScript(Page, GetType(), "TriggerPostback", script, true);
 }
}
Please don't forget to change Save button's "Redirect and Send Email Actions" to "Stay on the current page." Otherwise, the inserted code won't get executed as commented in the method.

Conclusion

Using popups discretionally can improve your web applications' workflow. The tips in this post may help you decide whether to popup or how to popup.