This post is about Glimpse 0.82 which is obsolete, for version 0.83+ check the official documentation at getglimpse.com/Help

In version 0.82 the Glimpse guys changed several aspects of the Glimpse package.

In this post I describe how to manually install Glimpse into your ASP.NET 4 web forms web site
without the need of Visual Studio, nuget, MVC or jquery. All you need is ASP.NET 4

First make sure your site in running under ASP.NET 4, you web.config should have

<compilation debug="false" targetFramework="4.0" />

Getting dependencies

Unless you have ASP.NET MVC or Web Matrix installed on your machine, you need a copy of the assembly:

Microsoft.Web.Infrastructure, Version=1.0.0.0

in your bin directory. Glimpse is using it.

It is part of ASP.NET Web Pages:

http://www.microsoft.com/downloads/en/details.aspx?FamilyID=300314DA-DEDD-4540-A236-A0DE0A5A534D&displaylang=en

Download the AspNetWebPages.msi and install it, or if you don't want to install it, extract the files on the command line:

msiexec /a C:\aspnetwebpages.msi /qb TARGETDIR=C:\temp 

You'll find the file "Microsoft.Web.Infrastructure.dll" under the path:

\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies

Copy the Microsoft.Web.Infrastructure.dll assembly into the bin directory of your web site.

You may also need the Json.Net Assembly, go to http://json.codeplex.com/ and download the JSON.NET 4.0 Release 2 zip file.

Extract the file: \Bin\Net35\Newtonsoft.Json.Net35.dll into your bin directory.

Get the glimpse Assembly

Download the nuget package manually from:

http://packages.nuget.org/v1/Package/Download/Glimpse/0.82

if you want to use Glimpse with MVC3 you also need this package:

http://packages.nuget.org/v1/Package/Download/Glimpse.mvc3/0.82

(these instructions are tested with version 0.82 only)

Rename Glimpse-0.82.nupkg to Glimpse-0.82.zip and extract the file \lib\net40\Glimpse.Core.dll into your bin directory.

For MVC do the same and copy \lib\net40\Glimpse.Mvc3.dll into you bin directory.

For web forms usage only:

Create a "glimpse" directory in the root of your web site,

Download the following files into that directory:

https://github.com/Glimpse/Glimpse/raw/master/source/Glimpse.Core/glimpseClient.js
https://github.com/Glimpse/Glimpse/blob/master/source/Glimpse.Core/glimpseSprite.png?raw=true
https://github.com/Glimpse/Glimpse/blob/master/source/Glimpse.Core/glimpseLogo.png?raw=true

You should now have the these files in the glimpse directory:

glimpseClient.js
glimpseSprite.png
glimpseLogo.png

Configuration:

Finally you need to add Glimpse to your web.config:

add or integrate into the configSections at the very top of the file (just after <configuration>)

<configSections> 
    <section name="glimpse" type="Glimpse.Core.Configuration.GlimpseConfiguration" /> 
<configSections> 

Add the following section anywhere within <configuration>

<glimpse enabled="true" /> 

Now you should be good to go, enter yoursite.com/glimpse/config in a browser and turn on Glimpse.


 
Categories: ASP.Net

This post is about Glimpse 0.81 which is obsolete, check this post about newer versions.

If you are not using MVC yet, but like to use some of the goodness of glimpse in your web forms sites, read on.

Requirements for the 0.81 version of Glimpse:

- A web forms site using ASP.NET 4
- jquery.js included on any page you want to glimpse into.
- MVC 3 (http://www.asp.net/mvc/mvc3)

Using nuget to add glimpse to your site in Visual Studio

Get nuget at http://nuget.codeplex.com/.

Use the nuget Package Manager Console,

or right click on your site and use the 'Add Library Package Reference'.

Install-Package Glimpse

in the console, or search for Glimpse in the GUI. The glimpse page on the nuget gallery is at
http://nuget.org/List/Packages/Glimpse

Manually add glimpse without Visual Studio

If you can't or don't want to install nuget, you can add Glimpse to your site manually.

Open the url http://packages.nuget.org/v1/Package/Download/Glimpse/0.81 to download the package.
Rename the .nupkg file to .zip and open it, navigate into /lib/net40 and copy the Glimpse.Net.dll
into the bin directory of your site.

Now open your web.config,

add or integrate the config section at the very top of the file (just after <configuration>)

<configSections>
    <section name="glimpse" type="Glimpse.Net.Configuration.GlimpseConfiguration" />
</configSections>
Add the following section anywhere within <configuration>
<glimpse on="true" saveRequestCount="5">
    <ipAddresses>
        <add address="127.0.0.1" /><!--IPv4-->
        <add address="::1" /><!--IPv6-->
    </ipAddresses>
    <contentTypes>
        <add contentType="text/html"/>
    </contentTypes>
</glimpse>

The Client Site Component

For MVC the required client site component is accessed through a route to /glimpse/glimpseclient.js
You could do the same in web forms, but you could also just drop the js file into that location.

Get the JavaScript file from https://github.com/Glimpse/Glimpse/blob/master/source/Glimpse.Net/glimpseClient.js
and save it into /glimpse/glimpseclient.js in your site.

You also want to grab the image at https://github.com/Glimpse/Glimpse/blob/master/source/Glimpse.Net/glimpseSprite.png
and save it as /glimpse/glimpseSprite.png.

Now enable glimpse by opening /glimpse/config on your site and click the 'Turn Glimpse On' button.

Any aspx page that has jQuery included, you should now see the glimpse button in the lower right corner, click it to open the glimpse panel.

Optional:
In your web.config add

<pluginBlacklist>
    <add plugin="Glimpse.Net.Plugin.Mvc.MetaData"/>
    <add plugin="Glimpse.Net.Plugin.Mvc.Binders"/>
    <add plugin="Glimpse.Net.Plugin.Mvc.Execution"/>
    <add plugin="Glimpse.Net.Plugin.Mvc.Routes"/>
    <add plugin="Glimpse.Net.Plugin.Mvc.Views"/>
</pluginBlacklist>


to the <glimpse section, this will hide the MVC extensions which are useless for web forms.

Troubleshooting:

On one of my sites, the /glimpse/config page did not work, that was because I had Firefox's 'Content-Security-Policy' enabled
on the site which does not allow JavaScript to be executed within a html page. Make sure to turn that off for glimpse.

How does it all work?

I asked myself, how the additional glimpse JavaScript gets onto my page, after all I did not make any changes to the web.config
except for the configuration. I look at the source of glimpse reveals that they are using the new ASP.NET 4 feature 'PreApplicationStartMethod'
to add a http module to a site by just dropping an assembly into the bin directory. No changes to the web.config files are required.
Learn more about this at  nikhilk.net


 
Categories: ASP.Net

I had a similar error on IIS proper but it came back after I installed Visual Studio 2010 SP1 and started using IIS Express.

ASP.NET works right out of the box, but they first call to a classic ASP page results in the basic:

HTTP/1.1 New Application Failed

error page.

The problem is actually the same, I have a

<system.webServer><asp>

section in my web.config and IIS does not allow this, but could really come up
with a better message to tell me.

To fix this in IIS Express, find the configuration file, in my case at C:\Users\username\Documents\IISExpress\config\applicationhost.config.
After making a backup, open it in a text editor and find the line

<section name="asp" overrideModeDefault="Deny" />
change the Deny to Allow.
 
Categories: ASP.Net

On some of my sites I am using an HttpModule to check for an existing Session value and if not found, redirect to a logon page.

I only to this for *.aspx and other dynamic content pages but not for *.css or *.js files. ASP.NET actually does not provide the session object for those static files anyways.

After installing SP1 on my developer workstation with Windows 7 64Bit I noticed a problem with some of my sites.

On pages where I use Ajax with json to call ASP.NET WebMethods, the HttpModule now kicks in and redirects to the login page, breaking the ajax request. Normal pages and non-webmethod ajax calls continue to work fine.

When I debugged the HttpModule I noticed that the HttpContext.Current.Session object is null when calling the webmethod.
This was working fine before SP1 and as I had no choice but going back. It worked again after I uninstalled the Service Pack.

I did some more investigation and found that before SP1 a hit to a page with some Ajax calls would result in the following
requests processed by the HttpModule:

/default.aspx: yes
/Styles/Site.css: no
/default.js: no
/Scripts/jquery-1.4.1.min.js: no
/service.aspx: yes
/service.aspx: yes
/service.aspx: yes

After installing SP1 the same page would result in these requests:

/default.aspx: yes
/Styles/Site.css: no
/default.js: no
/Scripts/jquery-1.4.1.min.js: no
/service.aspx/WebMethodTest: no
/service.aspx: yes
/service.aspx/WebMethodTest: no
/service.aspx: yes
/service.aspx/WebMethodTest: no
/service.aspx: yes

Notice that for a single Ajax Call to the webMethod there are now two requests and the one with the WebMethod name in
the URL has no session object.

As my logic can not find a session object for an *.aspx request it assumes there is no valid user and displays the logon page.

My workaround is to check for a name after the filename and exclude those requests as well. These URLs are internal
on the server and can not be changed by the browser otherwise a hacker could try to trick me into thinking I don't have
to authenticate this request.

So if you are doing something similar, be aware of this change.

P.S. This happened with ASP.NET 4.0 in integrated Pipeline mode, I don't know whether it affects other configurations as well. Also I could reproduce this on two machines, but not on a third one that did not have Visual Studio installed. I haven't tested this on Server 2008 R2 SP1 yet.

Update 3-March-2011: The ASP.NET team is now aware of this problem and has confirmed to me that this is an issue they will fix in a future service pack for .NET 4.  As a workaround they suggest to move the 'ExtensionlessUrl-Integrated-4.0' handler further down in the list of handlers. To do this open the file: C:\Windows\System32\inetsrv\config\applicationHost.config and search for ExtensionlessUrl-Integrated-4.0, move the whole line down within the handlers block just above the last entry (StaticFile).

Thanks to Scott and Stefan for their quick help on this one.


 
Categories: ASP.Net

I had a problem with a classic ASP site on a new Windows 7 Dev machine:

When opening the site for the first time, a white page with a single line appears:

HTTP/1.1 New Application Failed

I enabled "failed request tracing" in IIS and found out that the internal error is "ASP 0145"
but there is no further information about this problem online.

I retraced my recent steps and after removing the

<system.webServer><asp> 

section from the web.config file, the problem went away.

It turns out that in IIS on the server level, the Feature Delegation for "ASP" was set to "Read Only",
so if you still have the settings in the web.config, you get this error.

I enabled Read/Write delegation for ASP and now the settings in the web.config work fine.


 
Categories: ASP.Net

When executing aspnet_merge.exe to merge the compiled assemblies in a web site, I got the following error:

An error occurred when merging assemblies: ILMerge.Merge:  There were errors reported in App_Web_ngsd3smh's metadata.
Module or assembly depends on a version of core library that is newer than the one the Reader uses to resolve system types.

I triple checked the assembly in question and all related assemblies and they were all 4.0 ones.

I found nothing about this error on the web, that's because it was my own stupidity.

I used PowerShell with a current working directory of:

[C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools]

and started the command with

aspnet_merge.exe C:\precompiledSite -o targetassembly.dll

The problem was that I have the 2.0 Version of aspnet_merge.exe in my tools directory
which is also in the path. So the command above executes the old version, hence the errors.

The correct call is of course:

.\aspnet_merge.exe C:\precompiledSite -o targetassembly.dll

 
Categories: ASP.Net

I have a classic many-to-many relationship, for example persons are members in clubs:


in the UI for a person I need to display the clubs the person is a member of and all the
other clubs to allow the person to join another club.

In T-SQL I would do this like this (assume 3 is my personId)

SELECT Name FROM Clubs
WHERE ClubId IN
(SELECT ClubId FROM Members WHERE PersonId = 3)

SELECT Name FROM Clubs
WHERE ClubId NOT IN
(SELECT ClubId FROM Members WHERE PersonId = 3)
 
then I moved to Linq To SQL

from c in db.Clubs
join m in db.Members
on c.ClubId equals m.ClubId
where m.PersonId == 3
select new { Name = c.Name };

from clubs in db.Clubs
where !(
from members in db.Members
where members.PersonId == 3
select members.ClubId
).Contains(clubs.ClubId)
select new { Name = clubs.Name };

On to Linq to Entities, here the junction table 'members' is hidden
and I have use the persons collection on Clubs:

from c in context.Clubs
from p in c.Persons
where p.PersonId == 3
select new { Name = c.Name };

from c in context.Clubs
where !(from n in context.Clubs
from p in n.Persons
where p.PersonId == 3
select n.ClubId
).Contains(c.ClubId)
select new {Name = c.Name};

The generated SQL for LinqToSql and LinqToEntities is similar but both are quite different from the hand written SQL. However the execution plans for all three are nearly the same.
 
Categories: ASP.Net | SQL Server

My use case for this requirement was that users can add text on a web site and could include links (a tags). I want all these links
to open a new window when clicked on. So I needed to add a target="_blank" to all the a tags in the text.

My first idea was to use pure Regular Expressions, but I couldn't figure out how to use the negative lookahead feature, I needed
this because I can't simple add a target="_blank" to each a tag, what if it already has one?

So instead I am using a simple Regex to find the appropriate tags and then use a MatchEvaluator, which is a very cool
feature of the .Net regular expression class. It allows you to compute the replacement string in a method for each
match.

public class HtmlHelper
{
    string _attrib = string.Empty;

    public string AddAttribute(string source, string tagName, string attrib)
    {
        _attrib = attrib;

        string term = "<" + tagName + " [^>]+>";

        Regex r = new Regex(term, RegexOptions.IgnoreCase);

        MatchEvaluator myEvaluator = new MatchEvaluator(this.ProcessMatch);

        return r.Replace(source, myEvaluator);

    }

    private string ProcessMatch(Match m)
    {
        string tag = m.Value;
        if (tag.IndexOf(_attrib) == -1)
        {
            tag = tag.Replace(">", " " + _attrib + ">");
        }

        return tag;
    }
}
This can then be used like this:
HtmlHelper helper = new HtmlHelper();
htmlText = helper.AddAttribute(htmlText, "a", "target=\"_blank\"");

 
Categories: ASP.Net | Web

Two issues I had to fight with today which took longer to solve than I originally thought.

First I looked into a problem I had since the last new version of my travelog website. When I add a new entry for the day, the date and time is send along with the other data to the server with an http post. But every time I save a entry the time is reduced by 7 hours. So after a few edits even the day itself is wrong.
I can explain the 7 hours, that the time difference between where I am (Peru) and the server (Germany), but where does it come from?

I looked at the database server and it has the time as specified in the web form. So the issue is getting the data back from the server. I checked my JavaScript which pulls the data via AJAX from the server and puts it onto the form, that looked fine too. Then I used Firebug to look at the JSON data that comes from the web server. The time there is seven hours less than whats in the database! I use ASP.NET web methods to return an object serialized as JSON.

The JSON serializer figures out, the client is 7 hours behind the server and takes those seven hours off each DateTime value. How does the server even know about those seven hours. I first thought about the http request headers but there is no time zone information in there. Well I still don´t know how it knows.

To fix the problem I send the timezoneOffset in the browser available through the JavaScript getTimezoneOffset() method when making a request. On the server I find out the time zone offset of the server and then fix all DateTime fields before sending them down. Not nice but it works.

  // get a local time zone info
  TimeZoneInfo tz = TimeZoneInfo.Local;

  // get it in hours
  int offset = tz.BaseUtcOffset.Hours;

  // add one hour if we are in daylight savings
  if (tz.IsDaylightSavingTime(DateTime.Now))
  {
      offset++;
  }

The second problem I had was with a very simple site, I added an aspx page to download zip files from the site and log the downloads. It worked fine locally but on the live server the line:

if (System.IO.File.Exists(fileName)
which makes sure the file exists before it is trying to send it returned false. I triple checked that the file was there and it was. I checked NTFS permissions and IIS settings, all fine. I could download the file directly via http from the same location. I ran ProcessMonitor on the server and the web process didn't even try to open the file. What is going on here?

I finally removed the line above and let the Response.TransmitFile method throw the error, now it told me what was going on. I had set the site to Medium Trust because it only performed very basic operations. In Medium Trust your application is not allowed to access the file system.

So in this case System.IO.File.Exists returns a false, but shouldn´t it really throw an exception? I am not sure, but it made it harder to debug.
 
Categories: ASP.Net

On TweeNet to access a band page, the old url is


www.twee.net/bands/band.asp?key=smiths

You have to know the internal name of the band as well.

Now you can get to the same page using

www.twee.net/band/The Smiths

The site runs under ASP.Net 3.5 SP1 on IIS6, so I have all the routing goodness of that ASP.Net version.
To get this working, I first had to add the routing assemblies and modules to my web.config

<assemblies>
        <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
      
<httpmodules>
      <add type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, 
             Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" name="UrlRoutingModule" />
</httpmodules>


These are additions to the already existing assemblies and modules which are not listed here.

Next I implemented a routing handler:

using System;
using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
using System.Web.UI;

namespace TweeNet.PublicSite.RouteHandlers
{
    public class BandRouteHandler : IRouteHandler
    {
        public BandRouteHandler(string virtualPath)
        {
            this.VirtualPath = virtualPath;
        }

        public string VirtualPath { get; private set; }

        public IHttpHandler GetHttpHandler(RequestContext
              requestContext)
        {

            foreach (var urlParm in requestContext.RouteData.Values)
            {
                requestContext.HttpContext.Items[urlParm.Key] = urlParm.Value;
            }

            var page = BuildManager.CreateInstanceFromVirtualPath
                 (VirtualPath, typeof(Page)) as IHttpHandler;
            return page;
        }
    }
}

then in Global.asax I added the route:
    protected void Application_Start(Object sender, EventArgs e)
    {
            RegisterRoutes(RouteTable.Routes);
    }
    
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.Add(new System.Web.Routing.Route
        (
             "band/{bandname}"
             , new TweeNet.PublicSite.RouteHandlers.BandRouteHandler("~/bands/band.aspx")
        ));
    }

In the code behind of the band.aspx page I added some code:
    if (HttpContext.Current.Items["bandname"] != null)
    {
        string userTerm = HttpContext.Current.Items["bandname"].ToString();

        // now use that term to find the band data in the database and display the page content
    }


The last problem was that IIS6 doesn't pass something like "The Smiths" to the ASP.net pipeline. Steve Sanderson's blog post describes some options how to deal with this 

But in my situation the best option was the following:
As a /band directory did not existed yet, I created that empty folder in the filesystem.

Then in IIS I selected it and created an application for it. I now added a wildcard application mapping to the asp.net runtime. So all requests under /band/ will go to asp.net.

All the other static files on the site are not affected. I then removed the application from '/band/'. IIS still keeps the wildcard mapping even if the folder is no longer an application and you can not see that mapping in the IIS Manager GUI.


 
Categories: ASP.Net

As I use more and more JavaScript and Ajax on my sites the number and the size of the JavaScript include files grow. What do to about this? jQuery comes in a uncompressed and minimized version, ideally I want to use the first on my Dev-box and the second on the production server. Asp.net 3.5 SP1 comes with the feature CompositeScript which allows us to combine several script into one download. Well, I am trying to get away from the asp.net ScriptManager, so I came up with my own solution.

The first version supports the following:

During development the fullsize script files are used

In Production mode, a single minimized file is used that includes all the JavaScript code needed for the page.

This file is not generated on the fly, instead it is stored on the file system. This makes it more scalable.

Here’s how in works:

All my common JavaScript include files are in one location /css/. Instead of referencing them with

<script src="../css/jquery.js" type="text/javascript"></script>
<script src="../css/topas.js" type="text/javascript"></script>

On the page, I just have a single literal control:

<asp:Literal runat="server" ID="litSciptIncludes"></asp:Literal>

Which results in the following html:

<script src="../css/lib/3.js" type="text/javascript"></script>

3.js has the compressed content of both jquery.js and topas.js

In the Page_Load method in code behind I have some code to add scripts:

ScriptIncluder inc = new ScriptIncluder();
inc.AddScript(ScriptIncluder.Scripts.jQuery);
inc.AddScript(ScriptIncluder.Scripts.Topas);
litSciptIncludes.Text = inc.GetIncludes();

The ScriptIncluder class is responsible for building the <script src…> tags. We add all the scripts we need on the page with the AddScript method, a fixed number of scripts are defined in a enumeration.

[Flags]
public enum Scripts
{
  jQuery = 1,
  Topas = 2,
  TopasDate = 16,
  TopasEdit = 32,
  jQueryUI = 256,
  jQueryMaskedEdit = 512,
}

public void AddScript(Scripts scriptID)
{
  _scripts.Add(scriptID);
  _scriptCode += (int)scriptID;
}

In my application framework I have a setting in the web.config which defines the Debug-Mode, when in development I just loop through all the added scripts and return one <script> tag for each added script. However when in production, I return a single script tag that points to a single file.

public string GetIncludes()
{
    string html = string.Empty;
    string start = &quot;&lt;script type=\&quot;text/javascript\&quot; src=\&quot;&quot; + Core.Config.VirtualRoot + &quot;css/&quot;;
    string end = &quot;\&quot;&gt;&lt;/script&gt;&quot; + Environment.NewLine;

    if (Core.Config.CurrentDebugLevel &gt;= Core.Config.DebugLevels.SharedDev)
    {
        // include plain text files
        foreach (Scripts script in _scripts)
        {
            html += start + _scriptNames[script] + end;
        }
    }
    else
    {
        // include compressed file
        html = start + &quot;lib/&quot; + _scriptCode + &quot;.js&quot; + end;
    }
    return html;
}

_scriptNames[] is a generic list of all the script file names, _scriptCode is the sum of all the scriptIDs as defined in the Scripts enumeration.

So where do all the /css/lib/nn.js files come from that are needed for the single compressed file?

I use a batch file to create them:

pushd \websites\foo\wwwroot\css
jsmin.exe lib/769.js jquerymin.js jqueryuimin.js jquery.maskedinput.js
jsmin.exe lib/819.js jquerymin.js jqueryuimin.js jquery.maskedinput.js topas.js topas.date.js topas.edit.js
jsmin.exe lib/49.js topas.js topas.date.js topas.edit.js
jsmin.exe lib/3.js topas.js jquerymin.js
jsmin.exe lib/1.js jquery.js
popd

In theory I need hundreds of these /lib/nnn.js files, but in practice there are only a few combinations that I use. What is jsmin.exe? It is a small tool based on Douglas Crawford’s jsmin (http://www.crockford.com/javascript/jsmin.html). He has C# code to minimize a single JavaScript file. I added some code to process any number of files into a single file. I can just run the batch file every time I have changed any of my JavaScript include files and can also make it part of my build process.

Any drawbacks? If every page uses a different combination of include files, all these combinations have to be downloaded once and then cached, so the actual jQuery code may be downloaded several times.


 
Categories: ASP.Net | Web

When using asp.net with control containers or master pages, the ID of the DHTML controls rendered on the client look something like 'ctl00$cphMain$btnFilter', this means accessing the known id 'btnFilter' with getElementById doesn't work. There are some asp.net workarounds for this such as
document.getElementById("<%= btnFilter.ClientID %>");
but I keep most of my Javascript in separate *.js files where I can't use asp.net.
The reason why asp.net is adding a prefix to the ID is to make sure the ID stays unique even when the control is used within a repeater or other databound control. In most of my cases however I do know that the ID I assigned is unique on the page and I needed an easy way to access the control in JavaScript.

My solution is to add a simple function to my common JavaScript code that is including on all pages so I can use it everywhere on the site.

The new function GetElementByTagAndPartialID which takes two parameters, the name of the tag such as 'input' or 'div' and the original ID. This function first gets a collection of all controls with the specified tag name on the page and then loops through them to return the first one that partially matches the ID given.
function GetElementByTagAndPartialID(tagname, partialID)
{
    
    var elementsWithTag = document.getElementsByTagName(tagname);
    var elementFound = null;
    
    if (elementsWithTag!=null)
    {
        for (i=0; i<elementsWithTag.length; i++) 
        {
            if (elementsWithTag[i].id.indexOf(partialID) > -1)
            {
                elementFound = elementsWithTag[i];
                break;
            } 
        }
    }

    return elementFound;
}
If the tag name is not known in advanced, one would have to loop through all DHTML controls on the page which would take a bit longer.
 
Categories: ASP.Net

After some major refactoring which involved namespace renaming I encountered the error "The type specified in the TypeName property of ObjectDataSource 'dsEntries' could not be found." on some pages where I use the ObjectDataSource control.

This is easy to fix by just changing the TypeName property to the new type, however I don't like the fact that my asp.net site compiles but still has these errors in it. So I moved the TypeName property from the aspx file to the Page_Load event in the codeBehind file. Rather than just using a string I use the following

dsEntries.TypeName = typeof(MyCompany.Project.Component.Directory.Entries).ToString();


Now this will no longer compile if I change the namespace, class or method names.

Declarative syntax is great but it has its drawbacks.


 
Categories: ASP.Net

October 6, 2007
@ 01:09 PM

I was going to encrypt the connection strings in some web.config files on a production server. I followed the instructions in the MSDN Library 'Walkthrough: Encrypting Configuration Information Using Protected Configuration'

http://msdn2.microsoft.com/en-us/library/dtkwfdky.aspx

However when I ran

aspnet_regiis.exe -pa "NetFrameworkConfigurationKey" "NT AUTHORITY\NETWORK SERVICE"

I got the following error:

Adding ACL for access to the RSA Key container...
Could not access the RSA key container. Make sure that the ACLs on the container
allow you to access it.
Failed!

I was running the command under an administrator account, so I expected to have all the access in the world. I used Process Monitor to find out where the problem was: The file

C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys\ d6d986f09a1ee04e24c949879fdb506c_34b3925e-0f96-4fb7-a312-e89b0b98f24a

Seems to have the RSA key and the ACLs of it allowed full access to SYSTEM and one other user that isn’t an administrative account. I added permissions for the Administrators group and now the command worked fine.


 
Categories: ASP.Net

September 24, 2007
@ 08:31 AM
I'm currently doing some consulting work in a company that is still exclusively using classic ASP as their web technology. There is no way for them to move to ASP.Net by starting over or migrating their whole application framework in one go. The idea now is to allow to write sub-modules in asp.net which are then integrated into the existing classic asp infrastructure. One problem here is the sharing of session variables which hold information about the logged on user including some permissions.

They did some research and found a solution that several people online recommend: Instead of calling an aspx page in a sub-modules directly, the source asp page calls a intermediate page which puts all existing asp session variables into hidden form fields and then auto-submits the form to an aspx page. That page then picks up the hidden values and puts it into an asp.net session. From now on the sub-module has a read-only copy of the asp session.

There are several problems with this approach:
  • The calling asp page has to know that it calls it non-asp sub-module and has to call the intermediate page instead passing in the url of the real target page.
  • The user will see a flicker when the intermediate page is shown and auto-submitted.
  • But by far the biggest problem is that the session variables are send to the client and then back to the server, just because they are in hidden fields doesn't mean they are save. It was always possible to change the data send back to the server and now with Firefox extensions it is very easy to change the values in hidden form fields before they are sent to the server. A user can just change the value of the user ID to someone else's or increase permissions.
The user can control the content of the session copied to the asp.net module. Session data should never leave the server.

Which brings me to the solution I recommend:
The calling asp page calls any aspx page without having to pass anything in or going through an intermediate page. In Page_Load each aspx page calls a method in a common class to check for an existing session. This class, lets call it UserState wraps all access to the asp.net session object. If there is no session object it knows it has to get it from the parent asp application. To do this it calls a special page ‘sessionstate.asp' which returns XML with all the session variables of the current user. The data in the session can then be made available as a hashtable, or even strongly typed properties if the various session variables are known during design time.
The UserState class uses an webRequest to call the sessionstate.asp page which is on the same server and the data transfer never leaves the server. To get the correct session variables UserState takes the ASP Session cookie and passes it along with the request to the sessionstate.asp page.
To make the call to sessionstate.asp more secure I use a simple secret string which both 'sessionstate.asp' and the UserState class share. This is also passed in as a cookie to 'sessionstate.asp' and only if it matches it's own value it returns data. One could also limit access to 'sessionstate.asp' to callers from the same IP address by using IIS access security. Or encrypt the session XML synchronously and have the two sides share the same key.

Sample code:

source.asp:
<%
  ' set some session variables
  Session("userid") = "10"
  Session("username") = "Peter"
  Session("permissions") = "31"  
%>
Sessionstate.asp:
    Dim mySessionSecret: mySessionSecret = "mysecret"   
    Dim sessionSecret: sessionSecret = Request.Cookies("SESSIONSECRET")
       
    Response.Write ("" & vbCrLf)
    Response.Write("" & vbCrLf)
    
    If mySessionSecret = sessionSecret Then
   
    	Dim sessitem
   
	For Each sessitem in Session.Contents
	  If IsObject(Session.Contents(sessitem)) Then
          ' do not do objects
	  Else
	  	If IsArray(Session.Contents(sessitem)) Then
	          ' or arrays in this version
	        Else
                  ' write a XML node for each variable
		   Response.write("" & Session.Contents(sessitem) & "" & vbCrLf )
		End If
	   End If
	Next 
		
    End If
		
    Response.Write("")   
target.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Net;
using System.IO;
using System.Text;

public partial class _Default : System.Web.UI.Page
{
    string _sessionSecret = "mysecret";
    string _classicASPSessionPage = "http://localhost/sessionstate.asp";

    protected void Page_Load(object sender, EventArgs e)
    {
       CheckSession();
    }

    private void CheckSession()
    {
        Uri hosturi = new Uri(_classicASPSessionPage);

        HttpWebRequest myWebRequest = (HttpWebRequest)WebRequest.Create(hosturi);
        myWebRequest.Method = "GET";
        myWebRequest.Timeout = 3000; // three seconds
        AddCookie(ref myWebRequest, hosturi);
        
        lblValues.Text = Server.HtmlEncode(GetResponseBody(myWebRequest));
    }

    private string GetResponseBody(HttpWebRequest myWebRequest)
    {
        WebResponse myWebResponse = myWebRequest.GetResponse();

        Stream ReceiveStream = myWebResponse.GetResponseStream();

        Encoding encode = System.Text.Encoding.GetEncoding("utf-8");

        // Pipe the stream to a higher level stream reader with the required encoding format. 
        StreamReader readStream = new StreamReader(ReceiveStream, encode);
        Char[] read = new Char[256];

        // Read 256 charcters at a time.    
        int count = readStream.Read(read, 0, 256);

        StringBuilder body = new StringBuilder();

        while (count > 0)
        {
            // Dump the 256 characters on a string and display the string onto the console.
            String str = new String(read, 0, count);
            body.Append(str);
            count = readStream.Read(read, 0, 256);
        }
        // Release the resources of stream object.
        readStream.Close();
        myWebResponse.Close();

        return body.ToString();
    }

    private void AddCookie(ref HttpWebRequest request, Uri url)
    {

        string aspSessionKey = string.Empty;
        string aspSessionValue = string.Empty;

        // find the classic asp session cookie, we need it
        foreach (string key in Request.Cookies.AllKeys)
        {
            if (key.StartsWith("ASPSESSION"))
            {
                aspSessionKey = key;
                aspSessionValue = Request.Cookies[key].Value.ToString();
            }
        }

        // add the asp session cookie
        Cookie cookie = new Cookie(aspSessionKey, aspSessionValue);
        cookie.Expires = DateTime.Now.AddHours(24);
        cookie.Domain = url.Host;
        cookie.Path = "/";

        // add a secret that only I and the target page know
        Cookie secretCookie = new Cookie("SESSIONSECRET", _sessionSecret);
        secretCookie.Expires = DateTime.Now.AddSeconds(10);
        secretCookie.Domain = url.Host;
        secretCookie.Path = "/";

        request.CookieContainer = new CookieContainer();
        request.CookieContainer.Add(cookie);
        request.CookieContainer.Add(secretCookie);
    }

}


 
Categories: ASP.Net

July 6, 2007
@ 10:04 PM

For a while now I'm getting certain errors in the error logs for www.twee.net:

Invalid postback or callback argument. Event validation is enabled using in configuration or in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

The source for this error is in System.Web and the type is 'System.ArgumentException'. My error logs also show all the user input from the submitted form and I can tell that someone is trying to spam me using the contact me page or other pages with forms.

I can also tell that they download the form page and change the values of text fields, checkboxes and dropdown lists.

There is not much I can do about this but the annoying thing is that I get an email every time they do this. The error is handled by my global error handling in Application_Error in global.asax but to handle this particular problem I needed to reproduce it first:

On my local development server I open the form page, then save the html for it into a local file and adjust the action attribute to point to the aspx page. Then I open the flat file in a browser and submit it, it works fine. Now I added a new option to a drop down box and pre-select it. Submitting the page now results in the exact same error as above.

I can now handle this error in a special way which logs it but doesn't send me an email.


 
Categories: ASP.Net

When using the aspnet_compiler.exe and having a theme defined in your web.config file, the compiler puts that theme into the page directive for every page file.

Not sure why they do this? I may be a performance thing. In my case however it is not helpful because after I uploaded my pre-compiled site to the production server, I'm using it for several different actual web sites which all use different themes. Because the page directive overrides the web.config setting all the sites end up with the same theme, the one that was set in web.config on my development machine.

So I added another step to my deployment script, after compiling the site I call:

rxfind.exe c:\projects\mysite\wwwroot\*.as?x /P:" theme=\"[a-z0-1]*\" " /R:" " /I /S /B:2

rxfind is a nice little .net based replace strings in files tool.


 
Categories: ASP.Net

So after finally migrating to Web Site projects and deploying my site to the production server things seem to work fine.

But I noticed dates like '13 Dezember 2006' were showing up on my pages, what's that all about? The production server has a German OS, that's why I have the following code in Global.asax.cs

protected void Application_BeginRequest(Object sender, EventArgs e)
{
   System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB");
   System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
}

This makes sure that whatever OS or user is running the app, the dates will always be in British format.

Why isn't this working anymore? It seems global.asax is not working correctly.

As far as I understand in the pre-compiled Web site projects you don't have a global.asax file in the root of your site anymore but instead a file called 'App_global.asax.compiled' in the bin directory. This was there alright.

When I copied global.asax back into the root I got the error that my Global class is defined in two places, once in the main precompiled assembly and secondly in a dynamic assembly for 'App_global.asax' and the runtime can't figure out which one to use.

The type 'ASP.global_asax' exists in both ...WebUI.DLL and ...8725\App_global.asax.ocwfm9jz.dll

So it seemed I can't have global.asax there but the runtime also doesn't fire 'bin/App_global.asax.compiled'!

I fired up Process Monitor (be carefull when running this on a production server, it slows things down) to have a look at the access to 'App_global.asax'. I set a filter 'Process Name is w3wp.exe' to limit the output. I couldn't see any references to 'App_global.asax' but saw that a file called 'PrecompiledApp.config' was not found. I remembered this file being created during the compilation of the site but my deployment script doesn't include it in the zip file that goes on to the server.

Well turns out, it has to be there. I copied it over and suddenly my dates worked. I guess the runtime looks for that file and only when it is found actually looks for the *.compiled files in /bin/.

 UPDATE:

Once again I noticed German month names in some XLST output, again an indication that Global.asax is not firing. You can actually fix this by setting the cultures in web config:

   <system.web>
      <globalization uiculture="de" culture="de-DE">
   <system.web>

 But I do more stuff in global.asax, especially global error handling.

So I spend some hours figuring out why global.asax is not executing on the live server. It worked fine on staging and also for other applications on the server. It didn't throw any errors anywhere but just didn't execute all at. I couldn't find anything about this problem online and changing web sites and application pools didn't help either. There were also no problems accessing any of the files. While looking at the assemblies in Reflector to check whether all the classes are there I noticed that there was a reference to Microsoft.Web.Extension, I had just replaced ASP.NET Ajax Beta 2 with RC1 where they changed the assembly to System.Web.Extension. The bin directory on staging had both assemblies while the live site only had the new one. After removing the reference to Microsoft.Web.Extension and rebuilding the assembly, the site worked fine. I wonder why this didn't threw an error and the rest of the site worked but global.asax didn't.


 
Categories: ASP.Net

After my last post about switching between these two types I actually started using it and found a major flaw in my solution. The problem is the declaration of asp.net and html control variables. Is WS these are done magically in the background and are in a partial class file generated during run-time. In WAP you put them in default.aspx.designer.cs files. So after using WS for a while there is no easy way to synchronize the designer.cs files with the controls on the aspx page. It would be nice if Visual Studio could do this and it doesn't at least not in my version which reports "Warning 24 Generation of designer file failed: Object reference not set to an instance of an object." a lot if not always. I also need to be Visual Studio independent.

So I did some more research and found out about VS 2005 Web Deployment Projects, which allow you do compile and merge all the assemblies in a WS project into a single assembly, better yet it is using the command line tool 'aspnet.merge.exe' so you could do this without Visual Studio.

So here's how I now build my deployment package for the staging and production servers.

For development in Visual Studio or Notepad2 I'm now always using WS, it is so much easier not having to recompile all the time. When I done with testing I run a batch file to create a new zip file with the files I need to copy to the servers.

As usualy I'm using some 4NT specific features so the batches wont work with cmd.exe but you get the idea.

I first compile the site with aspnet_compiler.exe into a new directory

I then use aspnet_merger.exe to merge all the assemblies into a single one for which I can specify a name.

I then run a second batch which compares the new directory with the previous version and adds all changed files into a zip file which I can then upload to the live server.

I ran into some small problems:

1. Apparently you can specify an assembly.cs file in aspnet_merger to specify the meta data for the single assembly. This didn't work but I needed to combine two different files anyway because I am using a common SolutionInfo.cs for all the different assemblies.

2. After I create the compiled site and try to run it, I get an error "Could not load type '__ASPNET_INHERITS'." in global.asax. The file has a single line "<%@ application inherits="__ASPNET_INHERITS" %>" what the heck is that? I found out that global.asax is not even suppose to be there anymore because the file bin/App_global.asax.compiled points the runtime to the correct class in the single assembly. So I just deleted global.asax and it works fine.

Here's the batch file for a specific project:

@ECHO OFF

SETLOCAL

set app=admin.twee.net
set target=%tnHostsDir\Deployment\%app\compiled
set iisSite=/LM/W3SVC/236937994/Root/Topas

:: empty target
rmdir /S /Q %target

:: create new assemblyinfo.cs
copy %tnHostsDir\Topas\SolutionInfo.cs %tnHostsDir\Topas\TweeNet.Topas.UI.Web.Info.cs %tnHostsDir\Topas\TweeNet.Topas.UI.Web\App_Code\AssemblyInfo.cs

:: compile site
%tnDnDir\aspnet_compiler.exe -m %iisSite %target -u -nologo

:: merge assemblies
%tnBinDir\aspnet_merge.exe %target -o TweeNet.Topas.WebUI -copyattrs

:: clean bin directory
PUSHD %target\bin
del /Q /E SongTitles.* WinTopas.* topas.*.* *.xml nunit*.* TweeNet.Topas.Unittests.* *.pdb 
POPD
:: and some files in the root
PUSHD %target
del /Q /E *.user *.sln *.csproj PrecompiledApp.config global.asax
POPD

:: now call the main script to find files to be updated
CALL %tnHostsDir\scripts\DeploymentPackageCommon.cmd %app

ENDLOCAL

@ECHO ON

And here's a more generic one to find the updated files:

@ECHO OFF

:: TweeNet Topas Deployment Synchronizer

:: Description:
:: =============
:: Batch to find all updated files to be copied to the live server.
:: The is done by comparing a source and a master directory.
:: 1. Find out which files are newer in the source
:: 2. Create a batch file to list those files which is 
::    then used to copy them but also as a history log
::    to see which files have been updated.
:: 3. Copy the new files into a unique directory
:: 4. Zip the content of that directory
:: 5. Copy the new files into the master directory
:: 6. Manually upload and deploy the files on the live server.

:: Usage:
:: =======
:: TopasDeploySync.cmd appName
:: where appName is one of the applications we use.

:: Requirements:
:: ==============
:: 4NT.exe 7.0 or higher is required for this
:: Robocopy.exe Version XP010 or higher
:: Windows NT 5.0 or higher

:: Assumptions:
:: ============= 
:: - All related files are in X:\hosts
:: - The source files are in X:\hosts\Deployment\appname\compiled
:: - The folders X:\hosts\Deployment\appname\wwwroot exists
:: - in the source \bin\TweeNet.Topas.Business.dll exists

:: History:
:: =========
:: Version 1.0 /  4-Oct-2006 Singapore
:: Version 1.1 / 12-Dec-2006 Singapore

:: Credits:
:: =========
:: written by Peter Hahndorf, contact me at www.twee.net/contact/ 

SETLOCAL

SET scriptVersion=1.0.10.0
SET scriptName=TopasDeploySync.cmd

:: we pass in the folder name for the application
SET appName=%1

:: the second parameter is optional for the hosts directory
:: if not specified we use \hosts on the current drive
iff %# == 2 then
	SET hostDir=%2
else
	SET hostDir=%@LEFT[1,%_CWD]:\hosts   
endiff

:: the third parameter is optional for the wwwroot directory
:: if not specified we use wwwroot on the current drive
iff %# == 3 then
	SET wwwrootDir=%3
else
	SET wwwrootDir=wwwroot   
endiff

:: sourceDir is the folder with the latest files
SET sourceDir=%hostDir\Deployment\%[appName]\compiled
:: masterDir the folder with the last updated version
SET masterDir=%hostDir\Deployment\%[appName]\wwwroot

:: get the version from the main assembly in the source
SET TopasVersion=%@VERINFO[%[sourceDir]\bin\TweeNet.Topas.Business.dll]

iff "%@files[%[sourceDir]\bin\TweeNet.Topas.Business.dll]" == "0" then
	@ECHO No Version information found at ^n%[sourceDir]\bin\TweeNet.Topas.Business.dll
	EXIT /B 15
endiff

:: if we have run this before, we need a new unique deployement directory
 iff "%@files[%hostDir\Deployment\%[appName]\%[TopasVersion]\]" == "0" then
	:: first time, deploy directory is simple the name and version
	SET uniqueToken=
 else
	:: Standard named directory is already there, use a different one
	:: winticks is pretty unique
	SET uniqueToken=_%_WINTICKS
 endiff

:: use the unique token we got above to build the name
SET DeployDir=%hostDir\Deployment\%[appName]\%[TopasVersion]%uniqueToken\

:: the zip file to use, u- stands for update, f- would be full
:: we use the same unique token as above
SET zipFile=u-%[appName]-vs%[TopasVersion]%[uniqueToken].zip 

:: the batch file we create and use to copy the files to be updated
SET batchFile=%hostDir\Deployment\%[appName]\TopasSync_vs%[TopasVersion].cmd

:: temp file to store list of files to be copied
SET tempFile=%TEMP\%[appName]-vs%[TopasVersion].temp
:: temp file to redirect robocopy output into
SET tempFileSuppressOutput=%TEMP\%[appName]-vs%[TopasVersion]_output.temp

:: display some info
@ECHO ^n%scriptName Version %scriptVersion^n
@ECHO Create Deployment package for %[appName] %TopasVersion
@ECHO Source: %sourceDir
@ECHO Target: %DeployDir
@ECHO Master: %masterDir

:: get all files that are relevant and different, we use the fabulous robocopy to do this:
:: the /N* switches suppresses output.
:: the /L switch means we don't actually copy anything, just get a list of the files.
:: we only copy files that we really want on the live server, so loads of filters
robocopy.exe %[sourceDir] %[masterDir] /S /R:3 /W:10 /XX /L /NC /NS /NJH /NJS /NP /NDL *.aspx *.asmx *.ascx *.asax *.gif *dll *.jpg *.xml *.xsl? *.js *.ico *.css *.master *.skin /XD old obj topas /XF nunit.framework.dll TweeNet.*.xml TweeNet.Topas.UnitTests.dll Microsoft.Web.Atlas.dll WebDev.WebHost.dll app.css get_aspx_ver.aspx /Log:%tempFile >%[tempFileSuppressOutput]

:: we also want some *.exe files in the root
:: robocopy.exe %[sourceDir] %masterDir /R:3 /W:10 /XX /L /NC /NS /NJH /NJS /NP /NDL *.exe /Log+:%tempFile >>%[tempFileSuppressOutput]

:: count the number of lines, which represents the number of files.
:: if we have files in both directory, we get one extra blank line
SET numberOfNewFiles=%@LINES[%tempFile]

:: we now have a file with the list of all files to be copied

:: built content of the new batch file
@ECHO :: Topas Deployment Batch File >%batchFile
@ECHO :: created by %scriptName Version %scriptVersion >>%batchFile
@ECHO ::       created: %_ISODATE >>%batchFile
@ECHO :: Topas Version: %TopasVersion >>%batchFile
@ECHO ::   Application: %[appName] >>%batchFile ^n

@ECHO :: Files to be copied: ^n >>%batchFile
:: go through every line of the file and just output the filename
for /f %a in (@%tempFile) echo :: %@replace[%sourceDir,, %a] >>%batchFile

:: the copy command expects existing directories, robocopy can create them
:: but needs directory as input, not files, so we have to remove the file names
:: which is a bit messy with 4NT commands. dummy.xyz is the file to copy, 
:: it never exist, so robocopy just creates directories for us.
@ECHO ^n::Create directory structure with robocopy:>>%batchFile
for /f %a in (@%tempFile) echo robocopy.exe %@replace[%@filename[%a],,%a] ^t %@replace[%@filename[%@replace[%sourceDir,%DeployDir, %a]],,%@replace[%sourceDir,%DeployDir, %a]] dummy.xyz /NC /NS /NJH /NJS /NP /NDL >>%batchFile

:: this creates the actual copy commands
@ECHO ^n::Copy the files to deployment >>%batchFile
for /f %a in (@%tempFile) echo copy /Q %a ^t %@replace[%sourceDir,%DeployDir, %a] >>%batchFile

:: we also want to copy the files to the master directory to bring it up to date.
:: first directories, which may not be there
@ECHO ^n::Create directory structure with robocopy:>>%batchFile
for /f %a in (@%tempFile) echo robocopy.exe %@replace[%@filename[%a],,%a] ^t %@replace[%@filename[%@replace[%sourceDir,%masterDir, %a]],,%@replace[%sourceDir,%masterDir, %a]] dummy.xyz /NC /NS /NJH /NJS /NP /NDL >>%batchFile

:: and the files themselves
@ECHO ^n::Copy the files to master>>%batchFile
for /f %a in (@%tempFile) echo copy /Q %a ^t %@replace[%sourceDir,%masterDir, %a] >>%batchFile

:: delete the temp file
DEL /Q %tempFile

:: execute the sync batchfile we just created
:: this does all the copying, we don't want any output
CALL %batchFile >>%[tempFileSuppressOutput]

:: delete the temp file for outpur suppression
DEL /Q %[tempFileSuppressOutput]

:: check whether there were some new files
:: if not the Deploy directory has not been created
 iff "%@files[%DeployDir]" == "0" then
	:: delete the empty batch file
	DEL /Q %batchFile
	:: tell the user about this
	@ECHO ^nNo new files to be deployed
 else
	:: we have some new files
	:: now zip the target
	:: move into the directory above the content,
	:: this way we get a nice structure in the zip file.
	pushd %DeployDir
	:: create a new zip file
	zip -r -q %zipFile *.*
	
	:: copy the zip file to the news folder for easy upload
	copy /Q %zipFile %tnNewsDir
	
	:: move the batch file into the folder
	move /Q %batchFile %DeployDir
	
	:: back to our previous location
	popd
	
	:: tell the user we're done
	@ECHO ^N %numberOfNewFiles new files found
	@ECHO ^N Deployment package ready for upload at^N %tnNewsDir\%zipFile

 endiff

ENDLOCAL

 
Categories: ASP.Net

Visual Studio 2005 now supports two different asp.net project types, the original file system based 'Web Site' (WS) type and as a web-download but included in Service Pack 1 the 'ASP.NET Web Application project' (WAP) type which is pretty much the way we used to do asp.net projects in .Net 1.x.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/WAP.asp describes this type and shows the pros and cons between the two types. So far I as only using WAP because that's how I always did it and it was much easier to port my applications from 1.1 to 2.0. However I liked the fact 'dynamic compilation' of the code-behind files in WS. If you have a bigger site it takes a while to compile all your code-behind code into that single assembly, and you have to do that all the time.

I played with WS a little but because of the deployment model for my main site, I decided to stick with WAP.
But I thought it would be nice to use WS during development and WAP for test, staging and production sites. The asp.net runtime looks at the page directive of an requested aspx page. If it finds the 'CodeFile=' attribute it knows it has to compile the class in that file and deals with a WS project. So by simply changing all CodeBehind= to CodeFile I could convert my WAP project into a WS project and could use the benefit of the dynamic compilation of the aspx.cs files.

Switching between the two. To switch between the two types I came up with a little bit of scripting:

The main task the batch files does is to replace CodeBehind= with CodeFile= and back. I also had to rename the folder 'App_code' to something else like 'AppCode', otherwise you get two copies of the same classes.

One other thing I had to change in my site was an HttpModule which was part of the single assembly. I guess it has to be in a well known assembly because it has to be set up in web.config. So I just moved the class out into it's own little assembly.

I started by using a plain old batch file and some external tools like 4nt.exe and RxFind  as well as Windows scripting host but the next day I ported the whole thing to a Windows Powershell script.

The script supports both IIS and the Visual Studio internal web server because I use both of the at different times. I first delete all temporary asp.net files because otherwise we may end up with two copies of the same classes.

The I check for the existance of my single web assembly, if is exists, I want to move from WAP to WS, if not its the other way.

I then replace all codebehind with codefile or the other way around, it's pretty easy to do in Powershell.

At the end I start the Application poll or the web server process again.

The solution doesn't require Visual Studio at all, but so far I only tested on a Windows 2003 Server, when using XP you don't have AppPools, just kill the ddlhost.exe process hosting the asp.net runtime. Also because you stop an AppPool you need to be an administrator, which is a pain because of course we all never ever log on as one of those. I usually always have one console window that runs under an admin account so I run the batch there.

Here's the script, save it as switch.ps1

# Powershell script to switch an asp.net project from a 'Web Site' type
# to a 'Application Web Project' type and back.
# Peter Hahndorf
# Singapore 11-Dec-2006
# www.twee.net/contact

# Configuration
$AppPoolName="DefaultAppPool"   # The IIS Pool used for the app
$WwwRootDir="c:\hosts\Topas\Tweenet.Topas.UI.Web" # The path to the web files
$WebUIAssembly="TweeNet.Topas.UI.Web.DLL" # The Name of the assembly
$VirtualDirName="Topas" # The name of the virtual directory
$ProjectFileName="TweeNet.Topas.UI.Web.csproj" # Name of the project file
$useIIS = $false # set to $true when using IIS or $false when using Cassini

#======================================================================

# Build some paths
$DotNetDirectory = $env:windir + "\Microsoft.NET\Framework\v2.0.50727"
$assemblyfile = $WwwRootDir + "\bin\" + $WebUIAssembly

# a function to replace text, I thought I had to use
# .net for this, but it's all built in.
function ReplaceString([string]$fileName,[string]$find,[string]$replaceWith)
{
  (Get-Content $fileName) | foreach-object {$_ -replace $find, $replaceWith} | set-content $fileName
}

# function to loop through files
function SwitchFileContent([bool] $toApplication)
{

	$files = get-childitem  $WwwRootDir -Include *.as?x -Recurse
	
	foreach ($file in $files) 
	{
	   if ($toApplication)
	   {
		  ReplaceString $file.fullname " CodeFile=" " CodeBehind="
	   }
	   else
	   {
		  ReplaceString $file.fullname " CodeBehind=" " CodeFile="
	   }
	}

	$files = get-childitem  $WwwRootDir -Include *.master -Recurse
	
	foreach ($file in $files) 
	{
	   if ($toApplication)
	   {
		  ReplaceString $file.fullname " CodeFile=" " CodeBehind="
	   }
	   else
	   {
		  ReplaceString $file.fullname " CodeBehind=" " CodeFile="
	   }
	}

}

if ($useIIS)
{
	# get the IIS Application Pool
	$pool = get-wmiobject -namespace root/MicrosoftIISv2 -class IIsApplicationPool|where {$_.name -match $AppPoolName}
	
	# Stop the application Pool
	$pool.Stop()
}
else
{
	# don't show error if the process does not exist.
	$server = get-process -name webdev.webserver -ErrorAction SilentlyContinue
	
	if ($server -ne $null )
	{
		stop-process -name "WebDev.WebServer"
	} 

}

# Delete all temporary asp.net files for this app
$tempDir = $DotNetDirectory + "\Temporary ASP.NET Files\" + $VirtualDirName + "\"

if ((test-path $tempDir))
{
   Remove-Item $tempDir -Recurse -force
}

if ((test-path $assemblyfile))
{
   "Switch to Web Site Project..."

    # delete the web assembly
    Remove-Item $assemblyfile
	
	# Enable automatic compilation and additional classes in app_code
	$cmdLine = $WwwRootDir + "\AppCode\"
	Rename-Item -Path $cmdLine -newName "App_Code"
	
    SwitchFileContent $false
}
else
{
    "Switch to ASP.Net Web Application Project..."

    # replace the attributes
    SwitchFileContent $true

	# disable App_Code
	$cmdLine = $WwwRootDir + "\App_Code\"
	Rename-Item -Path $cmdLine -newName "AppCode"

	# Use MsBuild to compile the single web assembly
	$startInfo = new-object System.Diagnostics.ProcessStartInfo
	$startInfo.FileName = $DotNetDirectory + "\msbuild.exe "
	$startInfo.Arguments = $WwwRootDir + "\" + $ProjectFileName + " /t:Build /nologo"
	$startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden

	$process = [system.Diagnostics.Process]::Start($startInfo)	

}

if ($useIIS)
{
	# Start the pool again
	$pool.Start()
}
else
{
	$startInfo = new-object System.Diagnostics.ProcessStartInfo
	$startInfo.FileName = $DotNetDirectory + "/WebDev.WebServer.exe"
	$startInfo.Arguments = " /port:80 /path:" + $WwwRootDir
	$startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden

	$process = [system.Diagnostics.Process]::Start($startInfo)
}

"Done"




 
Categories: ASP.Net

I recently needed to parse a line of text which represents a stored procedure call to a database.
The format is
databaseName#storedProcedureName @parameterName=parameterValue

in .Net I used the named groups in my regular expression to get the parts:

^(?<database>[a-z]+)#(?<sproc>[a-z_0-9]+)( (?<parametername>@[a-z_0-9]+)=(?<parametervalue>[a-z_0-9']+))?

Database and stored procedure name are mandatory, then optionally separated by a space a single parameter.


   RegexOptions options = RegexOptions.IgnoreCase;
Regex regex = new Regex(@"^(?[a-z]+)#(?[a-z_0-9]+)( (?@[a-z_0-9]+)=(?[a-z_0-9']+))?", options);

Match match = regex.Match(sprocCall);

if (match.Groups.Count == 6)
{
// we always expect six groups
// if not the string format was not correct
string database = match.Groups["database"].Value;
string sprocName = match.Groups["sproc"].Value;
string parameterName = match.Groups["parametername"].Value.Trim();
string parameterValue = match.Groups["parametervalue"].Value.Trim();
// use the components to make a database call
}


 
Categories: ASP.Net

September 9, 2006
@ 09:35 AM
The asp.net session object is a great way to keep user specific data throughout the lifetime of your web application.

However I have two fundamental problems with it:
1. Sessions out of the box don't work in Web gardens or web farms, you can use the asp.net session service or a SQL database to manage your session state, but that is slower than the default inProcess session state. Even for smaller sites there are problems; every time I update web.config or an assembly in the bin directory I loose my sessions. In addition every time the worker process is restarted through IIS health measures, sessions are gone as well.

2. I need to use user state within my business objects because that's where my authentication and authorization code resides. It's possible to access the session object in your business tier but they don't work if I access it from a console application or nUnit for testing.

Today I started working on a solution and have something working but it needs more testing.  First to make things cleaner I abstracted all access to the session and cache objects into a new class in my business layer called 'State' all access to state variables has to go through this class. So I have get, set and remove methods. Each take an enumeration of items rather than a string for the key to keep a nice clean list of all session variables I'm using.

Within this class I can now address my two problems. To solve the loosing sessions problems I bring in a custom database table that stores session variables for me. Not quite as sophisticated as the Microsoft's SQL-Server solution but it works. When I save 'state' I not only store it in the session object but also in the database table. When I try to access 'state' I first check session and if that is null, then I check in the database. If I find the value in the database table I put it into session and return the value. This way the database is only accessed when a value is created/updated or when we lost the session object, otherwise we use the fast in memory session object.

Some considerations:

I'm only using this methods for integer and small string values, but not for my 'state' with datatypes of Hashtable or even DataTable. This would involve some serialization to store these types in a database field. In my case these 'bigger' values could be rebuild from normal database data based on the integers stored in state. (I have an integer UserID and hashtables for a security token and a DataTable for navigation data, both can easily be refilled when needed, remember this only happens when we loose session).

I mostly use 'read-only' state. All my session variables are set after a user has been authenticated and after that, they are never updated. This also works fine in web gardens and web farms because the session objects in all the various worker processes are synchronized through the database table.

For session variables that change during their lifetime this approach only works for a single worker process. If you have multiple processes that update state in there own inprocess session object there is no way to notify the other worker processes. One idea is to check the database periodically for updated data.

In my case I use the state variable to identify that user after initial authentication. When the user logs out, I remove the state from the inprocess memory and the database. However if I'm using multiple worker processes, the other ones still have the state data in their local session objects. The only solution I see to this problem is that you also have to delete the Asp.Net_SessionID cookie, so when the user hits another worker process it is no longer associated with it previous session. Of course this means it's all or nothing, you can't remove some state values but not others. For web-farms and web-gardens you should really only use this for write-once state data that should also be deleted together.

What about expiration. My method to remove a session variable also deletes the associated record in the database but it doesn't expire in the same way that in-memory sessions variables do. Deleting all related records when the Session_End event in globals.aspx fires is one option.
This all works because the Asp.Net_SessionID cookie remains in the browser even if you loose your session on the web-server. The new worker process picks up the existing cookie and uses the same ID for the new session instance.

This allows you to restart IIS or even the server and not loose state for the user, great!

The second problem I solved by checking for an existing HttpContext.Current instance, if this doesn't exist I am not in a web application and can't use Session or Cache. In this case I am using using my own Hashtables to store state information. I implemented a singleton pattern for an AppContext class to ensure that I have always only one instance of my Hashtable.

All this shouldn't be to hard to implement, you should even be able to do this in classic Asp or php, there is nothing asp.net specific about this, session variables are widely supported. I don't have any example code for this yet because it's all deeply integrated into my application. Lets see whether there is some interest.


 
Categories: ASP.Net

August 28, 2006
@ 05:09 AM
The Microsoft ATLAS Ajax framework makes heavy use of web services to get data from the server to display on a page via JavaScript. I implemented some ATLAS pages in a web that uses a custom form based authentication method. I'm using the asp.net session state to keep track of the currently logged on user. For the ATLAS web services this didn't work because I got exceptions when accessing the session object.

So for a while I was running with unsecured web service methods, checking the referrer server variable is in no way a secure method. But somehow I ran into the EnableSession property of the WebMethod attribute. By default web methods for performance reasons don't have access to the session state. By setting:
[WebMethod(Description = "method description",EnableSession=true)]      
this changes and I can now access my authentication object to check for the current user.

This should also work for other authentication methods like the ones built into asp.net.


 
Categories: ASP.Net

For a small project I had to display web pages within a Windows form, no problem, just drag a webBrowser control onto the form set a url. Works fine but it turned out the pages I have to display require basic authentication over SSL. There is no property on the control to set the credentials like there is for the .net WebRequests. There are some people on the net asking about this but I didn't find an easy answer. I went through all the members of the control and in addition to the url property I used before I found the Navigate method, one of the overloads takes an AdditionalHeader parameter. I couldn't exactly remember how the authentication header looked like, so I fired up Wfetch (IIS Resource Kit) to look at them. The header in question is something like:

Authorization: Basic RmlsZVVwbG9hZXVyOjG4NUgkOTNT7UpTeQM=\r\n

the crypted string in the middle is a base64 version of “myusername: mypassword”
the whole code is:

    
     System.Uri uri = new Uri("http://www.mysite.xxx");

     // encode the credentials in Base64
     byte[] authData = System.Text.UnicodeEncoding.UTF8.GetBytes("myusername: mypassword");
            
     // build the whole header
     string authHeader = "Authorization: Basic " + Convert.ToBase64String(authData) + "\r\n";

     // open the page with the extra header
     webBrowser1.Navigate(uri, "", null, authHeader);

 
Categories: ASP.Net

July 31, 2006
@ 01:15 PM
I spent several hours on this small problem. atlasglob.axd is a virtual page in Atlas, Microsoft's AJAX framework. It is used by several Atlas components and is created by the GlobalizationHandler class in the Atlas assembly. To get it to work you need to have the following line in your web.config:

<add verb="*" path="atlasglob.axd" type="Microsoft.Web.Globalization.GlobalizationHandler" validate="false"/>
this all worked fine on my dev box but when I first deployed my Atlas enabled web site to the live server it didn't work anymore. atlasglob.axd resulted in a 404. I triple-checked my web.config but it was okay, I looked online but no one had the same problem. I didn't install the Atlas framework on the live-server, I just copied the Atlas assembly into my bin directory, I was under the impression that should be enough.
In the end it struck me, as a security concious guy I always kept the script mappings on IIS to a minimum. So I opened IIS and checked the Application Mappings, axd was not among them. This means IIS never passes control over any *.axd files over to the asp.net runtime and the registered handler can never kick it. Adding the mapping fixed the problem. It also proved that you don't need to install the Atlas framework to use it on a machine, just put Microsoft.Web.Atlas.dll into your bin directory.

 
Categories: ASP.Net