January 2008 Entries

Its good practice to separate out code that can be re-used into its own class library which can easy be referenced by other projects. Until now any resources such as images/JavaScript files that I'd created I needed to remember to copy to each web site that used the dll, ensuring that I created the correct directory structure so the paths would be correct. Then it dawned on me that you must be able to embed these files into the dll itself. Here's the steps I used to embed an image file for a AjaxControlTookit Calendar Extender that I was using in a custom server control (the same principle can be applied to any file you need - typically JavasScript and/or images will be embedded)

Step 1:

Add your file to the Class Library project

(in my example I added [PROJECT ROOT]/Resources/Calendar.png")

Step 2:

Select the newly added file and in the Properties window select:

Build Action = "Embedded Resource";

Step 3:

In the Project's AssemblyInfo.cs file add the following line:

//Replace "BradsNamespace" with the namespace of your project
//Replace "Resources" with the folder structure from the root of the project 
//to the file (using . instead of / )
//Replace "Calendar.png" with your file's name //Replace "img/png" with relevant type e.g. "text/javascript", "image/jpeg" etc [assembly: System.Web.UI.WebResource(
    "BradsNamespace.Resources.Calendar.png", "img/png")]

Step 3:

Now retrieve the Url for the file:

Image CalendarImage = new Image();

CalendarImage.ImageUrl = Page.ClientScript.GetWebResourceUrl(
 this.GetType(),
 //Replace following with the string entered in AssemblyInfo.cs
 "BradsNamespace.Resources.Calendar.png");

Finished:

And that's it! Now when you run it .NET will generate the Url for you in the format:

src="/WebResource.axd?d=_Y_kbnu3Fp4-fBKDGxpOa-LgnKcgPOIASf1ExUjshsj3373650125705280" 

Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

Unfortunately the following code will fail if the file in question is ReadOnly:

File.Delete(path);

So I'd always recommend using the following (especially when Visual Studio keeps annoying checking in files that it shouldn't concern itself with!!)

//Check the file actually exists
if (File.Exists(path))
{
   //If its readonly set it back to normal
//Need to "AND" it as it can also be archive, hidden etc if ( (File.GetAttributes(path) & FileAttributes.ReadOnly)
== FileAttributes.ReadOnly) File.SetAttributes(path, FileAttributes.Normal); //Delete the file File.Delete(path); }

Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

If you want to display the size of a file to the user simply doing the following will result in the size of the file in bytes:

new FileInfo(PathToMyFile).Length

However that's fairly useless in today's world of bigger and bigger files so along comes this neat bit of code:

public static string GetFileSizeAsString(long size)
{
    double s = size;
    string[] format = new string[] { "{0} bytes", "{0} KB",  "{0} MB", "{0} GB", "{0} TB", "{0} PB", "{0} EB", "{0} ZB","{0} YB" };
    int i = 0;
    while (i < format.Length-1 && s >= 1024)
    {
       s = (int)(100 * s / 1024) / 100.0;
       i++;
    }
return string.Format(format[i], s.ToString("###,###,###.##")); }

This will return you the following strings (depending on how big the file is...)

  • 160 bytes
  • 345 KB
  • 34.7 MB
  • 1.2 GB
  • 1.76 TB
  • 8.19 PB and so on...

If you get a file bigger than an 1,024 Yottabytes (YB) then tough! I'm sure you'll agree its not really that much of a limitation!


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

UpdatePanels work fine with almost all controls, one control that it does 'break' however is FileUpload. If you try and upload a file it will seem to work - but if you check FileUpload.HasFile it will always return false. Rubbish if you've got an UpdatePanel in your MasterPage! However there is a solution, in fact it's not the UpdatePanel that breaks it, but the asynchronous PostBack instead. So a neat trick is to register the button you're using to cause the upload to use a normal PostBack. This can be done as follows:

this.ScriptManager.RegisterPostBackControl(btnUpload);

Then when the user clicks the upload button they'll get a normal PostBack (and therefore an uploaded file), while all the other controls on the page will continue to operate asynchronously - no need to re-jig the whole layout of the site.


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I've just been investigating Microsoft SQL Server 2005 Reporting Services (SSRS) and in a word its great! However this post is about fixing a problem I found when trying to use the ReportBuilder from other computer.  When you click "ReportBuilder" in the SSRS web-based UI it will fail with a 401 denied error. By default (for security) the "ReportServer" virtual directory doesn't allow anonymous access, and Microsoft's ClickOnce technology doesn't seem to allow you to provide a username and password - fairly limited I think you'll agree.

Anyway on to the solution... if you give anonymous access to the whole virtual directory it will download ok - but when you come to login it will constantly fail, complaining that the Internet guest account doesn't have the required permission - even if you're providing your own credentials! Also I wouldn't be happy about giving this kind of access to the whole virtual directory as I'm a newbie to SSRS so I'm not sure what security holes I'd be exposing.

However after some digging I discovered that if you just give anonymous access to "ReportServer/ReportBuilder" folder within IIS then bingo it will let you download - and when you come to login it will work ok.

And for the security conscious amongst us, in fact you can be more secure than that, only the following files require anonymous access for it to work:

  • ReportServer/ReportBuilder/ReportBuilder.application
  • ReportServer/ReportBuilder/ReportBuilder.exe
  • ReportServer/ReportBuilder/ReportBuilder.exe.manifest

Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I've just come back to do a "Phase II" of an old EPiServer 4.61 project and was having issue's with none of the child pages working, or even the CSS being loaded in the CMS area. I decided to investigate the style sheet error in the admin area - it was looking for /util/styles/system.css but if I looked in the file system such a file didn't exist... hmm! Then I realised it must be a virtual path and remembered that pre version 5 EPiServer used a custom error page to return virtual files and to do its friendly URL re-writing. So I looked in IIS and bingo there was the problem.... IIS was using the default 404 error page, once it was changed to /Util/Notfound.aspx everything sprung into life...

Tip from Steve: If you Open EPiManager and click on the offending site you'll get a warning message saying that IIS's configuration is incorrect - do you want to fix the 404 error page value! If only I'd thought to look there earlier!!


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I seem to spend my whole life re-licensing EPiServer sites during development, then again when it gets moved to stage and finally production. Each time I go to EPiServer's site and can never find a simple link to generate a licence and always end up having to go the download area, re-register my details blah blah blah.

Anyway here's the link I should have been using - and it's now firmly bookmarked!!

http://license.ep.se/public/


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

Who hates trying to work out what a complex C# method does that you didn't write? I know I do! Now who also hates writing the aforementioned comments... yep me too again! Along comes GhostDoc... a nice little plug-in for Visual Studio that automatically generates XML comments for your C# code! All you need to do is hit the hot key (which you can define) or right click and choose "Comment this..." and bingo a method or property you had selected now has a comment. GhostDoc uses some clever behind the scenes code based on the name of the method/property and the data types involved to make a "best guess" effort on giving it a comment  - now I admit some of its suggestions do provide a few comedy results - but either way its often a good starting point!

I suggest you give it a go...

http://www.roland-weigelt.de/ghostdoc/


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I've just spent a while pulling my hair out trying to find out why some text in my repeater control wasn't updating after postback on one page but was on another. The repeater was in a UserControl I'd made, and the same control was on both pages - the only difference - once was inside an AJAX UpdatePanel and on the other page it wasn't.

Here's the offending code:

<ItemTemplate>
    <tr>
       <td><asp:Image runat="server" ImageUrl='<%# Eval("Image") "%>' ID="iImage" /></td>
       <td><%# Eval("Score") %></td>
       <td><%# Eval("Percentage") %></td>
    </tr>
</ItemTemplate>

Now what was strange is that the image was updating ok (this confused me a lot while viewing it as the image is a bar representing the percentage figure and i spent ages working out while the image was wrong - before i noticed that it was the figure that was wrong not the image!)

Once I changed it to the following it worked fine:

<ItemTemplate>
    <tr>
        <td><asp:Image runat="server" ImageUrl='<%# Eval("Image") "%>' ID="iImage" /></td>
        <td><asp:Literal runat="server" Text='<%# Eval("Score") %>' ID="lScore" /></td>
        <td><asp:Literal runat="server" Text='<%# Eval("Percentage") %>' ID="lPercent" /></td>
    </tr>
</ItemTemplate>

The page that wasn't working contained an UpdatePanel holding a Dropdown box that causes my UserControl to rebind its Repeater control... Does anyone know why rebinding in the standard page works in a normal postback but not when inside an AJAX UpdatePanel?


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

I've been using the .NET version of FCKeditor for some time, but recently changed the page layout, resulting in the FCKeditor instance now residing in an AJAX UpdatePanel. All was fine while using IE, but when I tested it in Firefox I noticed that the changed content in the FCKeditor wasn't getting saved to the database. It turns out its a JavaScript issue that effects Firefox and Safari.


I've been unable to find the perfect solution but in the meantime I discovered the following code does the trick - but unfortunately causes Firefox/Safari users to see a full post back - not just what's in the UpdatePanel.

if (Request.Browser.Browser.ToLower().Contains("firefox") || Request.Browser.Browser.ToLower().Contains("safari"))
        {
            ScriptManager sm = Master.FindControl("ScriptManager1") as ScriptManager;
            sm.RegisterPostBackControl(btnMySubmitButton);
        }

If anyone knows a better way to fix it please let me know!


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

It's always frustrated me that Visual Studio takes so long to start debugging web applications compared to a standard windows form application. Also when you stop debugging it would kill the spawned IE window too - annoying if you were in the middle of testing something... Anyway I often found it was better to launch my own version of IE (so it remains open) then in Visual Studio select Debug > attach to process > then select the WebDeb.WebServer.EXE process(s). This was much quicker than clicking the standard "start debugging" button (or F5). But even that wasn't enough - it still annoyed me that you had to manually select the process to attach to - then while looking for something else I stumbled across the solution...

Right click the web site in the solution and select properties, followed by the "Start Options" node. In there change the "Start action" from the default "Use current page" to "Don't open a page. Wait for a response from an external application" then click OK. With the site still selected I recommend setting "Use dynamic ports" to false in the properties window - this means you'll always know what port to connect to. Now open IE as before and browse to the correct URL (and port). Now simply hit F5 in Visual Studio whenever you want to debug and it'll load quicker, without spawning a new instance of IE and it will remain open when you stop debugging.


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist

Typically our clients opt to use Google's free web stats package Google Analytics to track their site usage - and who can blame them! Anyway adding them to every project is a bit of a chore, so I decided to save time by adding it to my BasePage class. For those of you that don't create base pages - you should! It's great for situations like this where you want to apply or access some code in every page (I admit it's far more useful to make all UserControls inherit from a BaseUserControl than doing it at a page level - but its still good programming practice... All you have to do is create a class "BasePage" which inherits from System.Web.UI.Page then make all your aspx pages inherit from BasePage not System.Web.UI.Page.

Anyway here's how i added in the Google Analytics tracking code...

First add the following constant to your BasePage:

public const string GOOGLE_ANALYTICS_JAVASCRIPT = 
@" 
<script type=""text/javascript""> 
/* Google TRACKING CODE */ 
var gaJsHost = ((""https:"" == document.location.protocol) ? ""https://ssl."" : ""http://www.""); 
document.write(unescape(""%3Cscript src='"" + gaJsHost + ""google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"")); 
</script> 
<script type=""text/javascript""> 
var pageTracker = _gat._getTracker(""{0}""); 
pageTracker._initData(); 
pageTracker._trackPageview(); 
/* Google TRACKING CODE */ 
</script> 
"; 

Second add the following AppSetting key to your web.config (making sure to replace the UA-XXXXXXX-X with your unique tracking code key generated in your Google Analytics control panel):

<add key="GoogleAnalyticsKey" value="UA-XXXXXXX-X"/> 

Finally add the following to your BasePage constructor:

if (!string.IsNullOrEmpty(WebConfigurationManager.AppSettings["GoogleAnalyticsKey"]) && !Page.IsClientScriptBlockRegistered("GoogleAnalytics")) 
    Page.RegisterClientScriptBlock("GoogleAnalytics", string.Format(GOOGLE_ANALYTICS_JAVASCRIPT, WebConfigurationManager.AppSettings["GoogleAnalyticsKey"])); 

Now run your site! What the above code does is check if an AppSetting called GoogleAnalyticsKey exists, if it does it registers a client script block containing Google Analytics new tracking code (javascript) adding your unique tracking code pulled from the AppSettings key.... bingo! Now you can re-use your BasePage on other projects/sites and so long as you remember to add the relevant GoogleAnalyticsKey to the AppSettings you'll have Google stats.

Some of you might wonder why i'm using obsolete methods (Visual Studio will complain and tell you to use Page.ClientScript..... instead) but this is because i've had issues whereby the IsClientScriptBlockRegistered would return incorrect results - namely false every time, so until Microsoft fix it (probably in .NET 3.5) I'll continue doing it old school.


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist