Tuesday 21 June 2011

Asp.Net: Handle empty list in Repeater



Repeater in Asp.Net Webforms is a commonly used user control for presenting lists data. It's quite handy but it lacks support for empty lists.

Below is a simple workaround:









<asp:Label ID="lblEmptyList"
runat="server"
Text="The list is empty"
Visible='<%#bool.Parse((RptrMyList.Items.Count==0).ToString())%>'>
</asp:Label>

Thursday 16 June 2011

Asynchronous calls to WCF service from Asp.Net

One of the functionalities I'm currently working on is document upload. After the document is uploaded to the server via web interface (Asp.Net Webforms) it is passed to a WCF web service which processes it and returns the response. For the whole time the user interface is locked and the end user waits for upload confirmation.

This typical synchronous scenario may be very frustrating for the users because they are blocked until the operation completes. The bigger the file to process the worse it gets.

We decided to change that so an asynchronous upload is used: Once the file is sent to the server the confirmation is immediately displayed to user. The confirmation states only that the upload process was successfully started and the user can continue working with the web app while the file is processed.

In such scenario you'll also need to display upload results at some stage. There are many possible options for displaying the final operation results (e.g. ajax calls combined with some popups, additional tab etc.). This part is not covered in this post.

Implementation:

Because the file is processed by a web service I wanted to use a WCF mechanism for asynchronous service calls. The mechanism is quite easy to use. When you generate the service proxy using Visual Studio select to "Generate asynchronous operations" (under "Advanced" options). This will add additional "Async" method for each operation and Completed event. All you need to do is set the handler for Completed event and call the Async method using generated client e.g.
client.UploadDocumentCompleted +=
new EventHandler<UploadDocumentCompletedEventArgs>(UploadDocumentCallback);
client.UploadDocumentAsync(fileToUpload);

In order to make this work on your Aspx page you'll need to add Async="True" to you page directive (see my other post for details).

Some useful links on how to call a WCF service asynchronously:

Problem:

So I implemented my async service call in code behind of my Aspx page and then it came out it's no good in my case. I was expecting that after the upload operation of the target WCF service is called my page will return response to the user and the UI will not be blocked anymore. It came out that although the service was called asynchronously the page still waits until the operation completes before sending response to the user.

I started to search for the reason of such behaviour and stumbled upon this article. It explains the concept of asynchronous service call from Asp.net pages. It works different than I expected: the async operation must complete before Page's PreRenderComplete event so the page waits for the service call results. It still allows you to boost performance (e.g. by releasing threads to the pool) but not in the way I needed.

Workaround - starting threads manually:

Because the async service proxy didn't solve my problem I decided to implement a workaround. When I need to call the upload operation of the service I create a new thread and call the service within that thread. Since the service is called in a new thread the Page doesn't wait for the service operation to complete.

Sample class for the upload thread:
public class UploadThread
{
private byte[] _byteArray;

public UploadThread(byte[] byteArray)
{
_byteArray = byteArray;
ThreadPool.QueueUserWorkItem(this.Run);
}

protected void Run(object obj)
{
try
{
MyServiceClient client = new MyServiceClient();
client.UploadDocument(_byteArray);
client.Close();
}
catch (Exception e)
{
// Handle exception here
}
}
}
You pass the doc content in the constructor and it starts a new thread that invokes the "Run" method. That method calls the service (synchronously in my case). To be more exact the thread that processes this task is taken from the ThreadPool (see line 8).

To start the thread simply call the following code from the code behind your aspx page:
new UploadThread(fileToUpload);
where fileUplaod is an array of bytes representing the doc content.

One thing to note here is that the new thread will not have direct access to HttpContext of the page. If you needed this in your thread simply pass it in constructor.

Further enhancements:

Thinking about further enhancements I decided to configure the service operation to be One Way. It means that after the client calls the service it doesn't wait for the service response. This will cause that the upload thread will be released to the pool earlier.
[OperationContract(IsOneWay = true)]
void UploadDocument(byte[] byteArray);

An additional performance enhancement may be using streams instead of byte arrays when passing documents to WCF service. I couldn't implement this in my case because of other limitations but you can find a nice example of this here.

Wednesday 15 June 2011

WCF: How to increase request size limit

When you generate a new WCF web service using Visual Studio it will use "wsHttpBinding" with its default configuration. Default endpoint configuration generated for a new web service looks like that:
<endpoint
address=""
binding="wsHttpBinding"
contract="Your.Namespace.IService1" />

This will work well in most cases at development stage. However, before deploying the web service you should consider changing the default settings according to your needs (e.g. change security settings).

The problem you may often get already at development stage is the following error thrown when you send large amount of data to your service (e.g. a large file):

The remote server returned an unexpected response: (400) Bad Request

The request fails because it's size exceeds the limit allowed by your service. The default request size limit is 65536 bytes (i.e. 64KB). In order to change this limit you need to use custom binding configuration. The following example sets the request size limit to 50MB (i.e. 52428800 bytes) using custom binding configuration:
<endpoint
address=""
binding="wsHttpBinding"
bindingConfiguration="myCustomConf"
contract="Your.Namespace.IService1" />
<bindings>
<wsHttpBinding>
<binding name="myCustomConf"
maxReceivedMessageSize="52428800"
maxBufferPoolSize="52428800" >

(...)
</binding>
</wsHttpBinding>
</bindings>

In example above I used custom binding configuration named 'myCustomConf'. You can read more about wsHttpBinding properties here.

The code above cares about configuration of your WCF service. However, you'll most likely need to increase the request size limit for Asp.NET runtime as well:
<system.web>
<httpRuntime maxRequestLength="51200" />
(...)
</system.web>

Note that Asp.Net maximal request size is set in KB whereas WCF configuration uses bytes! So, in order to allow requests of size 50MB you'll need to set value of 51200KB.

Thursday 9 June 2011

Convert Excel date into timestamp

Excel stores dates internally as number of days since January 1, 1900.
For example: "June 9th, 2011 10:30 AM" would be stored as "40703.4375".
40703 is the number of full days since 01/01/1900 and 0.4375 represents the time (10.5/24 = 0.4375).

When you process dates read from an Excel spreadsheet (e.g. using PHPExcel) you often want to convert them into a UNIX timestamp i.e. a number of seconds elapsed since midnight of January 1, 1970 UTC.

Here is a PHP code to do that:

// Numbers of days between January 1, 1900 and 1970 (including 19 leap years)
define("MIN_DATES_DIFF", 25569);

// Numbers of second in a day:
define("SEC_IN_DAY", 86400);

function excel2timestamp($excelDate)
{
if ($excelDate <= MIN_DATES_DIFF)
return 0;

return ($excelDate - MIN_DATES_DIFF) * SEC_IN_DAY;
}
Although the code above is written in PHP the function should be very similar in any other language e.g. C# or java. If the provided date is earlier than 1/1/1970 then the minimal timestamp value will be returned.

Alternative solution:

If you provide the Excel spreadsheet that you later on read from in your app you could add a hidden cell that would calculate the timestamp for you, within the spreadsheet.
Assuming that B2 is the cell that stores your date the formula for calculating timestamp would be:
=(B2-DATE(1970,1,1))*86400
Now you only need to read the calculated value from the hidden cell.

Wednesday 8 June 2011

Localized error messages

If you are not a native English speaker it is very likely that you're using a localized operating system. I'm currently working on Windows 7 Enterprise with system language set to Polish.

The disadvantage of this is that when I develop any .net code on my machine and an error occurs the error message I'm getting is localized. This is supposed to help me understand the error cause but it's actually doing the exact opposite. After an error message is translated into Polish it's usually totally meaningless to me. This is mainly because most of documentation is written in English and many technical terms are simply hard for direct translation.

Also, when I search the web for the error message I got there are obviously much more results for the English version.

Another downside is that I can't directly share the error message with my foreign team mates as they wouldn't understand it.

Lately I discovered a web page that can unlocalize the message for me: www.unlocalize.com. It allows you to search for the message or browse the catalog. It also offers some browser plugins for faster unlocalization.

Asynchronous operations are not allowed in this context

While calling a WCF service asynchronously from 'Code Behind' of my .Aspx page I got the following error:

Asynchronous operations are not allowed in this context. Page starting an asynchronous operation has to have the Async attribute set to true and an asynchronous operation can only be started on a page prior to PreRenderComplete event.

Solution:


Add Async="true" to you Page directive i.e.
<%@ Page Language="C#" Async="true" ...

You can read more about Async attribute HERE.

BTW. The original error message was in Polish as I use localized operating system:

Operacje asynchroniczne nie są dozwolone w tym kontekście. Strona rozpoczynająca operację asynchroniczną musi mieć atrybut Async o wartości True. Operację asynchroniczną można uruchomić na stronie tylko przed zdarzeniem PreRenderComplete.

I translated it using the tool descriebd in my next post.