RSS 2.0 | Atom 1.0 | CDF

Search

Categories

Archive

Blogroll

Sign In

# Wednesday, December 28, 2011
Wednesday, December 28, 2011 9:16:30 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
I'm using Asp.Net to open excel files and read the values cell by cell, normally i prefer to import Excel files as a datatable, but some documents don't fit the strict rows/columns format required for importing into a DataTable.  the code i'm using is based on this from dotnetperls.  worked fine for the most part on my dev PC but I had a few issues trying to get this to work on a 32bit version of excel on a 64 bit Windows 2008 server.
here's what i had to do to get it working:
  • disable macros (throwing exceptions opening the workbook), requires a reference to the Microsoft.Office.Core assembly:
    excelApp.AutomationSecurity = Microsoft.Office.Core.MsoAutomationSecurity.msoAutomationSecurityForceDisable;   // disable macros
  • give launch / open permissions to the Excel application in DCOM config, this was tricky because it isn't shown in a 64 bit OS if the program is 32 bit, i found the answer here.
  • set the identity in DCOM for Excel to the interactive user.


Comments [0] | | # 
# Friday, June 11, 2010
Friday, June 11, 2010 6:32:37 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
Ran into a permissions problem today using Excel interop code from within ASP.Net, which had worked fine from a windows forms application.
Thanks to 'Frosty' for his post which explains how to enable the appropriate permissions, reproducing it here in case the link ever goes down:

From command prompt / start->run... type dcomcnfg

Select Component Services->Computers->My Compter->DCOM Config

Scroll down and select Microsoft Excel Applicaton

Right click on Microsoft Excel Applicaton and select properties.

Select the Security tab

In Launch Permissions group box click Edit button.

Add the appropriate user for your particular situation. In my case, I
selected MyDomainName\Domain Users.

Make sure that Allow check box is checked for your appropriate user.

Click OK

In Access Permissions group box click Edit button.

Add the appropriate user for your particular situation. In my case, I
selected MyDomainName\Domain Users.

Make sure that Allow check box is checked for your appropriate user.

Click OK

Excel interop will now work via asp.net

Comments [0] | | # 
# Tuesday, April 21, 2009
Tuesday, April 21, 2009 1:30:58 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net )
I got a request from a client for a 'mini' navigation menu to appear on a page to allow the user to 'jump' to the various sections of the page.  Maintaining a set of named anchor hyperlinks in a document (with a javascript-based select menu) is not really an option for someone who doesn't know HTML, which is most people who use content management.  In the wrong hands this creates more trouble than it's worth. 
so i started thinking. the documents in question are well structured, using paragraphs and headings, as enforced by the content management system.  i worked out this solution which is exactly what i need, a maintenance free javascript jump menu, that doesn't add clutter to the document with named anchors.  it assumes that the all H2 tags should be displayed as links in the menu.  the javascript iterates through the H2 tags and loads up the menu, with a ScrollTo function used when an item is selected.

here's a screenshot


The code for the HTML page:
<script src="/JumpMenu.js" type="text/javascript"></script>
<select id="jumpNavSelect" name="jumpNavSelect" onchange="JumpToHeading(this.selectedIndex)">
<option value="">On this page...</option>
</select>
and the JumpMenu.js file
var array;

function CreateAnchorMenu() {
array = document.getElementsByTagName("h2");
var src = document.getElementById('jumpNavSelect');
// iterate through all H2 headings, add dropdown items for each.
for (var i = 0; i < array.length; i++) {
var heading = array[i];
var text = heading.firstChild.nodeValue;
if (!text)
continue;
src[i+1] = new Option(text, i);
}
}

function JumpToHeading(HeadingIndex) {
if (HeadingIndex == 0)
return;
var heading = array[HeadingIndex-1];
ScrollToElement(heading);
}

function ScrollToElement(theElement) {
var selectedPosX = 0;
var selectedPosY = 0;
while (theElement != null) {
selectedPosX += theElement.offsetLeft;
selectedPosY += theElement.offsetTop;
theElement = theElement.offsetParent;
}
window.scrollTo(selectedPosX, selectedPosY);
}

window.onload = CreateAnchorMenu;
Comments [3] | | # 
# Tuesday, March 24, 2009
Tuesday, March 24, 2009 4:38:48 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
if your application has users who like to log in as "JOE BLOGGS" or "joe bloggs" when their username is actually "Joe Bloggs", you might want to ensure that they login with the correct "case" of their username.  It can cause discrepancies if you refer to the current Identity.User in your web application, for example if you use the username anywhere in your database and run reports grouped on the username, you will get multiple records for each variation on the case of the username.  It is surprisingly unintuitive to solve this problem.  Firstly you would think that Asp.Net membership would take care of this itself.  Then you would think that you could determine the correct case of the username as follows:
protected void Login1_LoggedIn(object sender, EventArgs e)
{
string correctUsername = Membership.GetUser(this.Login1.Username).Username;
FormsAuthentication.SetAuthCookie(correctUsername , true);
}

but this doesn't work because the "Username" property of the MembershipUser object does not collect its value from the AspNetDB SQL database like you would expect, instead it is filled with whatever you pass it when loading the user, this must be a bug but i'm not bothered trying to convince MS.  instead, i came up with this solution below, to directly load the AspNetUser object from a Linq DataSource of the AspNetDb database (created using SqlMetal).

protected void Login1_LoggedIn(object sender, EventArgs e)
{
// correct the case of the username
string Username = this.Login1.UserName;
AspNetDb db = new AspNetDb();
MembershipUser memUser = Membership.GetUser(Username); // load the MembershipUser object to get the UserID
Aspnet_User aspnetUser = db.Aspnet_Users.SingleOrDefault(z => z.UserId == new Guid(memUser.ProviderUserKey.ToString()));
if(aspnetUser != null)
Username = aspnetUser.UserName;
FormsAuthentication.SetAuthCookie(Username, true);
}

Comments [1] | | # 
# Friday, February 27, 2009
Friday, February 27, 2009 5:47:52 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
Cstr(CDate({Table.Column}), "dd/MM/yyyy")

Comments [0] | | # 
# Thursday, February 19, 2009
Thursday, February 19, 2009 4:18:50 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
today i came across a situation where a user was spending a long time filling out a form.  they went past the session timeout and when they hit save they got booted back to the login screen, having lost the previous 30 minutes of work.  i looked around the net and found a few options with AJAX and what not but it doesn't make sense to keep renewing the session automatically otherwise you might as well not have a timeout, which is there for security reasons anyway.  in my case i don't actually want to save the information to the database (it's a transaction) until the user hits the Save button, i just want to keep the session alive. 

so i came up with this idea to ask the user if they want to keep their session alive with a javascript prompt every 15 minutes. if they leave their computer and don't answer the prompt, the session will time out since they won't have interacted with the web site (regardless of how they answer the prompt after the timeout).  if they answer OK to the prompt it invokes a Ping() type web service to keep the session alive.  this is the most lightweight way i could think of doing it.  i don't want to post back to the page because that will interrupt the user and make them wait for the page to reload.

there are 3 parts:

"KeepAlive" function

I put this function in a 'Util' class so pages can easily turn on the 'Keep Alive' functionality, simply call Util.KeepAlive(this);

public static void KeepAlive(Page p)
{
p.ClientScript.RegisterClientScriptInclude("KeepAlive", "/KeepAlive.js");
}

Ping.asmx (add the web service to your root folder)

[WebMethod]
public void Ping()
{
HttpContext.Current.Response.Write("OK");
HttpContext.Current.Response.End(); // this makes the result easier to parse than an XML web service message
}

KeepAlive.js (put this file in your root folder)

var timerID = 0;	     // used to track the timer function
var interval = 1000*60*15; // 15 mins
var KeepAliveUrl = '/Ping.asmx/Ping'; // replace your web service address here
var xmlhttpKeepAlive;

function AutoSaveSubmit()
{
if(confirm('15 minutes of idle time has passed, do you want to keep your session active?'))
InvokeWebService();
}

function InvokeWebService()
{
if (window.XMLHttpRequest)
{
xmlhttpKeepAlive=new XMLHttpRequest();
xmlhttpKeepAlive.onreadystatechange = xmlhttpChangeKeepAlive;
xmlhttpKeepAlive.open('GET',KeepAliveUrl + '?T=' + timerID,true);
xmlhttpKeepAlive.send(null);
}
// code for IE
else if (window.ActiveXObject)
{
xmlhttpKeepAlive=new ActiveXObject('Microsoft.XMLHTTP')
if (xmlhttp)
{
xmlhttpKeepAlive.onreadystatechange = xmlhttpChangeKeepAlive;
xmlhttpKeepAlive.open('GET', KeepAliveUrl + '?T=' + timerID, true);
xmlhttpKeepAlive.send();
}
}
return false;
}

function xmlhttpChangeKeepAlive()
{
var text;
if (xmlhttpKeepAlive.readyState == 4)
{
text = xmlhttpKeepAlive.responseText;
if (xmlhttpKeepAlive.status==200) // OK
{
if(text == 'OK') // reset the timer
timerID = setTimeout('AutoSaveSubmit()', interval);
else
alert('Your session has already expired.\nIf you have any information on this page you will lose it if you try to save this page now. You have 2 options to avoid losing the information on this screen.\n\nOption 1: open a new internet window and log in again to the web site, then close that window and go back to this window, at which point you will have a new session and you will be able to save the information.\n\nOption 2: Copy all the text you have typed on this page and paste it into another program (such as Word or Notepad), then log in to the web site again (click the home page) and come back to this page, and paste in the text again.\n\nTo prevent this happening in the future, click OK on the 15 minute reminder box each time it appears, this will keep your session active. If you do not answer the reminder within 10 minutes the session may expire.');
}
else
alert('Error: ' + xmlhttpKeepAlive.status + '. The session may have already expired, please try to save the page now');
}
}
window.setTimeout('AutoSaveSubmit()',interval);

You might be wondering why i append the timer code/number to the url of the web service.  The reason is because IE has a caching bug and will not actually send the XmlHttp request unless the URL is different to what it has in its history, it will just return the previous result.

Comments [6] | | # 
# Tuesday, December 23, 2008
Tuesday, December 23, 2008 12:08:07 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
many web sites export tabular information from grids/tables into 'Excel' format. the file is not a true Excel format and actually contains X/HTML.  if the file is opened in Excel 2007 you may get a prompt that the file is in a different format than specified by the file extension.  if you regularly work with files like this, there is a way to disable this prompt, add a DWORD key called ExtensionHardening with value 0 to HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Excel\Security
it works straight away, no need for a reboot.

Comments [0] | | # 
# Sunday, October 26, 2008
Sunday, October 26, 2008 1:41:10 PM (GMT Standard Time, UTC+00:00) ( Asp.Net | Windows Server 2003 )
IIS went down this morning after KB 958644 was isntalled automatically last night. My sites were showing "Service Unavailable"
there were several system event logs like so:
  • A process serving application pool '.Net 2.0 App Pool' suffered a fatal communication error with the World Wide Web Publishing Service. The process id was '2960'. The data field contains the error number.
  • Application pool '.Net 2.0 App Pool' is being automatically disabled due to a series of failures in the process(es) serving that application pool.

rebooting the server did not solve the problem.  so, on further research, KB article 885654 revealed the cause of the errors, lack of registry permissions for NETWORK SERVICE.  i hadn't changed any configuration on the server so assumed it was caused by a change in that windows update package.  i dug out ProcessMonitor and set up a registry filter for w3wp.exe where the Result was ACCESS DENIED.  there were about 50 entries during the time i enabled event capture and loaded up one of the sites that uses the affected application pool.  Most of them pointed to SystemCertificates entries.  I went through each one in regedit and gave NETWORK SERVICE full control where the process attempted to Create a key, and Read only access where the process attemped to Open the key.  worked fine then.

hope this helps someone out.
Comments [0] | | # 
# Thursday, July 17, 2008
Thursday, July 17, 2008 5:25:32 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net )
if you ever send a string across a web service, and write it out to a file, make sure you specify Encoding.UTF8 explicitly. otherwise characters such as the EURO symbol may not render correctly in some browsers (IE6).  it took me ages to pin this down, because everything i read about was about HTTP header charset values, or HTML document charsets, or database encodings.  In my case i was using the default encoding and this messed up EURO symbols. I suspect it is because of the string being serialized in the web service, but haven't the time to look into it any further.  it's fixed now anyhow.

Comments [3] | | # 
# Thursday, January 31, 2008
Thursday, January 31, 2008 4:32:13 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
this wasn't very obvious to work out, and i couldn't find a decent solution online, so i came up with this.
this.DropDownList1.Attributes.Add("onchange", "if(!confirm('Are you sure you want to delete this item?')) return;");
This works with .Net 2 and 3 and 3.5, but may break with future versions if .Net changes the javascript code for AutoPostBack drop down lists.  The reason it works now is because .Net adds the custom 'onchange' attribute before it's on doPostBack() function call.  So if we return from the confirm prompt, the form will not submit. 

Comments [0] | | # 
# Tuesday, November 27, 2007
Tuesday, November 27, 2007 11:32:12 AM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net | Windows Server 2003 )
if, like me, you were running VS 2008 Beta 2 on a server and you had crystal reports working fine, and then recompiled your projects in VS 2008 RTM, you may get an error like this when you try and create a crystal report on a production server:
System.Runtime.InteropServices.COMException (0x800736B1):
Retrieving the COM class factory for component with CLSID
{5FF57840-5172-4482-9CA3-541C7878AE0F} failed due to the following error:
800736b1. at CrystalDecisions.CrystalReports.Engine.ReportDocument..cctor()
In my case, i had installed Crystal Reports Runtime for VS 2008 Beta 2, and i never installed the runtime for the RTM version.  it should be available on your VS machine, if you opted for CR in the VS installer:
C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\CrystalReports10_5

should probably uninstall the old version of the CR runtime while you're at it too. 

Comments [2] | | # 
# Friday, November 16, 2007
Friday, November 16, 2007 12:16:50 PM (GMT Standard Time, UTC+00:00) ( .Net General | .Net Windows Forms | Asp.Net )

Note: this doesn't really work..

i'm just leaving it here for interest.  the approach will work for the first invocation of either v2 or v3 client, but after a v2 client has used the web service, any v3 clients will fail.  MS told me this is not supported because of the 2 soapExtensions interfere with each other in the pipeline etc


My asp.net CMS has some winforms clients running WSE2, and i'm doing an upgrade to the client (and server) to use WSE3, but i want to support both versions.  Both WSE2 and WSE3 clients are using a CustomUsernameTokenManager.  it was a bit tricky to work out, but here is what i ended up doing. thanks to brian o'keefe for his newsgroup post for most of the answer.  Your exact config might vary but hopefully it will save you some of the hassle of working all this out.
  • Leave the existing web service in place, e.g. Service_WSE2.asmx
  • Create a new web service for the WSE3 client, essentially copy/paste the ASMX file, e.g. Service_WSE3.asmx
  • Create a new CustomUsernameTokenManager for the WSE3 service which inherits from Microsoft.Web.Services3.Security.Tokens.UsernameTokenManager.  In my case it was as easy as copy/paste from the WSE2 token manager and change all the WSE2 namespaces to WSE3.
  • then in web.config, make sure both WSE config sections are listed:
    <configuration>
    <configSections>
    <section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3,Version=3.0.0.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <section name="microsoft.web.services2" type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </configSections>
  • add in references to both the assemblies for the compilation section (not 100% sure if this is necessary, but at least it means the app will not compile on the production server if you forget to deploy either of the WSE assemblies, instead of waiting till one of your clients connects).

    	<compilation defaultLanguage="c#" debug="true">
    <assemblies>
    <add assembly="Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <add assembly="Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  • remove the <webServices> section from <system.web>, because each web service should be configured by location, not global as this interferes.

    	<location path="Service_WSE2.asmx">
    <system.web>
    <webServices>
    <soapExtensionTypes>
    <add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
    </soapExtensionTypes>
    </webServices>
    </system.web>
    </location>

    <location path="Service_WSE3.asmx">
    <system.web>
    <webServices>
    <soapExtensionTypes>
    <clear/>
    </soapExtensionTypes>
    <soapServerProtocolFactory type="Microsoft.Web.Services3.WseProtocolFactory, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <soapExtensionImporterTypes>
    <add type="Microsoft.Web.Services3.Description.WseExtensionImporter, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </soapExtensionImporterTypes>
    </webServices>
    </system.web>
    </location>
  • my webservice policy are defined in policyCache.config files, so the following web.config sections point each version of WSE to the right file:
    	<microsoft.web.services3>
    <policy fileName="policyCache_WSE3.config"/>
    <security>
    <securityTokenManager>
    <add type="Whatever.CustomUsernameTokenManager3" namespace="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" localName="UsernameToken"/>
    </securityTokenManager>
    </security>
    </microsoft.web.services3>
    <microsoft.web.services2>
    <messaging>
    <maxRequestLength>10000</maxRequestLength>
    </messaging>
    <security>
    <securityTokenManager type="Whatever.CustomUsernameTokenManager2, MyAssemblyName" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    qname="wsse:UsernameToken" />
    <defaultTtlInSeconds>86400</defaultTtlInSeconds>
    <timeToleranceInSeconds>86400</timeToleranceInSeconds>
    </security>
    <policy>
    <cache name="policyCache_WSE2.config" />
    </policy>
    </microsoft.web.services2>
  • and lastly, the policy cache files themselves:
    WSE 3 version
    <?xml version="1.0"?>
    <policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
    <extensions>
    <extension name="usernameOverTransportSecurity" type="Microsoft.Web.Services3.Design.UsernameOverTransportAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <extension name="requireActionHeader" type="Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </extensions>
    <policy name="usernameTokenSecurity">
    <usernameOverTransportSecurity />
    <requireActionHeader />
    </policy>
    </policies>
  • WSE 2 version
    <?xml version="1.0"?>
    <policyDocument xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy" xmlns="http://schemas.microsoft.com/wse/2003/06/Policy">
    <mappings xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy">

    <endpoint uri="http://localhost/Service_WSE2.asmx">
    <defaultOperation>
    <request policy="#username-token-signed" />
    <response policy="" />
    <fault policy="" />
    </defaultOperation>
    </endpoint>

    <defaultEndpoint>
    <defaultOperation>
    <request policy="" />
    <response policy="" />
    <fault policy="" />
    </defaultOperation>
    </defaultEndpoint>

    </mappings>
    <policies xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <wsp:Policy wsu:Id="username-token-signed" xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wssp="http://schemas.xmlsoap.org/ws/2002/12/secext">
    <wsp:MessagePredicate wsp:Usage="wsp:Required" Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
    wsp:Body() wsp:Header(wsa:To) wsp:Header(wsa:Action) wsp:Header(wsa:MessageID) wse:Timestamp()
    </wsp:MessagePredicate>
    <wssp:Integrity wsp:Usage="wsp:Required">
    <wssp:TokenInfo>
    <SecurityToken xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext">
    <wssp:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</wssp:TokenType>
    <wssp:Claims>
    <wssp:UsePassword wsp:Usage="wsp:Required" />
    </wssp:Claims>
    </SecurityToken>
    </wssp:TokenInfo>
    <wssp:MessageParts Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
    wsp:Body() wsp:Header(wsa:Action) wsp:Header(wsa:FaultTo) wsp:Header(wsa:From) wsp:Header(wsa:MessageID) wsp:Header(wsa:RelatesTo) wsp:Header(wsa:ReplyTo) wsp:Header(wsa:To) wse:Timestamp()
    </wssp:MessageParts>
    </wssp:Integrity>
    </wsp:Policy>
    </policies>
    </policyDocument>

Comments [1] | | # 
# Tuesday, September 18, 2007
Tuesday, September 18, 2007 1:22:32 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net | Database )
Here is the sql that is generated by a DataContext when you use Skip() and Take() to efficiently select records for the grid:
SELECT TOP 10 [t1].[Name], [t1].[Address], [t1].[Tel1], [t1].[Tel2], [t1].[Email], [t1].[DateCommenced], [t1].[Comments], [t1].[Active], [t1].[Fax]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[Name], [t0].[Address], [t0].[Tel1], [t0].[Tel2], [t0].[Email], [t0].[DateCommenced], [t0].[Comments], [t0].[Active], [t0].[Fax]) AS [ROW_NUMBER], [t0].[Name], [t0].[Address], [t0].[Tel1], [t0].[Tel2], [t0].[Email], [t0].[DateCommenced], [t0].[Comments], [t0].[Active], [t0].[Fax]
FROM [dbo].[Table1] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] > @p0
-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [50]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

it's not immediately obvious why they use the subquery like this, but i'm sure they have been very thorough in optimising LINQ. 

Comments [1] | | # 
# Wednesday, August 29, 2007
Wednesday, August 29, 2007 1:34:31 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
i couldn't figure out why IE was giving me an 'invalid argument' for using code like:
window.open("http://whatever", "1234321-ABCDE-1234231");
the problem is that IE only accepts alphanumeric and underscore characters for the second parameter (window name), so the hyphens were causing problems.  just trim them out and it will work fine.

Comments [1] | | # 
# Friday, July 27, 2007
Friday, July 27, 2007 5:49:16 PM (GMT Daylight Time, UTC+01:00) ( .Net General | .Net Windows Forms | Asp.Net )
Just got up and running with VS 2008 Beta 2 and converted my Orcas Beta 1 projects over to VS 2008.  have a read of Scott Gu's post on downloading and installing.  whaddya know? VS 2008 hasn't crashed yet :)  seriously though, this is a major relief to see good stability.  as a developer it did my head in to have to keep nuking devenv.exe and losing a few minutes work, driving me quietly insane.  hopefully those days are over now.  Beta 1 was a thousand times better than VS 2005, but Beta 2 looks even better. 

in terms of breaking changes with upgrading projects etc., here are some tidbits i came across
  • the namespace System.Data.Linq.Expressions no longer exists.  i just deleted this namespace and it worked fine, they must have moved various classes back into the root Linq namespace or something.
  • if you use SqlMetal, you will have to use the new version of this tool, which i found at C:\Program Files\Microsoft SDKs\Windows\V6.0A\Bin\SqlMetal.exe. The generated code from the new tool is very different to before so you will need to regenerate in order to use the new LINQ libraries.
  • web.config changes are also introduced.  the best way to figure out the changes is to open up a blank web application and compare the standard web.config in the empty project to your own web.config.
  • Crystal Reports assembly version numbers are the same with this release of VS, 10.5.3700.0, so no changes here since Beta1.
i'll post more next week when i have some real experience using VS 2008.  so far so good.  great job Scott & Co.

Update 30 July 2007 - Deployment Issues
  • To deploy a VS 2008 beta 2 web application to a Server 2003 with .net runtime 2.0, you need to install 3.5 of the runtime.  i got all sorts of web.config errors due to the new syntax with beta 2.  A web app compiled against v3.5 in Beta 1 had no trouble running on top of the 2.0 runtime with the various v3.5 DLLs deployed to the /bin directory, however this doesn't seem to be possible anymore.  it took me a while to find the correct download for the 3.5 beta 2 redistributable, it installed in about 10 mins and required a reboot, and did not affect any of the v1.1 and v2 web sites running on the server. 

Comments [0] | | # 
# Thursday, July 05, 2007
Thursday, July 05, 2007 3:53:40 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
this is a bit of a strange one.  I have a table and it has approx 60 columns, with only one row being shown.  I wanted a human-readable display of all the fields in the table for any given row, i.e. i didn't want the user to have to scroll several screens across just to read the data for a single row.  Vertical scrolling is obviously easier, so i wrote a Repeater implementation, which works nicely.  For a table with X columns and Y rows, the output will have X rows and Y columns.  Screenshot below shows the output of all the ServerVariables in a HTTP-request, which is a good example of a datasource that does not naturally fit the traditional 'grid' display.
<!-- ASPX usage -->
<xyz:VerticalGrid ID="vGrid" runat="server" />

// sample databinding...
this.vGrid1.DataSource = new DataView(dataSet1.Tables[0]);
this.vGrid1.DataBind();

/// <summary>
/// A Repeater control that shows the rows and columns inverted.
/// No template is required for the items.
/// The datasource must be an ADO.Net DataView
/// </summary>
[ToolboxData("<{0}:VerticalGrid runat=server></{0}:VerticalGrid>")]
public class VerticalGrid : Repeater
{
public VerticalGrid()
{
this.EnableViewState = false;
}

protected override void Render(HtmlTextWriter writer)
{
// invert the rows and cols
if (!(base.DataSource is DataView))
throw new Exception("Error: only a dataview datasource can be used");

DataView dv = base.DataSource as DataView;

// output start of table
writer.Write("\n<table class='grid'>");

// output each row
for (int i = 0; i < dv.Table.Columns.Count; i++)
{
writer.Write("\n<tr class='{0}'><th>{1}</th>", i % 2 == 1 ? "gI" : "gA", dv.Table.Columns[i].ColumnName); // output the column name in the first cell. class names gI and gA are abbreviations of gridItem gridAlternatingItem, to reduce output markup.
for (int j = 0; j < dv.Count; j++)
writer.Write("<td>{0}</td>", dv[j].Row.IsNull(i) ? "&nbsp;" : dv[j][i].ToString());
writer.Write("</tr>");
}
writer.Write("\n</table>");
}
}



Comments [0] | | # 
# Thursday, May 17, 2007
Thursday, May 17, 2007 5:36:06 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net )

I know we're not supposed to be going live with .Net 3.5 / Orcas Beta 1 yet, but hey. 

I ran into a Crystal Reports deployment problem with an asp.net web application developed in Orcas beta 1, using the bundled version of crystal reports.  The problem is that the crystal report dlls used in a Beta 1 project are 10.5.3700.0 but there don't appear to be any merge modules available to support this version number, so there is no supported way to run the Orcas version of Crystal Reports on a server, without installing Orcas itself.  I tried numerous options of digging out the 10.5.3700.0 dlls from program files\common files\business objects etc and putting them in the web site bin directory on the server, but that didn't work.  The obvious error message that comes up is as follows:

Could not load file or assembly 'CrystalDecisions.CrystalReports.Engine, 
Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304' or one of its dependencies.
The system cannot find the file specified.

what i ended up doing was instructing the web application to bind to the 10.2.3600.0 versions of the assemblies, which are already deployed using the normal CR server install.

here is what i added to the end of my web.config file to get it going:

     ....
<runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="CrystalDecisions.CrystalReports.Engine" publicKeyToken="692fbea5521e1304"/>
                <bindingRedirect oldVersion="10.5.3700.0" newVersion="10.2.3600.0"/>
            </dependentAssembly>
            <dependentAssembly>
                <assemblyIdentity name="CrystalDecisions.CrystalReports.Shared" publicKeyToken="692fbea5521e1304"/>
                <bindingRedirect oldVersion="10.5.3700.0" newVersion="10.2.3600.0"/>
            </dependentAssembly>
            <dependentAssembly>
                <assemblyIdentity name="CrystalDecisions.Shared" publicKeyToken="692fbea5521e1304"/>
                <bindingRedirect oldVersion="10.5.3700.0" newVersion="10.2.3600.0"/>
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
</configuration>
Comments [1] | | # 
# Wednesday, May 02, 2007
Wednesday, May 02, 2007 6:43:46 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net )
i spent hours trying to troubleshoot this.  obviously the error makes no sense because CR should not be logging on to anything in a dataset scenario.
anyway, a thousand thanks to Jason for his post on the MSDN forums with the simple answer: set the datasource to the DataTable, not the DataSet.

i can't wait to lose Crystal reports altogether and move to the microsoft reporting thingy, i haven't had time to play about with it yet but at least it will be properly programmed and it won't contain the 10 years of bugs that crystal reports have carried through each release of their software.  </RANT>

Comments [0] | | # 
# Monday, April 30, 2007
Monday, April 30, 2007 11:50:28 AM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net )
here are a few of my main findings/hurdles encountered when upgrading May CTP web projects to Orcas beta 1 Web Application Projects.  I'm targeting version 3.5 of the framework for deployment on a Server 2003 with .Net 2.0 runtime installed, i will update this article if it doesn't work out. 
  • if you had any code in the "App_Code" folder, VS will probably have set the compile action to "content", it should be changed to "compile".  otherwise you will get errors like: The type or namespace name 'xyz' could not be found (are you missing a using directive or an assembly reference?)
  • you'll have to remove any reference to the System.Query and System.Expressions, these are not part of the new LINQ spec.
  • Replace System.Data.DLinq with System.Data.Linq.
  • if you want to use any LINQ expressions, like "from x in db.Table select x" then you need to include the System.Linq namespace.  otherwise you will get errors like 'System.Data.Linq.Table<xyz>' does not contain a definition for 'Where' and no extension method 'Where' accepting a first argument of type 'System.Data.Linq.Table<xyz>' could be found (are you missing a using directive or an assembly reference?)
  • you will also need to include System.Linq.Expressions if you are using "language-level code expressions to be represented as objects in the form of expression trees".
The old LINQ assemblies are versioned 1.0.2319.19044 (May CTP) but the new ones for Beta 1 are versioned 2.0.0.0.  Have a check through the referenced assemblies in your project to make sure you have the latest versions. 

Crystal reports

look out for the new crystal report assemblies, 10.5.3700.0.  a web app referencing the new versions in web.config will complain if these versions are not available on the server.  i'll update soon when i have found out how to do this.

Comments [0] | | # 
# Friday, March 09, 2007
Friday, March 09, 2007 5:43:24 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net | Database | General )
If you run a web server, chances are you have some form of automated monitoring system in place.  If you use MOM or another enterprise level thing then this post won't be of much relevance.  If, like me, you have simpler requirements, read on.

I have been caught out a few times with my web sites being down because Windows Server 2003 automatically installed an update and something went wrong and IIS got stopped, or like this morning at 4am, SQL 2005 SP2 failed to install and left the SQL Service offline.  i didn't find out till i got a phone call from a client.

My datacenter provide very good ping monitoring with SMS alerts etc., but this is not a complete solution because the web site may have a configuration error, and it will still respond to pings.  similarly, you can't just check for an OK HTTP status code because your error ASPX page may not be configured to send an error HTTP status code.

I have used various online web site monitoring services, with varying degrees of success / satisfaction.  My current provider are InternetVista.com and for €70 a year i get a 10 minute check for a single HTTP site, with a keyword match on the contents of the page, and an email/sms alert if the match is not found.  You can pay for extra and more frequent checks, but €70 is as much as i think the service is worth.  To have this level of checking done on 10 sites would cost a lot, so to save a few quid i wrote a very simple aspx page that does a series of tests on all the resources i want to verify on the server, e.g. SQL Server, MS Access, IIS web sites.  The aspx code is listed below, i wrote it inline rather than compiled/dll because it is easier to deploy in an existing web site without any risk of any side effects (dll collisions), it should be straight forward to understand for a c# programmer.  let me know if you have questions.  It runs in a few miliseconds on my server so i'm not worried about polling all these resources every 10 mins.



Run_Server_Tests.aspx code:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Net" %>

<script RunAt="server">

/* Server Monitoring Script:
* - test SQL databases by running an sql string against an SQL connection string
* - test Access databases by running an sql string against a JET connection string
* - test web sites by Regex matching a search string against the contents of a HttpWebRequest
*/

enum TestType {Sql_Server, Ms_Access, Http_Request } // different types of supported requests

/// <summary>
/// Container class to represent a 'test' object for a resource on the server.
/// </summary>
class TestObject
{
public TestType Type; // e.g. Sql_Server.
public string TestString; // e.g. connection string for a database. or URI for http request.
public string TestParam; // e.g. sql string for a database. or search string for a http request.

public TestObject(TestType type, string testString, string testParam)
{
this.Type = type;
this.TestString = testString;
this.TestParam = testParam;
}
}

void Page_Load(object sender, EventArgs e)
{
List<TestObject> tests = new List<TestObject>();

tests.Add(new TestObject(TestType.Sql_Server, @"Data Source=.\SQLEXPRESS;Initial Catalog=DB1;Integrated Security=True", "select top 10 * from Table1"));
tests.Add(new TestObject(TestType.Sql_Server, @"Data Source=.\SQLEXPRESS;Initial Catalog=DB2;Integrated Security=True", "select top 10 * from Table1"));
tests.Add(new TestObject(TestType.Sql_Server, @"Data Source=.\SQLEXPRESS;Initial Catalog=DB3;Integrated Security=True", "select top 10 * from Table1"));
tests.Add(new TestObject(TestType.Sql_Server, @"Data Source=.\SQLEXPRESS;Initial Catalog=DB4;Integrated Security=True", "select top 10 * from Table1"));

tests.Add(new TestObject(TestType.Ms_Access, @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Inetpub\Database\DB5.mdb;Persist Security Info=True", "select top 10 * from Table1"));
tests.Add(new TestObject(TestType.Ms_Access, @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Inetpub\Database\DB6.mdb;Persist Security Info=True", "select top 10 * from Table1"));

tests.Add(new TestObject(TestType.Http_Request, "http://mysite1.ie/", "Site 1"));
tests.Add(new TestObject(TestType.Http_Request, "http://mysite2.ie/", "Site 2"));
tests.Add(new TestObject(TestType.Http_Request, "https://mysite3.ie/", "Site 3"));
tests.Add(new TestObject(TestType.Http_Request, "https://mysite4.ie/", "Site 4"));
tests.Add(new TestObject(TestType.Http_Request, "https://mysite5.ie/", "Site 5"));

int numCompleted = 0;
int numFailed = 0;

// write the HTML header. (a result is flushed to the client after each test finishes.)
Flush(@"
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>
<html>
<head>
<title>Server Test</title>
<meta name='ROBOTS' content='NOINDEX,NOFOLLOW'>
<link rel='stylesheet' type='text/css' href='ServerTestStyles.css' />
</head>
<body>");

foreach(TestObject test in tests)
{
try
{
switch(test.Type)
{
case TestType.Sql_Server:
runQuerySql(test.TestParam, test.TestString);
break;
case TestType.Ms_Access:
runQueryOleDb(test.TestParam, test.TestString);
break;
case TestType.Http_Request:
string pageContents = new WebClient().DownloadString(test.TestString);
if(!Regex.IsMatch(pageContents, test.TestParam, RegexOptions.IgnoreCase))
throw new Exception("Search string not found: " + test.TestParam);
break;
default:
throw new Exception("Test type not handled " + test.Type);
}
Flush(String.Format("<span class='pass'>Pass</span> &nbsp; <span class='type'>{0}</span> &nbsp; {1} <hr />", test.Type, test.TestString));
numCompleted++;
}
catch(Exception ex)
{
Flush(String.Format("<span class='fail'>Fail</span> &nbsp; {1} <span class='type'>{0}</span><BR><span class='error'>{2}</span><hr />", test.Type, test.TestString, ex.Message));
numFailed++;
}
}
if(numFailed > 0)
Flush(String.Format("<h1>{0} errors occured</h1>", numFailed));
else
Flush(String.Format("<h1>All Good!</h1>", numFailed)); // if you use this page with an automated monitoring service, look for "All Good" in the page contents. otherwise an error occured
Flush("</body></html>");
}

/// <summary>
/// Method to run an sql string against an sql database
/// </summary>
public static DataSet runQuerySql(string sql, string connString)
{
SqlConnection conn = new SqlConnection(connString);
DataSet ds = new DataSet();
SqlDataAdapter dba = new SqlDataAdapter();
SqlCommand cmd = new SqlCommand(sql, conn);

try
{
dba.SelectCommand = cmd;
dba.Fill(ds, "Table");
return (ds);
}
catch(Exception e)
{
throw e;
}
finally
{
cmd.Connection.Close();
conn.Close();
}
}

/// <summary>
/// Method to run an sql string against an Access database
/// </summary>
public static DataSet runQueryOleDb(string sql, string connString)
{
OleDbConnection conn = new OleDbConnection(connString);
DataSet ds = new DataSet();
OleDbDataAdapter dba = new OleDbDataAdapter();
OleDbCommand cmd = new OleDbCommand(sql, conn);

try
{
dba.SelectCommand = cmd;
dba.Fill(ds, "Table");
return (ds);
}
catch(Exception e)
{
throw e;
}
finally
{
cmd.Connection.Close();
conn.Close();
}
}

/// <summary>
/// Flush output to the browser (useful to indicate which tests are causing any delay)
/// </summary>
/// <param name="output"></param>
private void Flush(string output)
{
Response.Write(output);
Response.Flush();
}

</script>

ServerTestStyles.css:  (just to make the output more legible)

body
{
font-size: 90%;
font-family: Calibri, Helvetica, Sans-Serif;
padding: .5em;
}

hr
{
color: #87ceeb;
background-color: #87ceeb;
margin: .3em 0 .3em 0;
padding: 0;
height: 1px;
}

.pass
{
color: Blue;
font-weight: bold;
}
.fail
{
color: Red;
font-weight: bold;
}
.type
{
color: purple;
font-weight: bold;
}
.error
{
color: Red;
font-size: small;
}

I have configured the test in InternetVista to search for "All Good" in the url for the test page.  If this isn't present, i'll get an SMS/email alert and i can go and see what exactly is wrong.  It should be fairly easy to add other test types if you have different resources you need to check on.
Enjoy.

Comments [3] | | # 
# Friday, March 02, 2007
Friday, March 02, 2007 9:58:51 AM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
Here is the event log crash entry:

Faulting application devenv.exe, version 8.0.50727.762, time stamp 0x45716759, faulting module craxddrt.dll_unloaded, version 0.0.0.0,
time stamp 0x43068582, exception code 0xc0000005, fault offset 0x0d26e30f, process id 0x1180, application start time 0x01c75cae13960a41.

it would crash completely randomly, even if i had closed all crystal reports and was working in a style sheet or other such harmless file. As i later found out, it was because the solution opened by default with the last crystal report i was working on, and this seemed to start some crystal report ActiveX thing that craps out on Vista.  sometimes it would take 2 minutes and sometimes 5 minutes, but it would always crash, even if i closed the report straight away.  The only way to get rid of it was to load up VS, close the report immediately and safely close VS before it got a chance to crash!  then next time it opens, there is no crystal report and the ActiveX control never loads.  it works ok now, although this is just one of a long list of complaints i have with VS 2005.  i think i'm allergic to crystal reports.

Comments [5] | | # 
# Friday, February 23, 2007
Friday, February 23, 2007 5:51:46 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
The full error i got was below:
Error    35    The type 'CrystalDecisions.Shared.ExportFormatType' exists in both 
'c:\Windows\assembly\GAC\CrystalDecisions.Shared\9.1.5000.0__692fbea5521e1304\CrystalDecisions.Shared.dll' and
'c:\Windows\assembly\GAC_MSIL\CrystalDecisions.Shared\10.2.3600.0__692fbea5521e1304\CrystalDecisions.Shared.dll'  
The reason is because version 9 and 10 of crystal reports are installed on my dev box and VS needed help deciding which one to use.  the fix was to specify the exact assembly binding to use in web.config, as follows:
	<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="CrystalDecisions.CrystalReports.Engine" publicKeyToken="692fbea5521e1304" />
<bindingRedirect oldVersion="9.1.5000.0" newVersion="10.2.3600.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CrystalDecisions.CrystalReports.Shared" publicKeyToken="692fbea5521e1304" />
<bindingRedirect oldVersion="9.1.5000.0" newVersion="10.2.3600.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CrystalDecisions.Shared" publicKeyToken="692fbea5521e1304" />
<bindingRedirect oldVersion="9.1.5000.0" newVersion="10.2.3600.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
thanks to rick ersek for the solution.

Comments [2] | | # 
# Tuesday, January 23, 2007
Tuesday, January 23, 2007 3:05:27 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
using Forms auth in an asp.net web site, with a standard Login control, i found a problem today where user 'Joe Bloggs' can log in with 'JOE BLOGGS' as his username.  this messes up my database a little because User.Identity.Name yields 'JOE BLOGGS' and i use this value in the application database. for consistency purposes i only want to use the case-correct version of the username, as it was created.

to fix this, i added the following code to my login
    protected void Login1_LoggedIn(object sender, EventArgs e)
    {
        // correct the case of the username
        foreach(MembershipUser u in Membership.GetAllUsers())
            if(u.UserName.ToLower() == this.Login1.UserName.ToLower())
            {
                // fix the username case
                FormsAuthentication.SetAuthCookie(u.UserName, true);
            }
    }

Comments [4] | | # 
# Wednesday, November 29, 2006
Wednesday, November 29, 2006 4:54:25 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
I was interested to see that Atlas/AJAX has now made it to Beta 2.  I looked at it earlier in the year but didn't have time to really get stuck in.  So today i eventually got it working in conjunction with LINQ.  I wanted to get a basic AutoComplete thing working off my database, with which i am hopefully going to use LINQ throughout.

First of all, go to http://ajax.asp.net/ and download and install the 3 bits they recommend. 

Getting the darn thing to work

Brad Adams has a very good getting-started example on this post, i used his article as a starting point, but i had to sort out a few other things first.  i couldn't get any documentation on the AutoCompleteExtender so i scavenged some crucial facts off the net after a lot of fumbling around, i've listed these below.  One mistake i made was to start testing the AutoComplete control before making sure that the web service was returning correct data, my LINQ query was chucking exceptions and the AJAX control doesn't give you any feedback on exceptions, which is understandable and probably desirable.
  • you must mark your web service class with the [ScriptService] attribute, you'll need the Microsoft.Web.Script.Services namespace for this.
    [WebService(Namespace = "http://whatever")]
    [ScriptService]
    public class AjaxService : System.Web.Services.WebService
    {
  • you need two parameters in your web service method, the naming of the parameters is crucial:
    [WebMethod]
    public string[] GetList(string prefixText, int count)
  • if you're using Linq, and if you're new at it like me, you might have trouble getting your LINQ query results into a string array for the web service.  I tried returning IEnumerable<TableName> and List<string> etc. but it didn't like that, i ended up using the ToArray() extended method that is part of System.Query.  Here is the query i used which concatenates some columns from the table into a collection of strings. 
    IEnumerable<string> ds =
                from r in MyDataBase.Restaurants
                where r.RestaurantName.Contains(prefixText)
                select (r.RestaurantName + "," + r.Address1 + "," + r.County); // join the name + address in a string
    string[] results = ds.ToArray<string>();
    return results;

Formatting the AutoCompleteExtender

The control has some nasty hard-coded values, but where there's a will there's a way.  Thanks to the Firefox dom inspector and the CSS !important modifier, all is not lost. 

The control does have a property called CompletionListElementID which specifies the container element where the results are inserted.  I put a simple DIV next to the textbox:
<div id="AutoComplete" runat="server"> </div>
Then in my stylesheet i have the following values to override the hard-coded styles set by the control:
#AutoComplete
{
    width: auto !important;   
    overflow: visible !important;
}
#AutoComplete div
{
    font-size: x-small !important;   
}
In case you're wondering, the control renders each result as a DIV inside your specified element.  The control applies the following styles to your specified element:
border: 1px solid buttonshadow; 
overflow: hidden;
visibility: visible;
background-color: window;
color: windowtext;
cursor: default;
width: 130px;
position: absolute;
left: 184px;
top: 27px;
display: inline;

You can override whichever ones you want by applying the !important attribute inside the #AutoComplete entry in the style sheet.  The individual items then can also be styled via the #AutoComplete div entry, i prefered a slightly smaller font size because the default setting looked too big.

Comments [4] | | # 
# Saturday, October 07, 2006
Saturday, October 07, 2006 1:32:30 AM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net | Windows Server 2003 )
i found this insanely difficult because none of the supposedly normal options worked.  merge modules didn't seem suitable because i just wanted to install the CR dlls once and for all on the server.  according to an MSDN2 article i should be able to perform a windows installer deployment, but of course when i copy the key code from the VS2005 > Help > About dialog, and use it in the installer on the server, the error message is that the key code has expired or is invalid.  'business objects' appear to be forcing users into an upgrade path for their new product, by claiming that a 'compatibility upgrade' is required for VS 2005, which of course you have to fork out for.  such a load of crap. 
anyway, what eventually worked for me was to go back into my VS 2005 dev machine and create a web set up project. go into the project properties and click the prerequisites button, select CR for .Net 2.0 and then just build the empty setup project. if you look in your output folder, there is an installation file called CRRedist2005_x86.msi.  Just whack this onto the server and your crystal reports should run fine, i didn't need to reboot or restart IIS.  Note there is no also need to copy any of the CR dlls to your web site bin folder. 

yet another miserable failure for crystal reports!

Comments [1] | | # 
# Wednesday, September 06, 2006
Wednesday, September 06, 2006 11:50:04 AM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
I encountered this error after upgrading a web site to VS2005 and converting it to a Web Application Project.  the project built without problems, it was just the designer kept throwing up errors in the error list which was very annoying.

Thanks to 'ratureus' for his post on http://forums.asp.net/thread/1212425.aspx

  • Close VS 2005
  • delete all files in C:\Documents and Settings\yourusername\Application Data\Microsoft\VisualStudio\8.0\ReflectedSchemas\
  • Restart VS 2005
this worked for me. 

Comments [0] | | # 
# Tuesday, September 05, 2006
Tuesday, September 05, 2006 3:29:49 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
I use exported crystal reports in PDF format on my web site, and i was getting intermittent errors from a small number of users.  I couldn't reproduce it, and i found a thoroughly excellent article on the problem at the Fluid Components International web site. 

They explain what's going wrong and have some suggestions to fix it.  I suspect in my case it was a problem with embedding the PDF in an activeX control.  i'll just redirect users to download the file instead. 
Comments [0] | | # 
# Wednesday, August 30, 2006
Wednesday, August 30, 2006 5:12:43 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
http://www.dynamicdrive.com/dynamicindex16/maxlength.htm

<script type="text/javascript">

/***********************************************
* Textarea Maxlength script- © Dynamic Drive (www.dynamicdrive.com)
* This notice must stay intact for legal use.
* Visit http://www.dynamicdrive.com/ for full source code
***********************************************/

function ismaxlength(obj){
var mlength=obj.getAttribute? parseInt(obj.getAttribute("maxlength")) : ""
if (obj.getAttribute && obj.value.length>mlength)
obj.value=obj.value.substring(0,mlength)
}

</script>


<textarea maxlength="40" onkeyup="return ismaxlength(this)"></textarea>

Comments [0] | | # 
# Wednesday, August 16, 2006
Wednesday, August 16, 2006 4:39:04 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net | General )
i took this off Wikipedia and trimmed it down to plain text for a database.  just posting it here for reference:

You may be more interested in the ISO official list of countries and their 2-digit codes.

Abkhazia
Afghanistan
SBA Akrotiri and Dhekelia
Ã…land
Albania
Algeria
American Samoa
Andorra
Angola
Anguilla
Antigua and Barbuda
Argentina
Armenia
Aruba
Saint Helena Ascension Island
Australia
Austria
Azerbaijan
The Bahamas
Bahrain
Bangladesh
Barbados
Belarus
Belgium
Belize
Benin
Bermuda
Bhutan
Bolivia
Bosnia and Herzegovina
Botswana
Brazil
Brunei
Bulgaria
Burkina Faso
Burundi
Cambodia
Cameroon
Canada
Cape Verde
Cayman Islands
Central African Republic
Chad
Chile
China
Christmas Island
Cocos (Keeling) Islands
Colombia
Comoros
Congo
Cook Islands
Costa Rica
Côte d'Ivoire
Croatia
Cuba
Cyprus
Czech Republic
Denmark
Djibouti
Dominica
Dominican
Ecuador
Egypt
El Salvador
Equatorial
Eritrea
Estonia
Ethiopia
Falkland
Faroe Islands
Fiji
Finland
France
French Polynesia
Gabon
Gambia
Georgia
Germany
Ghana
Gibraltar
Greece
Greenland
Grenada
Guam
Guatemala
Guernsey
Guinea
Guinea-Bissau
Guyana
Haiti
Honduras
Hong Kong
Hungary
Iceland
India
Indonesia
Iran
Iraq
Ireland
Isle of Man
Israel
Italy
Jamaica
Japan
Jersey
Jordan
Kazakhstan
Kenya
Kiribati
North Korea
South Korea
Kosovo
Kuwait
Kyrgyzstan
Laos
Latvia
Lebanon
Lesotho
Liberia
Libya
Liechtenstein
Lithuania
Luxembourg
Macau
Macedonia
Madagascar
Malawi
Malaysia
Maldives
Mali
Malta
Marshall Islands
Mauritania
Mauritius
Mayotte
Mexico
Micronesia
Moldova
Monaco
Mongolia
Montenegro
Montserrat
Morocco
Mozambique
Myanmar
Nagorno-Karabakh
Namibia
Nauru
Nepal
Netherlands
Netherlands Antilles
New Caledonia
New Zealand
Nicaragua
Niger
Nigeria
Niue
Norfolk Island
Northern Cyprus
Northern Mariana Islands
Norway
Oman
Pakistan
Palau
Palestinian
Panama
Papua New Guinea
Paraguay
Peru
Philippines
Pitcairn Islands
Poland
Portugal
Pridnestrovian Moldavian Republic
Puerto Rico
Qatar
Romania
Russia
Rwanda
Saint Helena
Saint Kitts and Nevis
Saint Lucia
Saint Pierre and Miquelon
Saint Vincent and the Grenadines
Samoa
San Marino
São Tomé and Príncipe
Saudi Arabia
Senegal
Serbia
Seychelles
Sierra Leone
Singapore
Slovakia
Slovenia
Solomon Islands
Somalia
Somaliland
South Africa
South Ossetia
Spain
Sri Lanka
Sudan
Suriname
Svalbard
Swaziland
Sweden
Switzerland
Syria
Tajikistan
Tanzania
Thailand
East Timor
Togo
Tokelau
Tonga
Trinidad and Tobago
Tristan da Cunha
Tunisia
Turkey
Turkmenistan
Turks and Caicos Islands
Tuvalu
Uganda
Ukraine
United Arab Emirates
United Kingdom
United States
Uruguay
Uzbekistan
Vanuatu
Vatican City
Venezuela
Vietnam
Virgin Islands (British)
Virgin Islands (United States)
Wallis and Futuna
Western Sahara
Yemen
Zambia
Zimbabwe

Comments [5] | | # 
# Friday, August 11, 2006
Friday, August 11, 2006 1:20:57 PM (GMT Daylight Time, UTC+01:00) ( .Net General | .Net Windows Forms | Asp.Net )
I have a few reports in my web application that i export in PDF format only.  You would think this would remove any printer driver complications but apparently not.
Crystal Reports have a surprisingly good article to help troubleshoot issues with printing or exporting a report.

The problem i was finding was that in a Text object or Unbound field, the text would be truncated at the edge of the box boundary.  Some users have reported strange mid-word wrapping, but mine was a different problem, actually hacking off the end of each line.  This didn't happen on my dev machine, but it did happen on the server (2003).  When i compared them side by side, the Times New Roman font on the server was stretched by about 5% compared to my dev machine.
with the help of the CR article, i tracked it down to the lack of any printer installed on the server, i had also disabled the Print Spooler service which was preventing even the Microsoft Office Document Imaging Printer from being available.  So i started the print spooler service and made sure that the Office Imaging Printer was marked as the default printer.  Then i went back to Visual Studio and set all the reports to use the Office Imaging printer.  This solved the problem. 

You could of course install a PDF only printer such as PrimoPDF if you didn't have MS office installed on the server. As long as the server has the same printer installed as the one set in the report design, everything turns out fine.

Comments [1] | | # 
# Thursday, August 10, 2006
Thursday, August 10, 2006 5:31:20 PM (GMT Daylight Time, UTC+01:00) ( .Net General | .Net Windows Forms | Asp.Net )
I was getting blank fields in my crystal report if one of the values in the formula was null.
Thanks to this post i know better than to trust Crystal's crummy concatenation rules.  Here is what Gundula Wangerin had to say:
A string concatenated with null always gives null.
You have to check if the first name is null with
     if not IsNull(firstname) then 
        lastname + " " + firstname
    else
        lastname

Comments [3] | | # 
# Friday, August 04, 2006
Friday, August 04, 2006 11:26:38 AM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net | Windows Server 2003 )
I picked up a tip on the Internet to display a "No records" message if the report contains no records, rather than leaving the user with an empty screen.  Simply Format the text field and enter a formula next to 'Suppress' with something like
Count({Table.Field}) > 0
This will suppress (hide) the message if there are records in the report.

I tried using a similar approach to hide a text field when there are no records, so it is essentially the same thing in reverse.  You would think a simple formula on the 'suppress' property this would achieve the desired effect:
Count({Table.Field}) = 0

But apparently the value returned by Count can be null if there are no records.  so you have to use:

IsNull(Count({Table.Field})) OR Count({Table.Field}) = 0

This is just another crystal reports annoyance, of which there are many.

Comments [9] | | # 
# Wednesday, August 02, 2006
Wednesday, August 02, 2006 8:45:07 PM (GMT Daylight Time, UTC+01:00) ( .Net General | Asp.Net | Windows Server 2003 )
i'm part of the dd/MM/yyyy world, and that often means running into problems when using software developed with MM/dd/yyyy defaults, such as crystal reports. 
i have all the report options set to use the system defaults, which are regionally set to ireland in windows, but that isn't enough, the dates still come out in MM/dd/yyyy format.  i have the date + date/time fields customised within crystal reports to dd/MM/yyyy but that isn't enough either.  with lots of hours googling and no answers that worked, i resorted to searching the registry and found that some of the user accounts were still using US regional settings.  If you look in HKEY_USERS > you see a list of all the account IDs on the computer.  My guess is that crystal reports must use the SYSTEM account or another non-interactive account, and it takes the regional settings from there.  So if you search for sShortDate in the registry, you will find all the appropriate settings and can replace the MM/dd/yyyy values with your preferred format.
i had to reboot to get it to take effect. 

Comments [12] | | # 
# Thursday, July 27, 2006
Thursday, July 27, 2006 4:31:47 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
i ran into a problem with my Crystal Reports.  instead of using the usual Typed Dataset approach, where you use Report.SetDataSource(DataSet), i decided that was too much hastle and it seems faster and simpler just to design the query in the Crystal Reports 'wizard', and then supply parameters and set the record filter appropriately.  what i didn't know was that it was insanely complicated to direct the report to a different database path at runtime (such as most users would have in their deployment environment, whether for Access or SQL server). 

i was using Report.SetDataBaseLogon("admin", "") etc and all sorts of variations but none of them were working.  I kept getting Logon Failed exceptions.  Apparently you need to iterate through each table used in the report and set the source database for each one.  Not only that, but neither of the following lines of code has any effect:
Report.Database.Tables[i].SetDataSource("admin", "", DbPath, "")
Report.Database.Tables[i].LogOnInfo.ConnectionInfo.ServerName = DbPath
apparently you have to create a TableLogOnInfo object and use ApplyLogOnInfo() with that on each table. 
here is what does work:
TableLogOnInfo t = new TableLogOnInfo();
t.ConnectionInfo.ServerName = dbPath;            
for(int i=0; i<rpt.Database.Tables.Count; i++)
    rpt.Database.Tables[i].ApplyLogOnInfo(t);

Comments [3] | | # 
# Monday, April 24, 2006
Monday, April 24, 2006 5:10:13 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
if you're like me and use the onbeforeunload event in a web page to display a reminder to a user who might unintentially lose work by browsing away from the page, then you might be trying to find a way to cancel the event in the case where the user clicks the save button (they obviously don't need reminding now that they have clicked the button...)

on the internet you will find many people suggesting you use something like
window.onbeforeunload = null
but this does not work for me in IE6.  reading up in the MSDN docs for the event object i found a reference to the event.cancelBubble property, which i thought was the solution. but thanks to Orso who pointed out that setting "event.cancelBubble=true" is useless, the way to get rid of the confirm prompt is to exclude the return statement altogether, i chose to use a boolean variable as a flag to decide whether to return something or not. in the example below i add the javascript code programattically in the code behind:

	Page.ClientScript.RegisterStartupScript(typeof(String), "ConfirmClose", @"
<script>
window.onbeforeunload = confirmExit;
function confirmExit()
{
if(postback == false)
return ""Please don't leave this page without clicking the 'Save Changes' or 'Discard Changes' buttons."";
}
</script>");

then my save button contains the following aspx markup:
    OnClientClick="postback=true;return true;"
this sets the 'postback' variable to true, which gets picked up in the confirmExit() function, having the effect of cancelling the event. 

hope you find this useful.  it is tested and works in IE6 and FF 1.5.0.2.

Comments [16] | | # 
# Wednesday, April 12, 2006
Wednesday, April 12, 2006 3:27:51 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
my aspx page uses a master page, so all the content is inside a ContentPlaceHolder. 
aparently Page.FindControl() only works within a single container, which is really poor functionality if you ask me. 
to complicate the situation further, my aspx page has dynamic controls inside a Panel. 
in order to access the controls of my user control, from the code-behind of the page, i first had to find the panel and then the user control, and then i could finally use that to find the controls i wanted. 
note: you don't have to use the client ID of the control ...ctrl00_blah_blah_blah_YourControlName... when you use the above method, you just use the name of the control, e.g. txtFirstName. 

Comments [0] | | # 
# Friday, March 03, 2006
Friday, March 03, 2006 3:43:20 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
I wanted to install my web application assembly into the GAC, but this is made more complicated by the multitude of assemblies produced by VS when i publish the web site.  On the newsgroups, i found some talk of a tool called Merge_Aspnet.exe but i couldn't find it anywhere.  Eventually i found it as a download on MSDN, it is bundled as part of the Web Deployment Projects.  You install it, and then right-click your project in VS and you should see a new menu item "Add Web Deployment Project".  I am baffled as to why they didn't just add a new project type in the list of projects under "Deployment".  There is a link to "Search online templates" so i think it should really be available there.  but it looks like MS did a hack just to add in a new item to the project context menu.  but it works... so i'll stop complaining. 

Comments [0] | | # 
# Saturday, February 18, 2006
Saturday, February 18, 2006 7:11:26 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
In a web application i'm working on, i needed to get write access to a folder, and preferably not the temp folder because the data should be kept reasonably safe from being deleted accidentally.  If it was a windows-forms application this would be easy, i would just use Application.UserAppDataPath.  So i thought for asp.net, i could use Environment.SpecialFolder.ApplicationData but this maps to the folder for the current user logged on to Windows, not the process running the web site, or the authenticated web site user.  So i just skip up 2 parent directories from that, and use the current Principal.WindowsIdentity to find the actual process running ASP.NET, and then deduce the correct folder, which by default will have write permissions for Asp.Net.

Here is the code:
string Username = Path.GetFileName(System.Security.Principal.WindowsIdentity.GetCurrent().Name).Replace(" ", "");
string UserAppFolder = String.Format(@"{0}\{1}\Application Data", Directory.GetParent(Directory.GetParent(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)).FullName).FullName, Username);

By the way, the use of Path.GetFileName is entirely intentional, it solves the case where the current user is "NT Authority\Network Service" by yielding "Network Service", which is what we are interested in.  

This will hopefully come in handy for my 'zero configuration' idea for the application.  Next time you need a write folder, it may be a good idea to use this instead of making users specify write permissions for a custom folder. 
Comments [2] | | # 
# Thursday, February 02, 2006
Thursday, February 02, 2006 1:38:34 PM (GMT Standard Time, UTC+00:00) ( .Net General | .Net Windows Forms | Asp.Net | Windows Server 2003 )
For some reason i can't get SelectNodes to work with the IIS 6 MetaBase.xml file.  It just doesn't return any matches.  I think the structure may be non-standard or too complex, or else i'm not using it right.  but in any case, I worked around the problem by opening the file with File.OpenText and using Regex to parse out the nodes i'm interested in, namely the IISWebServer nodes.

string MetaBase = "";
using(StreamReader sr = File.OpenText(ConfigurationManager.AppSettings["MetaBasePath"].ToString()))
MetaBase = sr.ReadToEnd();

foreach(Match m in new Regex(@"(?x:)<IIsWebServer[^>]*>.*?</IIsWebServer>", RegexOptions.IgnoreCase).Matches(MetaBase))
{
XmlDocument doc = new XmlDocument();
string IisCode = "", ServerBindings = "", ServerComment = "";
doc.LoadXml(m.Groups[0].Value);
XmlElement node = doc.DocumentElement;
// now you can access the node attributes
...

Comments [0] | | # 
Thursday, February 02, 2006 1:25:33 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
<script language="javascript" type="text/javascript">
function Select(Select)
{
for (var n=0; n < document.forms[0].length; n++)
if (document.forms[0].elements[n].type=='checkbox')
document.forms[0].elements[n].checked=Select;
return false;
}
</script>


Select <a href="#" onclick="javascript:Select(true)">All</a> | <a href="#" onclick="javascript:Select(false)">None</a>

Comments [7] | | # 
# Monday, January 30, 2006
Monday, January 30, 2006 10:53:44 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net )
i kept getting these sporadic error messages on my web applications and i could never figure out why. 
Unable to validate data at
System.Web.Configuration.MachineKey.GetDecodedData(Byte[] buf, Byte[] modifier, Int32 start, Int32 length, Int32& dataLength) at
System.Web.UI.LosFormatter.Deserialize(String input)
searching the net just reveals a troop of similarly frustrated users without solutions.  but today i found the actual reason why, and the work-around.
this excellent post on experts-exchange has the answer.  apparently it happens when users leave a page open for a long time, and then cause a post back.  something to do with the machine key being automatically generated and it changes before the user causes the postback, and it can't validate it then. 
the fix is to set a static machine key. 
There is a kb article with some code to generate a key for you, + instructions.  

Sorted.

Comments [13] | | # 
Monday, January 30, 2006 3:30:12 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net | Database )
a web site i'm working on imports excel documents, and does some processing on them for importing into a database.  I use the code from this post to do the importing, and it works nicely.  I recently came across a problem where i was encountering duplicate records, and it took me ages to figure out why.  Apparently a 'named range' of cells in a worksheet is treated as a Table by ADOX.  so you get more than you bargain for when you iterate through the tables in the resulting DataSet. 
I was able to work around the problem by discarding any tables that do NOT end in the dollar $ character.

Comments [1] | | # 
# Wednesday, January 25, 2006
Wednesday, January 25, 2006 1:52:05 PM (GMT Standard Time, UTC+00:00) ( .Net General | .Net Windows Forms | Asp.Net )
I have a text object in my crystal report, and i set the value programatically for it, using something like this:
(rpt.Section3.ReportObjects["txtDate"] as TextObject).Text = DateTime.Now.ToShortDateString();
this works fine, until you put a string with line breaks inside it, specifically \r\n or the carriage return character.  This is a bug in CR for .Net, you can read the official blurb here.  the work-around code they post is in VB, and it made no sense to me when i read it.
what they actually do is make you change the TextObject into a FormulaField (you have to view the 'Field Explorer' tab next to the toolbox, and drag on a Formula Field).  Then you set 'CanGrow' to true, and then you go back to your code, and do the most arcane work-around i've ever seen.  you set the formula to your text string, but you must surround it in single quote characters, and replace \r\n with some inline managed code as follows: ' + chr(10) + '
when i first read this, i thought they had made a syntax error, but this is the way to do it, and it works.  the new format for setting the formula field is:
rpt.DataDefinition.FormulaFields[0].Text = "'" + YourString.Replace("\r\n", "' + chr(10) + '") + "'";

Comments [15] | | # 
# Friday, January 13, 2006
Friday, January 13, 2006 4:28:16 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
This is the first time i have been asked to add localization support for a content management system.  One of the supported languages is Arabic, which is usually written right-to-left.  to achieve this, you could obviously change your CSS or markup with something like <p align="right"> but this is not the way to do it.

there is an attribute in the HTML tag that controls the direction of text (and controls) on the page, called 'dir', which can take values 'ltr' or 'rtl', corresponding to "left-to-right" and "right-to-left" respectively.  The advantage of setting this property is that it instructs the browser to render all direction-related elements of the page accordingly.  for example, in RTL mode, when you type in a text box, the cursor stays flush to the right and the characters spread across to the left as you type.  similarly drop-down-menu's are rendered differently, as are bullet points, and the browser scrollbar.  these changes are shown in the screenshot below:



In this content management system, i set the direction based on the user's language setting, stored in a cookie.  to set the DIR attribute programatically, i suggest the following HTML tag in your Master/Template aspx page:
<html xmlns="http://www.w3.org/1999/xhtml" runat="server" id="htmlTag">
Then you can access it in the code behind, like so:
protected void Page_Load(object sender, EventArgs e)
{
this.htmlTag.Attributes.Add("dir", Util.HtmlDir);
...
and then the method to determine the direction, based on the cookie. note that 'Language' in my code is an enumeration.
/// <summary>
/// Returns rtl or ltr, depending on the current language setting
/// </summary>
public static string HtmlDir
{
get
{
Language lang;
try
{
lang = (Language)Enum.Parse(typeof(Language), HttpContext.Current.Request.Cookies["Language"].Value.ToString());
}
catch
{
// cookie not present, default to english.
lang = Language.English;
}
switch(lang)
{
case Language.Arabic:
return "RTL"; // right to left
default:
return "LTR"; // left to right
}
}
}

public enum Language
{
English,
Arabic,
...
}

for more information on languages and their directions, and related browser issues, see this excellent article from W3C

Comments [2] | | # 
# Thursday, January 12, 2006
Thursday, January 12, 2006 4:01:38 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
for some bizarre reason, you have to set HtmlEncode=false on a bound column in a gridview, to get the DataFormatString to work.
i hope this helps somebody else staring at their gridview in disbelief as to why it doesn't work by default!

Comments [64] | | # 
# Monday, January 09, 2006
Monday, January 09, 2006 5:57:34 PM (GMT Standard Time, UTC+00:00) ( .Net General | .Net Windows Forms | Asp.Net )
I ran into a frustrating problem today where some properties of my objects weren't getting serialized.  I noticed it was the ones without 'set' accessors.  I didn't want to put set accessors in because the properties should be read-only, but apparently the xml serializer needs the set accessor to de-serialize. 
there is a good discussion on it on this thread.
Luckily we can use the ReadOnlyAttribute setting on the property to prevent it being modified by a programmer at design time.
or you can use the Soap serializer instead of xml, whatever that is.

Comments [0] | | # 
# Thursday, December 29, 2005
Thursday, December 29, 2005 8:34:15 PM (GMT Standard Time, UTC+00:00) ( .Net General | .Net Windows Forms | Asp.Net )
just posting my code-project article on http chunking web services with MTOM here for reference.  by the way it has been updated on codeproject...

Screenshot of windows forms client, uploading a file

Introduction

In trying to keep up to speed with .NET 2.0, I decided to do a .NET 2.0 version of my CodeProject article "DIME Buffered Upload" which used the DIME standard to transfer binary data over web services. The DIME approach was reasonably efficient but the code is quite complex and I was keen to explore what .NET 2.0 has to offer. In this article, I use version 3.0 of the WSE (Web Service Enhancements) which is available for .NET 2.0 as an add-in, to provide a simpler and faster method of sending binary data in small chunks over HTTP web services.

Background

Just a recap on why you may need to send data in small chunks at all: if you have a large file and you want to send it across a web service, you must understand the way it all fits together between IIS, .NET, and the web service call. You send your file as an array of bytes, as a parameter to a web service call, which is all sent to the IIS web server as a single request. This is bad if the size of the file is beyond the configured MaxRequestLength of your application, or if the request causes an IIS timeout. It is also bad from the point of view of providing feedback of the file transfer to the user interface because you have no indication how the transfer is going, until it is either completed or failed. The solution outlined here is to send chunks of the file one by one, and append them to the file on the server.

There is an MD5 file hash done on the client and the server to verify that the file received is identical to the file sent.

Also, there is an upload and download code included in this article.

Adventures with MTOM

MTOM stands for SOAP "Message Transmission Optimization Mechanism" and it is a W3C standard. To use it (and to run this application), you must download and install WSE 3.0, which includes MTOM support for the first time. If you look in the app.config and web.config files in the source code, you will see sections referring to the WSE 3 assembly, and a messaging clientMode or serverMode setting. These are necessary to run MTOM in the application.

The problem with DIME was that the binary content of the message was sent outside the SoapEnvelope of the XML message. This meant that although your message was secure, the Dime Attachment may not be secure. MTOM fully complies with the other WS-* specifications (like WS-Security) so the entire message is secure.

It took me a while to realise that when MTOM is turned on for the client and the server, WSE automatically handles the binary encoding of the data in the web service message. With DIME and WSE 2.0, you had to configure your app for DIME and then use DimeAttachments in your code. This is no longer necessary, you just send your byte[] as a parameter or return value, and WSE makes sure that it is sent as binary, and not padded by XML serialization as it would be in the absence of DIME or MTOM.

How it works

The web service has two main methods, AppendChunk is for uploading a file to the server, DownloadChunk is for downloading from the server. These methods receive parameters for the file name, the offset of the chunk, and the size of the buffer being sent/received.

The Windows Forms client application can upload a file by sending all the chunks one after the other using AppendChunk, until the file has been completely sent. It can do an MD5 hash on the local file, and compare it with the hash on the file on the server, to make sure the contents of the files are identical. The download code is very similar, the main difference is that the client must know from the server how big the file is, so that it can know when to stop requesting chunks.

A simplified version of the upload code is shown below (from the WinForms client). Have a look in the code for Form1.cs to see the inline comments + the explanation of the code. Essentially, a file stream is opened on the client for the duration of the transfer. Then the first chunk is read into the Buffer byte array. The while loop keeps running until the FileStream.Read() method returns 0, i.e. the end of the file has been reached. For each iteration, the buffer is sent directly to the web service as a byte[]. The 'SentBytes' variable is used to report progress to the form.

using(FileStream fs = new FileStream(LocalFilePath, FileMode.Open, FileAccess.Read))
{
int BytesRead = fs.Read(Buffer, 0, ChunkSize);
while(BytesRead > 0 && !worker.CancellationPending)
{
ws.AppendChunk(FileName, Buffer, SentBytes, BytesRead);
SentBytes += BytesRead;
BytesRead = fs.Read(Buffer, 0, ChunkSize);
}
}

Example of the BackgroundWorker class in .NET 2.0

.NET 2.0 has a great new class called 'BackgroundWorker' to simplify running tasks asynchronously. Although this application sends the file in small chunks, even these small chunks would delay the WinForms application and make it look crashed during the transfer. So the web service calls still need to be done asynchronously. The BackgroundWorker class works using an event model, where you have code sections to run for DoWork (when you start), ProgressChanged (to update your progress bar / status bar), and Completed (or failed). You can pass parameters to the DoWork method, which you could not do with the Thread class in .NET 1.1 (I know you could with delegates, but delegates aren't great for thread control). You can also access the return value of DoWork in the Completed event handler. So for once, MS has thought of everything and made a very clean threading model. Exceptions are handled internally and you can access them in the Completed method via the RunWorkerCompletedEventArgs.Error property.

The code shown below is an example of the ProgressChanged event handler:

private void workerUpload_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// update the progress bar and status bar text
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.statusText.Text = e.UserState.ToString();
// summary text is sent in the UserState parameter
}

I have used four BackgroundWorker objects in the application:

  • one to manage the upload process,
  • one to manage the download process,
  • another to calculate the local MD5 file hash in parallel while waiting for the server result,
  • and another to download the list of files in the Upload server folder to allow the user to select a file to download.

The reason I use a BackgroundWorker object for each task is because the code for each task is tied in to the events for that object.

A good example of Thread.Join()

When the upload or download is complete, the client asks for an MD5 hash of the file on the server, so it can compare it with the local file to make sure they are identical. I originally did these in sequence. But it can take a few seconds to calculate the result for a large file (anything over a few hundred MB), so the application was waiting five seconds for the server to calculate the hash, and then five more seconds for the client to calculate its own hash. This made no sense, so I decided to implement a multi-threaded approach to allow them to run in parallel. While the client is waiting on the server, it should be calculating its own file hash. This is done with the Thread class, and the use of the Join() method which blocks execution until the thread is complete.

The code below shows how this is accomplished:

// start calculating the local hash (stored in class variable)
this.hashThread = new Thread(new ThreadStart(this.CheckFileHash));
this.hashThread.Start();

// request the server hash
string ServerFileHash = ws.CheckFileHash(FileName);

// wait for the local hash to complete
this.hashThread.Join();

if(this.LocalFileHash == ServerFileHash)
e.Result = "Hashes match exactly";
else
e.Result = "Hashes do not match";

There is a good chance that the two operations will finish at approximately the same time, so very little waiting around will actually happen.

Performance compared with DIME

I found that MTOM was about 10% faster than DIME in my limited testing. This is probably to do with the need to package up each chunk into a DIME attachment, which is no longer necessary with MTOM. I was able to upload files of several gigabytes in size without problems.

Obviously, there is an overhead with all this business of reading file chunks and appending them, so the larger the chunk size, the more efficient your application will be. It should be customised based on the network and the expected size of files. For very small files, it is no harm to use small chunk sizes (e.g., 32 Kb) because this will give accurate and regular feedback to the user interface. For very large files on a fast network, consider using 4000 Kb to make good use of the bandwidth and reduce the File Input/Output overhead. If you want to send chunks larger than 4 MB, you must increase the .NET 2.0 Max Request Size limit in your web.config.

Conclusions

Feel free to use this code and modify it as you please. Please post a comment for any bugs, suggestions, or improvements. Enjoy!

Comments [24] | | # 
# Thursday, December 22, 2005
Thursday, December 22, 2005 4:13:34 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
I'm new to MTOM, and i encountered this error while developing a WSE 3.0 application with .net.
The request failed with HTTP status 415: Unsupported media type

Explanation 1 - Basic Configuration Error

After a lot of digging around and comparing my code with the MTOM sample bundled with WSE3, i found out it was because i was missing a setting called "soapServerProtocolFactory" in my web.config, shown below.  I didn't think i needed to have a Protocol Factory (whatever that is!), but apparently I do. It works fine now that i added that section.

This is what my web.config looks like:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<appSettings>
<add key="UploadPath" value="Upload" />
</appSettings>
<system.web>
<webServices>
<soapServerProtocolFactory type="Microsoft.Web.Services3.WseProtocolFactory, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<soapExtensionImporterTypes>
<add type="Microsoft.Web.Services3.Description.WseExtensionImporter, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</soapExtensionImporterTypes>
</webServices>
<compilation>
<assemblies>
<add assembly="Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
</system.web>
<microsoft.web.services3>
<messaging>
<mtom serverMode="optional" />
</messaging>
</microsoft.web.services3>
</configuration>
My app.config for the Windows Client looks like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="microsoft.web.services3" type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="UploadWinClient.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<microsoft.web.services3>
<messaging>
<mtom clientMode="On" />
</messaging>
</microsoft.web.services3>
<applicationSettings>
<UploadWinClient.Properties.Settings>
<setting name="UploadWinClient_MTOM_WebService_MTOM" serializeAs="String">
<value>http://localhost:1423/UploadWebClient/MTOM.asmx</value>
</setting>
</UploadWinClient.Properties.Settings>
</applicationSettings>
</configuration>

Explanation 2 - Strange Behaviour with Web Service instances...

if you're configuration is already correct, then the following may be of interest.  i came across this error in a winforms application, that has a class library with an MTOM web service in it.  my code had something like this:
myClassLibrary.WebService = this.WebService;
i was doing this because i had an instance of the web service in the win form, authenticated with a cookie in its CookieContainer property, and i wanted to replace the web service that the class library was using with the one from my win-form.  however this caused the 415 error to appear sporadically all over the place.  the fix was to just replace the CookieContainer instead of the entire web service instance.  like so:
myClassLibrary.WebService.CookieContainer = this.WebService.CookieContainer;

now i get no errors.  weird huh?
Comments [7] | | # 
# Wednesday, December 21, 2005
Wednesday, December 21, 2005 3:51:28 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )

A field or property with the name 'xyz' was not found on the selected data source

I get this error when i bind a GridView to an array of custom objects with public variables. 
The recommended pattern for designing classes is to have private variables with public properties with get/set etc, but for my current application i'm not bothering with that. Apparently because of the Reflection methods used in GridView databinding, you have to have properties to bind fields in the GridView to your object fields. 

bit of a pain.

Comments [3] | | # 
# Tuesday, December 20, 2005
Tuesday, December 20, 2005 5:52:22 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
in .net 1.1, the GridCommandEventArgs (or whatever) used to give access to the row that triggered the command. 
in .net 2.0, it isn't as obvious as that.  Fritz posted an excellent discussion on the matter, and i'm just posting his 2-line solution here for reference.

If your button is a ButtonField, then you can access the index of the item via e.CommandArgument.  This may be enough information to do whatever you need.

If your button is a LinkButton in a TemplateField, you have to add the CommandArgument as an attribute to the LinkButton:
CommandArgument='<%# Eval("id") %>'
Then in the code behind for RowCommand, you can use the following syntax:
string id = (string)e.CommandArgument;
thanks Fritz!
Comments [0] | | # 
# Friday, December 16, 2005
Friday, December 16, 2005 2:21:53 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )

i'm just experimenting with the gridview control, and i got stuck trying to delete an object with the auto-included delete button.  the delete method would get invoked properly, but the ID parameter was always zero, which means the int parameter was uninitialised.

my problem was that i didn't have the DataKeyNames property set in the Gridview. I just set it to 'ID' and it worked fine then.

Comments [17] | | # 
# Thursday, December 15, 2005
Thursday, December 15, 2005 5:18:34 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )

Microsoft love small fixed fonts for some reason

Correction: this only applies to the Asp.Net development server (v8), so it's not all that important...

i noticed if you browse a folder on a .net 2 virtual directory, you get the directory listing.  but the font styles are set to 8 point verdana which is uncomfortably small for some users on some resolutions.  the fact that they hard-coded it in the css styles means it ignores the browser font-size setting, so it breaks a very important accessibility guideline.  

this is just another reason to use firefox which lets you increase font sizes for fixed sizes. surely MS would be trying to hang on to their user population who still uses IE.  
i looked in the source of the directory listing, and i see they have the file and folder info formatted inside <pre> tags, keeping the tabs aligned nicely.  but i took a copy of the source and removed the font sizes, and the alignment is still fine when you change the browser font size!

come on MS... why the love affair with "px" and "pt" for font sizes?  
i remember gotdotnet.com had fixed sized fonts also.  it's a silly decision in my book, let your users control the font size.  

i've just been experimenting with VS2005 and i like the improved support for xhtml and accessibility, but lots of people actually use directory listings, so why not make them accessible also?

Comments [0] | | # 
Thursday, December 15, 2005 1:28:01 PM (GMT Standard Time, UTC+00:00) ( .Net General | Asp.Net | Database )
i was experimenting with how .Net 2.0 does xml serialization of objects, and i got it serializing nicely with the following code. 
XmlSerializer xs = new XmlSerializer(typeof(PageCollection));
xs.Serialize(fs, pages); // 'fs' is a FileStream to my xml file, and 'pages' is a collection class of objects

the problem was when i tried to deserialize it, like so:
XmlSerializer xs = new XmlSerializer(typeof(PageCollection));
pages = xs.Deserialize(fs) as PageCollection;

i got this error:

xmlns=''> was not expected

i found this post on google which described the same errors, and a workaround (by adding an empty namespace) but it didn't work for me.  perhaps it is a difference in the serialization process with with .Net 2.0.  what fixed it for me was setting an XmlRootAttribute for the class i was serializing. like so:
[XmlRootAttribute("CmsPages", Namespace = "http://www.whatever.com/Cms", IsNullable = false)]
hope this helps someone else out there with the same problem.
Comments [0] | | # 
# Friday, December 02, 2005
Friday, December 02, 2005 12:26:22 PM (GMT Standard Time, UTC+00:00) ( .Net General | .Net Windows Forms | Asp.Net | Database )

I had a transaction comprised of about 5 commands, and in the middle of it, i needed to do a SELECT on a table to know what values to insert for one of the commands.  I encountered a timeout just after i called Fill on the DataAdapter.  it took me a while to figure out that it was because the transaction had already taken out a lock on the table i was selecting from.  makes sense now, but i spent ages on it!  just posting it here in case anyone else runs into the same problem.

Comments [0] | | # 
# Friday, November 11, 2005
Friday, November 11, 2005 11:58:32 AM (GMT Standard Time, UTC+00:00) ( Asp.Net )

i'm just posting this here for reference.  it is a datagrid that supports standard paging and sorting, and displays the current set of record indices e.g. "1-15 of 1000 records"

private void bindGrid()
{
    DataSet ds = new DB.Audits().SelectAllPendingAudits();

    // the sorting is always retrieved from viewstate, if it exists or not.
    string sort = String.Concat(ViewState["Sort"], "");
    if(sort != "")
        this.lblSort.Text = "Sorted by " + sort + " in ascending order";

    int numRows = ds.Tables[0].Rows.Count;
    if(numRows > 0)
    {
        int start = this.DataGrid1.CurrentPageIndex * this.DataGrid1.PageSize + 1;
        int end = Math.Min(numRows, start + this.DataGrid1.PageSize - 1);
        this.lblHeading.Text = String.Format("Displaying {0}-{1} of {2} records", start, end, numRows);
        this.DataGrid1.AllowPaging = (numRows > this.DataGrid1.PageSize);    // don't show pager unless relevant
        this.DataGrid1.Visible = true;
        DataView dv = new DataView(ds.Tables[0]);
        dv.Sort = sort;
        this.DataGrid1.DataSource = dv;
        this.DataGrid1.DataBind();
    }
    else
    {
        this.lblHeading.Text = "No records";            
        this.DataGrid1.Visible = false;
    }
}

private void DataGrid1_ItemCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
    if(e.CommandName == "Sort")
    {
        ViewState["Sort"] = e.CommandArgument.ToString();    // is picked up in bindGrid() function
        this.DataGrid1.CurrentPageIndex = 0;
        this.bindGrid();                
    }            
}

private void DataGrid1_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e)
{
    this.DataGrid1.CurrentPageIndex = e.NewPageIndex;
    bindGrid();
}
Comments [0] | | # 
# Thursday, November 03, 2005
Thursday, November 03, 2005 6:52:23 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )

i looked on the newsgroups to see if anyone had posted anything about this, and i found a few dead-end posts which seemed to conclude that it couldn't be done. 
i used a very simple approach that works well, and am posting it here for anyone looking to see how to do it.   the requirements are to present a radio-button-list with images instead of just text.

string imageBankFolder = "/ImageBankFolder/Thumbnails/";
DataSet ds = new DB.ImageBank().Select(); // get your dataset from wherever
foreach(DataRow dr in ds.Tables[0].Rows)
   this.RadioButtonList1.Items.Add(new ListItem(String.Format("<img src='{0}'>", imageBankFolder + dr["ImageFile"].ToString()), dr["ImageID"].ToString()));

this displays the images only.  note: firefox works fine with this, you can click on the image to select it, but IE6 requires you to actually click on the round radio button icon.  to work around this, i included some text above the image, which sits beside the button, and it is more intuitive for the user to click the text or the radio icon then.  to include some text above the image, try the following:

this.RadioButtonList1.Items.Add(new ListItem(String.Format("{1}<BR><img src='{0}'>", imageBankFolder + dr["ImageFile"].ToString(), dr["Text"].ToString()), dr["ImageID"].ToString()));

hope this helps someone out there.

Comments [12] | | # 
# Saturday, September 10, 2005
Saturday, September 10, 2005 2:38:39 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )

I was doing an upgrade on a web site recently, and all the pages were .html pages.  I wanted to add some .Net functionality, but didn't want to change all the urls, for bookmarks, search engines etc.  As well as scaring off the client with the strange ".aspx" file extensions.  yes- many irish companies are still technophobic. 

Add an IIS mapping for .html

i remember how to change mappings for a file extension in IIS (web site properties > home directory > configuration), so i did this for .html pages by adding a mapping for .html to aspnet_isapi.dll (copy the full path from the mapping for .aspx). 

Add a HttpHandler to the application web.config file

when i did the above, my .net code was ignored and rendered as plain text.  i found out this was because the web application (at the .net level) wasn't configured to handle .html files as .aspx files. this is what i added to my web.config to get it working:

<configuration> <system.web> <httpHandlers> <add verb="*" path="*.html" type="System.Web.UI.PageHandlerFactory" /> </httpHandlers> </system.web> </configuration>

now the whole application works with full .net functionality, overcoming all those migration problems usually associated with .net upgrades.

Comments [0] | | # 
# Friday, September 09, 2005
Friday, September 09, 2005 3:41:29 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )

Some users of a web application i wrote insist on clicking buttons more than once, probably out of impatience. this often causes duplicate key exceptions with the database, because the first time they clicked the button the record was created, and the second time they clicked it, an exception is thrown, so they get the error screen and don't know what they did wrong. 

i wanted to write a button control that would disable itself automatically and re-enable itself once it was finished.  i couldn't find any good samples out there.  javascript is obviously the answer, and the solution i came up with is quite simple.  here's the code: currently only works with .Net 1.1:

	/// <summary>
/// A button control that disables itself when clicked, and changes the text to "Please wait..."
/// This is to prevent duplicate clicks by impatient or novice users.
/// It requires the button to be placed in a server form.
/// </summary>
[DefaultProperty("Text"), ToolboxData("<{0}:SmartButton runat=server></{0}:SmartButton>")]
public class SmartButton : Button
{

/// <summary>
/// Add an 'onClick' attribute to disable the button when it is clicked, and submit the form,
/// invoking the postback.
///
/// The onClick code handles the case where __EVENTTARGET is registered on the page, in which case
/// this variable is set to the button ID, and the form is submitted.
/// The other case is where __EVENTTARGET does not exist on the page, i found this sometimes
/// occurred on pages with only one button. In this case, the form is simply submitted, and the
/// button_click event will be raised by virtue of the default submit button in the form.
/// </summary>
protected override void Render(HtmlTextWriter output)
{
string onClick = "if(this.form != null && this.form.__EVENTTARGET != null){ this.form.__EVENTTARGET.value='" + this.UniqueID + "'; this.disabled = true; this.value = 'Please wait...'; this.form.submit(); } else this.form.submit(); ";
if(this.Attributes["onclick"] != null) // prepend the existing onClick attributes
onClick = this.Attributes["onclick"].ToString() + onClick;
this.Attributes.Add("onclick", onClick);
base.Render(output);
}

protected override void OnClick(EventArgs e)
{
// do the OnClick code first
base.OnClick (e);

// then reset the enabled + text values to their original state
int insertAt = Math.Max(this.Page.Controls.Count-1, 0); // never insert at -1 if there are no controls on the page
this.Page.Controls.AddAt(insertAt, new LiteralControl(String.Format(@"
<script>
if(document.getElementById('{0}') != null)
{{
document.getElementById('{0}').disabled = false;
document.getElementById('{0}').value = '{1}';
}}
</script>
", this.UniqueID, this.Text)));
}
}
Comments [2] | | # 
# Monday, August 01, 2005
Monday, August 01, 2005 2:59:54 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
Scrollable DIV css code, useful for big checkboxlists etc
Comments [1] | | # 
# Thursday, June 30, 2005
Thursday, June 30, 2005 3:50:42 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
Crystal Reports for .Net, locked file when exporting to PDF with a crHtmlText field
Comments [0] | | # 
# Tuesday, May 24, 2005
Tuesday, May 24, 2005 11:45:00 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
A few useful functions for importing and exporting data in asp.net. Excel, PDF, Datasets, DataGrids, etc.
Comments [7] | | # 
# Wednesday, May 11, 2005
Wednesday, May 11, 2005 1:33:48 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
Fix: Forms authentication redirects to a bogus default.aspx page, with RedirectFromLoginPage()
Comments [0] | | # 
# Wednesday, March 23, 2005
Wednesday, March 23, 2005 4:47:02 PM (GMT Standard Time, UTC+00:00) ( Asp.Net | Database )
Howto: Export a dataset to Excel (c# / asp.net)
Comments [67] | | # 
# Wednesday, March 09, 2005
Wednesday, March 09, 2005 4:03:21 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
FIX: Crystal Reports error "Logon Failed" happens when viewer control is not in form server tag.
Comments [2] | | # 
# Friday, February 18, 2005
Friday, February 18, 2005 6:07:09 PM (GMT Standard Time, UTC+00:00) ( Asp.Net | Windows Server 2003 )
FIX: ISAPI Filter won't load on production server IIS
Comments [0] | | # 
# Thursday, February 03, 2005
Thursday, February 03, 2005 12:14:02 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
An ISAPI Filter to have nice URLs for your Asp.Net site
Comments [2] | | # 
# Friday, December 17, 2004
Friday, December 17, 2004 12:46:46 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
[FIX] aspx validation not working + buttons not clicking
Comments [1] | | # 
# Wednesday, December 15, 2004
Wednesday, December 15, 2004 10:58:09 AM (GMT Standard Time, UTC+00:00) ( Asp.Net )
Can't debug web projects with VS 2003 after installing VS 2005
Comments [1] | | # 
# Sunday, December 05, 2004
Sunday, December 05, 2004 3:15:28 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
An intelligent 404 page, suggest pages with a similar name to the requested page
Comments [0] | | # 
# Thursday, November 18, 2004
Thursday, November 18, 2004 12:45:53 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
System.Net.WebException: The request failed with HTTP status 401: Unauthorized.
Comments [5] | | # 
# Monday, November 08, 2004
Monday, November 08, 2004 6:33:01 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
Asp.Net DataGrid PageIndexChanged not working
Comments [0] | | # 
# Wednesday, November 03, 2004
Wednesday, November 03, 2004 5:43:16 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
using the same WSE2 web service with 2 different policies..
Comments [0] | | # 
# Monday, July 26, 2004
Monday, July 26, 2004 11:05:05 PM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
A great ASP.NET treeview control that is compatible with other browsers
Comments [1] | | # 
# Thursday, June 24, 2004
Thursday, June 24, 2004 9:49:24 AM (GMT Daylight Time, UTC+01:00) ( Asp.Net )
Http Status 400 trying to send large DIME attachments
Comments [8] | | # 
# Tuesday, March 16, 2004
Tuesday, March 16, 2004 11:06:12 PM (GMT Standard Time, UTC+00:00) ( Asp.Net )
Problem setting text for a password asp.net textbox
Comments [0] | | #