CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Peter's Gekko

public Blog MyNotepad : Imho { }

October 2005 - Posts

  • Getting my head around HTTPheader and HTMLheader. But no favicon yet.

    Thanks to the great feedback on my last post on Response.AppendHeader I'm beginning to see some light. I was indeed naive Response.AppendHeader appends a HTTP header to the response. A HTTP header indicates things like downloadable files. I was trying to manipulate an HTML header which steer the layout of the HTML rendered by the browser.

    The HTML returned to the browser can be manipulated in every detail by using the Response.Filter property. Page Brooks pointed to a nice article on completely customizing the HTML header. Which has its price and does not solve everything.

    The pages in my application need two links:

    • A link to a stylesheet. This link does not have to be in the head of the page to work. As Codebetter buddy Ben pointed out you can also put this in an user control. My app has a menubar used on all pages, the stylesheet is now dynamically set in the text of a literal control there. Which resembles my very first try at the problem (where I created literals on the fly in the init of the basepage) but without any dirty hacks. That's solved and clear.
    • A link to an icon. But even when this link is hard coded in the header of an aspx the icon does not show up. According to the HTML editor of VS (2003 and 2005) the "Shortcut Icon" value for the REL attribute is unknown. The icon shows on the default.aspx main page because it is in the root and has the default FavIcon.ico name. But as it looks now asp.net is blank on it.

    As the last post had such great feedback I'm not giving up yet. Does anybody know the way to set an icon from code ?

  • Response.AppendHeader, Webforms and favicons

    Perhaps I'm too naive on this but I'm stuck. Having turned the web upside down twice I'm still completely puzzled.

    In the header of an aspx page's response is some meta-information to help the browser. One of the headers can be the stylesheet to use when rendering the page. Having linked a stylesheet in the webform designer (Format|Document styles in VS 2003) your page header will look something like this.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
    <HTML>
    <HEAD>
    <title>Onderwijs</title>
    <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR">
    <meta content="C#" name="CODE_LANGUAGE">
    <meta content="JavaScript" name="vs_defaultClientScript">
    <meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">
    <LINK href="StyleSheetIndato.css" type="text/css" rel="stylesheet">
    </HEAD>
    <body>

    Linking a stylesheet this way has two disadvantages

    1. You have to repeat it for every page in the application
    2. It is hard to maintain, switching style sheets requires editing all aspx files

    Being an OOP guy I consider the best way to link the sheet is in my page base class. The Response object has an AppendHeader method, so this could be a nice way to set the stylesheet:

     protected override void OnPreRender(EventArgs e)
     {
       base.OnPreRender (e);
       Response.AppendHeader("LINK", string.Format("rel='stylesheet' type='text/css' href='{0}'></link>", System.Configuration.ConfigurationSettings.AppSettings["StyleSheet"]));
     }
     

    The code tries to add a header to the response which contains the link to the stylesheet.

    Alas this does not work. In all my tries the AppendHeader method does nothing at all when the response is rendered by a webform. And I've found no nice event to hook into or method to override. I had this frustration before, at the time the solution was to wrestle in the stylesheet link in the page body pretending it was a snippet of script.

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad (e);
        RegisterClientScriptBlock("MyStyleSheet", string.Format("<link rel='stylesheet' type='text/css' href='{0}'></link>", System.Configuration.ConfigurationSettings.AppSettings["StyleSheet"]));
    }       
     

    I'm still doing that, but recently revisited the subject and gave it another try. Another thing to put in a header is a shortcut icon, which is used in the browser's toolbar and favorites.

    <HEAD>
      <LINK REL="SHORTCUT ICON" HREF="http://www.mydomain.com/myicon.ico">
      <TITLE>My Title</TITLE>
    </HEAD>

    This looks simple, but neither VS 2003 nor VS 2005 accepts even the markup. Let alone anything like Response.AppendHeader or injecting the link in the body will work.

    As we say in Dutch it's giving me a pointed head. Any suggestion is more than welcome

  • Installing VS 2003 and VS 2005 on the same machine

    Like everybody else I downloaded and installed my copy of VS 2005. Downloading took some patience but installation was a snap. Once again the Daemon tools virtual DVD drive worked flawless. Didn't burn the image to disk, I even didn't have to rename the image file. Quite unlike Geoff I did not install on a clean machine but on my regular desktop. This machine is running VS 2003 every day but hadn' t seen any beta yet. Setup was without a glitch, I think I even beat Jeremy. No problems with the MSXML parser, only MS anti-spyware asked twice for an approval. Once in the framework and once for the debugger. The nice thing is that MS anti-spyware doesn't block the installation. There was just a blue prompt waiting for me, in the mean time the installation had rolled on.

    Do VS 2003 (.NET 1.1) and VS 2005 (.NET 2.0) work together on one machine ? Seeing is believing

    They do. At the same time. Side by side, including MSDN documentation. As the differences between asp.net 1.1 and 2.0 are pretty big I don't feel like converting my apps to 2.0 straight away. And I don't have to.

  • Real world database maintenance with Red-Gate SQL compare

    At this moment I'm working on an application which is based on an existing SQL server database. This database is precious as it holds a big amount of proven data as well as a large amount of proven business logic in it's stored procedures and triggers. I do not want to discuss here if that is the right place to store business logic; it is there and works well. If it ain't broken..... My application will add new functionality to the database so changes are inevitable. Up till recently I had one central document to list all alterations. This document went to the sa which would do the updates. Which could be difficult; how do you want to make clear you only changed line 127 of viewX ? A better way would to update be a script.

    This is where I started working with the Red Gate SQL bundle. In this post I'll explore SQL compare, one of the tools in the bundle. SQL compare compares two databases. These databases can be anywhere: on another machine, or even on two different other machines; it takes two ordinary connection dialogs to get them. Having connected SQL compare will analyze these objects in the databases

    • Tables
    • Views
    • Stored procedures
    • Users
    • Roles
    • Rules
    • Defaults
    • User Defined Data types (UDT's)
    • User Defined Functions (UDF's)
    • Full Text catalogs

    For each object it generates

    • A creation script for the object in database1
    • A creation script for the object in database2
    • A script to change the version in database1 to the version in database2
    • A script to change the version in database2 to the version in database1

    The two creation scripts are listed side by side with colored highlighting of the differences

    All objects are in one big list. To get an overview you use the Status filter and object filter toolbar. The first one filters on the kind of difference : Identical, Missing, Additional or Different. The object filter speaks for itself. These filters work very well but suffer somewhat from the UI choices made for the tool. Normally you see that a toolbar button is selected by its sunken appearance. Instead SQL compare changes just the border of the button. A button with a border stands for selected, one without for deselected. No big deal but just a little hard to get used to.

    Having analyzed the database and generated scripts SQL compare can also apply these changes. The nice thing is that setting which of these changes should be applied is very fine grained. Just (de-) select the checkbox.

    I find myself deselecting users and roles. Usually this does not work well. Even if the login is known in the database, SQL server will not correctly recognize the database users and their roles you're trying to import. Now SQL compare can execute the script. It will fire up a wizard. The first step is extremely important. You can either change db1 to be identical to db2 or the other way round. Making the wrong choice would result in a loss of all updates in my new database :o An option you will find in all steps of the wizard is save script. It will result in a big (in my case over 64K) sql script which you can pass to the sa who can run it in (FI) SQA. The quality of the script is good. The whole database change is transacted; in case the update crashes it rolls back. Neat.

    So far this might almost sound like magic. It almost is; but real magic is a miracle. What you always should do is re-compare the databases after running the update script. This is an option in the last step of the wizard as well.

    In my case I found out SQL compare had missed one weird constraint. So there is some handwork left but compared to my original way of working I'm in heaven. 99% of the work is automated and I can check the results with the same tool. The output of the tools are plain sqlscripts which every dba will accept. Even if they never heard of Red-Gate. In that case they are missing something. This product is really recommended, they are a good friend of Codebetter.

    Posted Oct 28 2005, 12:17 PM by pvanooijen with 13 comment(s)
    Filed under:
  • 3 years of DotNed, C# 3.0, innovation and retirement

    Yesterday Dutch user group DotNed held a meeting to celebrate it's 3d year. The main presentation was by Dennis Vroegop. <Update Dennis is not Dennis. Sorry, sorry, sorry> He had visited the PDC, blogged about it and gave a presentation on the most impressive stuff he had seen. Also Dennis has a background in Turbo Pascal and Delphi, no wonder its was all about Anders's new creations C# 3.0 and LinQ

    The audience was quite impressed

    but also somewhat confused. For example, it does take some time to understand that this
    var lijst = data.Where( s => s.Length > 3 );
     

    is a valid C# statement. To understand what it means is the next step. The good thing about an user group meeting is the live discussion. Some of the first reactions were "I don't want/need that". Later on that changed to "I don't understand that". In the end "Perhaps it does make sense". Learning together, that's a community.

    The main thing C# 3 does is generating code. Code which runs in the same CLR as C# 2.0. Behind the scenes the majority of  the weird statements is translated into several lines of "regular" C# 2 code. When you're coding now your code most likely follows a pattern. You're repeating the same idea over and over again. Shorthand C# 3 (and (D)Linq) code generates code following a pattern. Now if you accept that pattern and believe it does things the right way you're set. Personally I don't believe the patterns I'm using are any better then those C# 3 is using. I am looking forward to start really using it.

    The overall theme of the meeting was innovation. DotNed has been three years of innovation. Innovation means learning, learning and learning more; until you finally have to give up and retire. For the moment user groups keep me young, I'm not giving up yet. DotNed chairman Michiel van Otegem can look back on a good meeting.

    My pension is expected in 17 years. Hope to celebrate it with 20 years of DotNed.

    Query On !

  • Protecting an ASP.NET page against malicious input with ValidateRequest (A potentially dangerous Request.Form value was detected)

    By default ASP.NET jumps through some hoops to protect your asp.net applications against malicious user input. It does this by scanning the data post back on tags which might contain unintended markup or even script. Take a page where the users enters something like

    Now on postback asp.net will raise an exception

    It suspects the <SCRIPT> piece of text. It will also suspect something like <B>

    To prevent this you have to set the page directive validateRequest to false

    Now all user input is accepted.

    But you can still scan the user input for malicious input by using the ValidateInput() method of the Request. This methods validates three parts of the input

    • Form variables
    • QueryString
    • Cookies

    It does work in a somewhat strange matter. At first sight nothing happens. But the moment you touch one of the parts it is validated. In case it does contain suspected input an HttpRequestValidationException exception is thrown. This snippet of code demonstrates how to work with ValidateInput.

            [Flags]
            public enum RequestValid
            {
                AllInValid = 0,
                FormValid = 1,
                QueryStringValid = 2,
                CookiesValid = 4
            }


            private RequestValid validateRequest()
            {
                RequestValid isValid = RequestValid.AllInValid;
                Request.ValidateInput();
                try
                {
                    object touchForm = Request.Form;
                    isValid = isValid | RequestValid.FormValid;
                }
                catch(HttpRequestValidationException)
                {
                    // Take action
                }
                try
                {
                    object touchQueryString = Request.QueryString;
                    isValid = isValid | RequestValid.QueryStringValid;
                }
                catch(HttpRequestValidationException)
                {
                    // Take action
                }
                try
                {
                    object touchCookies = Request.Cookies;
                    isValid = isValid | RequestValid.CookiesValid;
                }
                catch(HttpRequestValidationException)
                {
                    // Take action
                }
                return isValid;
            }

     

    You cannot influence what ValidateInput will scan for, that's hard coded. But it does issue a warning and you know what part of the input needs a closer investigation.

  • Building an Excel sheet in C# the easy way

    My application had to produce a worksheet for Excel. At first I took Visual Studio Tools for Office (aka Visto). Very very nice but it gave me a hard time in registration problems with the Office COM servers used. Fearing a deployment nightmare I took the easy classical road: just create a CSV text file.

    A CSV file is quite simple

    • A text line is a row in the sheet. CR/LF is the next row
    • Columns are delimited by ;
    • In a row you only specify the columns you need. A CSV file can be jagged

    The good thing is that you're not limited to outputting plain data. The contents of every cell in an Excel sheet can be represented as a plain string. An Excel formula is a plain string which starts with "=". This snippet writes a 3 column sheet-row for every row of data. You will recognize the second column as a formula.

    StreamWriter sw = new StreamWriter(saveFileDialog1.FileName, false);


    DataView dv = new DataView(data.Regel);
     

    lc++;
    for (int i = 0; i < dv.Count; i++)
    {

         DataRowView dr = dv[i];

         sw.Write(dr["Breed"]);
         sw.Write(";");

         if (isTweeDimensionaal)
            sw.Write(string.Format("=(G{0}/1000)*(H{0}/1000)", lc));
         else
            sw.Write(string.Format("=G{0}/1000", lc));
         sw.Write(";");
         sw.Write(dr["Aantal"]);
         sw.Write(";");

         sw.WriteLine();
         lc++;

    }
     

    The ; character not only separates the columns in a CSV file it is also separates the parameters of an Excel function. These would lead to an unexpected splitting of a column. To prevent this you wrap the contents of a column in double string quotes.

    sw.Write(string.Format("\"=MAX((C{0}/1000)*(D{0}/1000);E{0})\"", lc));
     

    It takes extra backslashes to escape the quotes, but Excel does calculate the maximum just as intended.

    A CSV cannot contain layout, charts, cross-sheet references and the like. But for something as simple as the thing I needed it's hasta la Visto.

  • (Mis-) understanding dataviews and databinding

    I was given a nice little Pocket PC application which worked well but took far too long to start. My first code walk through was a WTF. It was the common scenario, a well known major consulting firm producing code which demonstrated they did not quite understand what they were doing. But instead of just making fun of it in the WTF style, I'll approach it from the positive side as well with a walkthrough what I did with the problem.

    The compact framework has no typed datasets. Working with a database in a CF application can be a branch of sports on itself. As a pleasant alternative you can work with plain XML files to store data and use dataviews in you code to sort and query this data. The DataView class provides a view on one table in an XML dataset. I never thought it was such a hard thing to use, but this project did show some people do completely misunderstand it.

    The application was highly configurable. XML files contain things like the caption of labels and menu items. Like this

    - <controls>
      <screen>screen2</screen>
      <object>btnNext.label</object>
      <value>Next</value>
      </controls>
    - <controls>
      <screen>screen2</screen>
      <object>btnPrev.label</object>
      <value>Previous</value>
      </controls>
    - <controls>

    At startup the application loaded the xml-file in a dataset. Some snippets found to actually set a caption.

            public static DataView dvControls31;

    .....

            dvControls31 = new DataView(vfpdata.Tables["controls"]);
                    dvControls31.RowFilter = "screen = 'screen3' AND object = 'btnNext.label'";

    .....

            if (Welcome1.dvControls31.Count != 0)
                btnNext.DataBindings.Add(new Binding("Text", Welcome1.dvControls31, "value"));

     

    A dataview is declared, instantiated on a table after which the desired row is filtered out. After that the button gets its caption by data-binding it to the dataview.

    This does work but gets more complicated when you want to set a property which is not data-bindable, like a menu item. It took a dummy label, leading to this

         lblCHAR1.DataBindings.Clear();
         if (Welcome1.dvMenu3A.Count != 0)
         {
             lblCHAR1.DataBindings.Add(new Binding("Text", Welcome1.dvMenu3A, "value"));
             mnuAgenda.Text = lblCHAR1.Text;
         }
         lblCHAR1.DataBindings.Clear();
     

    The dataview row is bound to the label caption after which the label caption is directly assigned to the menu text. Which is part of the solution:

    • Databindings are meant to use with data which changes. For setting a value just once they are a total waste of time. Use a plain assignment there.

    Changing the databindings to straight assignments made the code leaner and faster. But the greatest WTF was where the data was. You might have noticed that the dataview objects presented so far have different names. It turned out the application declared and instantiated a separate dataview for every configurable control property in the application. Well over 40. No wonder startup took so long.

    You can do more with a dataview than just filtering out one row. The rows in a dataview can be sorted and after that you perform a find to get to a row. As a small demonstration, a part of  a class to read the configuration files:

        public class ConfigLoader : DataSet
        {
            private DataView controls;
            private DataView fields;

            public void LoadFields(string fromFile)
            {
                readXMLfile(fromFile);
                fields = new DataView(this.Tables["fields"]);
                fields.Sort = "field, attrib";
            }

            public string FieldCaption(string field)
            {
                object[] searchArgs = new object[2];
                searchArgs[0] = field;
                searchArgs[1] = "label";
                int rowNo = fields.Find(searchArgs);
                if (rowNo >= 0)
                    return fields[rowNo]["value"].ToString();
                else
                    return "";
            }
        }
    }
     

    The dataset wraps up a controls and a fields config file. Their contents is queried using one dataview each. The complicated code to set up the controls now reads like

    Config = new ConfigLoader();

    lblCHAR1.Text = Config.FieldCaption("CHAR1");
    lblCHAR2.Text = Config.FieldCaption("CHAR2");

    • Dataviews are a dynamic view on underlying data. Do not create a new dataview for a new view on the same underlying data

    Having made these changes the amount of code had dramatically shrunken. Startup time had halved.

    But there was more to gain. The application worked with a larger set of data. It displayed just a subset. A dataview would be an way to get this subset. The application did use two but each of them was on another dataset. A large part of the code was dedicated to keeping these two datasets in sink. Throwing out one of the datasets and opening both dataviews on the one left was another boost in performance.

    • You can have multiple Dataviews on the same data, each providing a different set of rows. Do not duplicate data

    And now I'll stop kicking in open doors. It's all in Visual Studio's help files. WTFM ?

  • MSDN activities in the Netherlands

    In the North we were the first to have the MSDN/TechNet briefings. A couple of times a year MS organizes this to present their latest news. New was the amount of fun in the presentations. Usually you are bombarded with flashy videos on a heavy techno beat. This time we had a hilarious video on how MS processes error reports. The keynote-slides were presented with a live water-cooled pc which outclassed the one I saw at Cebit. The keynote itself contained the usual, but nevertheless impressing demos of Vista and Office 12. It was the first time I saw Office 12. In one word : "Yessss".

    Despite all that the developer track was quite serious. Alex Thissen gave a very good and very interactive survey of building ASP.NET 2,0 apps with Visual Studio 2005. Not just bulleted list with new features, but in 3 sessions a lot of the real details were covered. I had done a lot with beta1, the current beta2 was less known to me. Now I really do understand the new compilation and code behind model. In the new model every page gets compiled into it's own DLL. I think it is OK but the only bug we've seen in all the demo's might have to do with it. It sometimes appears when you do a crosspost from one page to the other. Your app pops up an error complaining about "cannot cast WebForm1 to WebForm1" (or something like that). Inside the .NET runtime a class is not just identified with it's name but also by it's build number. Looks like the other webform is still looking for the previous version of WebForm1. One page got recompiled, the other didn't. Wild guess, but that does make sense to me. The good thing about the new compilation model is that your pages can inherit from a custom page class. You can do that in 1.1 but could not do that in beta1. The custom page base class goes in the APP_Code folder of the website; or in a referenced assembly. It might look a little confusing at start but at the end of the day I felt comfortable with the new style of website projects.

    The only thing I felt under-covered were object datasources. SqlDatasources on a webform are nice but, as somebody remarked, hinder the separation of layers into tiers. Objectdatasources were part of the talk but not of the live demo's. I did some exploring in beta 1 and it looks like they still work the same. Perhaps they will not automagically generate proxies for the object provider, needs some further exploration.

    Next week Alex will be giving his talks in the other parts of the country. Absolutely recommended. The launch of VS 2005 will be a big party over here as well. In yesterdays meeting the location was said to be a secret, but the pre-invitation and the video clearly state the old van Nelle factory in Rotterdam. What a  location ! See you there ?

  • Is this code available in VB ?

    This is a question I often get. I'm a C# guy but that is just a matter of personal preference. It's the framework not the language. Recently I thought I needed VB.NET to get COM event support working in .NET But I was wrong. So as long as I don't know every aspect and detail of C# I'll keep writing all my samples in C#. In case you want a VB version: may I again recommend the on-line VB.NET<->C# translator. Which works like a snap, to get a VB version of my Back button takes just a copy and a click.

  • Disabling the SQL Reporting Services cache

    SQL Reporting Services keeps a copy of every report rendered in its cache. Of course this is quite a performance boost when a lot of users request the same report over and over again. But in our case this cache turned against us. We have an ASP.NET app whose reports are included in the app as a bunch of links to the reporting server. The page assembles the URL with the parameters (more on that later) and redirects the user to the report. Every time the user opened a report she was confronted with the previous version rendered. Based on the previous parameters. Clicking the the RS toolbar's refresh button (NOT the browser's refresh button) led to the intended report.

    You can configure the caching of reports in great detail. It's under the execution tab in the report manager.

    But there is a little quirk in this. Even when you set the report to Render this report with the most recent data, the user is (in our scenario) still confronted with a cached copy. This is either a bug or a misunderstanding between me and RS, I've seen others. What helped me here was using the rs:ClearSession parameter. In our app this is now the base URL for a report.

    protected string reportServer
    {
        get
        {
            return System.Configuration.ConfigurationSettings.AppSettings["ReportServer"] + "{0}&rs:Parameters=false&rs:Command=Render&rs:ClearSession=true";
        }
    }
     

    The report name and parameters go into the {0}

    To be continued.

    Posted Oct 05 2005, 02:46 PM by pvanooijen with 13 comment(s)
    Filed under:
  • Guessing my way through Vista 5219 (on a Tablet PC)

    Last weekend I did a successful install of Vista 5219 on my old, faithful tiny tablet. This weekend I had hoped to play around with XAML-ing ink. Didn't get that far, had to do some guesswork first to get really started. First guess is the name of the OS. There are several  monikers:

    • According to the boot screen it's still Longhorn
    • The build part of  the PDC goods
    • The September CTP 2005
    • Beta 2

    The first one is according to MS old-fashioned, the latter according to Geoff premature. Well, this is a part of the desktop. It does state beta 2 in the lower right corner:

    To me it is the Tablet build. Tablet functionality is great but has one little quirk which prevented me getting to the XAML stuff. Occasionally the TIP (Tablet Input Panel) stalls the machine. From time to time this was so bad I had to try to find out what was going on. It took some trial, error and guesswork but I think I've got the TIP more or less under control.

    • Disable automatic data collection
    • Do no use the Sym, or Web buttons

    The latter one is a good way to demonstrate the stall. The web button shows some quick text fragments like http:\\, .com and the top level domain according to the localization settings of the machine. So it interacts with the machine settings. Automatic data collection interacts with an external store. My wild, wild guess is that the TIP has a problem when it has to interact with something else then just the textbox it is filling. Crossing a process boundary ? Just a wild guess. But the stall does prevent my tiny tablet from being usable. But as long as I don't click them build 5219 is a delight to use. Also on my hardware. I can play chess, Inkball, browse codebetter and write a text in WordPad at the same time. No problem. But next weekend I really want to do something more serious.

More Posts