Showing posts with label wpf. Show all posts
Showing posts with label wpf. Show all posts

Sunday, March 14, 2010

Ajax and jQuery in the Enterprise, is it such a good idea?

image For the past six months I’ve been working on a web application for a business with around 800 employees. The employees are geographically spread around and they access most of their application by logging onto a Citrix server.

Now tracking back to the start of the project. I didn’t know there was a Citrix environment and started out developing the application screens in a traditional way. It’s a read only application and all actions can be performed with url parameters. Therefore all actions on the page navigates to a new url, using GET.

In a previous project I had been playing around with jQuery and ajax, and figured I could beef up the user experience by tapping into behind the scenes calls and DOM manipulation. Less refresh and flicker and updating relevant parts of the UI generally gives a better user experience.

Since all actions points were links it was fairly easy to ajax’ify them with jQuery.

$("A").each(function() {
$(this).click(
function(event) {
event.preventDefault();
navigate($(this).attr("href"));
});
}
);


The ajax function to execute the calls is also simple. It retrieves the #container element of the page retrieved (“code”) and inserts it into the page viewed by the user. I would define this as a “poor mans ajax”, but it required very little work.


function navigate(navurl) {
$.ajax({
url: navurl,
dataType: "html",
success: function(code) {
$("#container").html($('div #container', code).html());
}
});
return false;
}


In my development and test environments this worked like a charm, and even in production - when I accessed it from my laptop.

Then came the problems, test users were accessing it from a browser within the company Citrix environment.

A sub-second action on my machine, suddenly took anywhere from 5 to 40 seconds on Citrix. Investigating the matter showed that the Citrix server was using around 80%+ CPU at most times. The ajax call is executed fairly fast, but the line

$(“#container”).html( $('div #container', code).html() )

performed really slow. What it does is load the returned html into the DOM, traverse it to fetch the html from the #container element, and then find the #container element on the current page and replace the html. This actually uses a fair amount of cpu. On a stand-alone machine this is not an issue, but on a loaded Citrix server it is.

So there were two options, scrap the ajax calls, or try to fix it. Being stubborn by nature I went for the fix.

The fix was fairly easy. I cached a reference to the current page’s #container element in a global variable, and replaced the DOM search of the returned page with placeholders and good old fashioned substring.


var contentContainer = $("#container");
function navigate(navurl) {
$.ajax({
url: navurl,
dataType: "html",
success: function(code) {
var start = code.indexOf("<!-- cStart -->");
var end = code.indexOf("<!-- cEnd -->");
var html = code.substing(start, end);
contentContainer.html(html);
}
});
return false;
}


In effect I removed the two DOM traversals, and as expected indexOf and substring performs fast.

This shows that an application might behave very different in an Enterprise, since there are many factors to consider. Doing initial research to see how much resources are available for your application is a must for choosing the right strategy. This is equally true for desktop applications. How many colors are available and can the graphics card handle WPF transitions etc?

Tuesday, July 21, 2009

Remove Exif data from image files with C# and WPF libraries


(For my final solution check out Exif continued..)


A colleague of mine e-mailed me with a problem he had. He was developing a solution where the customer wanted all exif data to be removed from the images they provide on the web. He had tried a bit with no luck.

Since the image libraries in WPF is far superior to the ones in winforms I gave it a shot. I googled around, read the exif spec and came up with the code below. The image is read and then loop over all exif properties, and then blank them out. It might work just as good by removing them, but with blanking the file don’t change header wise. Properties pertaining to the image characteristics such as width and height are skipped. You can check them against the exif spec. I have only tried the code on jpeg images, and I didn’t have one with GPS coordinates in it, but in theory it should remove GPS coordinates as well.

I skipped the metadata.TrySave() all together since it didn’t work when I use the SetQuery method. If I just changed the metadata properties it worked. It’s easy to put this back in and you find a discussion about it in one of the links at the bottom.

using System;
using System.IO;
using System.Windows.Media.Imaging;

namespace ExifRemover
{
public class ExifReader
{
public void SetUpMetadataOnImage(string filename)
{
string tempName = Path.Combine(Path.GetDirectoryName(filename), Guid.NewGuid().ToString());
// open image file to read
using (Stream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
// create the decoder for the original file. The BitmapCreateOptions and BitmapCacheOption denote
// a lossless transocde. We want to preserve the pixels and cache it on load. Otherwise, we will lose
// quality or even not have the file ready when we save, resulting in 0b of data written
BitmapDecoder original = BitmapDecoder.Create(file, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
// create an encoder for the output file
BitmapEncoder output = null;
string ext = Path.GetExtension(filename);
switch (ext)
{
case ".png":
output = new PngBitmapEncoder();
break;
case ".jpg":
output = new JpegBitmapEncoder();
break;
case ".tif":
output = new TiffBitmapEncoder();
break;
}

if (original.Frames[0] != null && original.Frames[0].Metadata != null)
{
// So, we clone the object since it's frozen.
BitmapFrame frameCopy = (BitmapFrame)original.Frames[0].Clone();
BitmapMetadata metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;

StripMeta(metadata);

// finally, we create a new frame that has all of this new metadata, along with the data that was in the original message
output.Frames.Add(BitmapFrame.Create(frameCopy, frameCopy.Thumbnail, metadata, frameCopy.ColorContexts));
}
// finally, save the new file over the old file
using (Stream outputFile = File.Open(tempName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
output.Save(outputFile);
}
}
File.Delete(filename);
File.Move(tempName, filename);
}

public void StripMeta(BitmapMetadata metaData)
{
for (int i = 270; i < 42016; i++)
{
if (i == 274 || i == 277 || i == 284 || i == 530 || i == 531 || i == 282 || i == 283 || i == 296) continue;

string query = "/app1/ifd/exif:{uint=" + i + "}";
BlankMetaInfo(query, metaData);

query = "/app1/ifd/exif/subifd:{uint=" + i + "}";
BlankMetaInfo(query, metaData);

query = "/ifd/exif:{uint=" + i + "}";
BlankMetaInfo(query, metaData);

query = "/ifd/exif/subifd:{uint=" + i + "}";
BlankMetaInfo(query, metaData);
}

for (int i = 0; i < 4; i++)
{
string query = "/app1/ifd/gps/{ulong=" + i + "}";
BlankMetaInfo(query, metaData);
query = "/ifd/gps/{ulong=" + i + "}";
BlankMetaInfo(query, metaData);
}
}

private void BlankMetaInfo(string query, BitmapMetadata metaData)
{
object obj = metaData.GetQuery(query);
if (obj != null)
{
if (obj is string)
metaData.SetQuery(query, string.Empty);
else
{
ulong dummy;
if (ulong.TryParse(obj.ToString(), out dummy))
{
metaData.SetQuery(query, 0);
}

}
}
}
}
}