Feb 102007
 

I frequently place an UpdatePanel around a paged GridView with inline editing.   During OnRowUpdating, the object is saved.   If the save was not successful, I like to show an alert box with the error message.  


I recently migrated from the Atlas May CTP to ASP.NET 2.0 AJAX.   The following code worked with the May CTP, but, from my research, has not worked since July CTP:



Page.ClientScript.RegisterStartupScript(typeof(string), “alertScript”, “alert(‘Error Message’);”, true);


However, our pals at Microsoft have given us something just as good.   I found it by mistake while trying to put in a hack involving checking the value of ScriptManager.GetCurrent(Page).IsInAsyncPostBack.  Here is the AJAX-friendly equivalent:



ScriptManager.RegisterStartupScript(this.updatePanel, typeof(string), “alertScript”, “alert(‘Error Message’);”, true);


Hope this helps someone.  There are many blog posts, with many more comments, with people compaining about this.

 Posted by at 1:21 pm
Feb 082007
 

I just migrated from the Atlas July CTP to ASP.NET 2.0 AJAX Extensions 1.0.  After following all of the instructions on the AJAX site, my web site still showed tons of errors.  I’m using a Web Site, not Web Application, with a ScriptManager in a WebForm that uses a MasterPage.   Every page shows errors on AJAX tags. 


Yes, I removed the Atlas DLL.  No, my web.config is not messed up.  Something else is wrong here.   I found a “fix”.   The sequence of steps seems a little odd to me, but I did’t care, it worked.  However, the problem resurfaced when I closed my MasterPage.


More searching led me to a great alternative to a true solution.   An ASP.NET Forums post had some back-and-forth on the topic.  Ultimately, the best solution at this point is to change the tagprefix to “ajax”.   This works great, and, in fact, I like it better because it separates the ajax controls from the standard asp controls.


<add tagPrefix=ajax namespace=System.Web.UI assembly=System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35/>




 Posted by at 2:34 am
Nov 072006
 

I frequently store documents in the database for my ASP.NET apps, eliminating web farm complications with shared drives, permissions, etc.  When uploading a file, my Document class reads the uploaded file, zips the file with SharpZipLib, and inserts/updates in the database.   When opening a file, I have always used an ASPX page that uses the Document class to unzip the file, and then changes the Content-Disposition and ContentType headers, and then does a BinaryWrite to the Response object to display the file.


I have been using Handlers a lot lately, and figured that it was time to make this process a little more elegant.  If you’ve never written a handler, it’s quite simple.  You need to make a web.config change, and add a new class that implementts IHttpHandler.  All of the work is done in ProcessRequest.  Using the default .ashx extension for the handler eliminates the need to make any changes in IIS.  I thought about changing the handler to accept all requests with known file extensions with the document ID as the filename, like 3383.pdf.  I just figured that using the default extnesion would be easier.   Laziness or efficiency, you decide.  Check out the code.


In <system.web> in web.config:



<httpHandlers>
   
<add verb=”*” path=”DocumentHandler.ashx” type=”TestingWebApp.DocumentHandler, TestingWebApp” />
</httpHandlers>


DocumentHandler.cs:



using System;
using System.Web;

namespace TestingWebApp
{
    public class DocumentHandler : IHttpHandler
    {
        private int DocumentId
        {
            get
            {
                if(System.Web.HttpContext.Current.Request.QueryString[“DocumentId”] != null && System.Web.HttpContext.Current.Request.QueryString[“DocumentId”].ToString().Length > 0)
                    return Convert.ToInt32(System.Web.HttpContext.Current.Request.QueryString[“DocumentId”]);
                else
                    throw new ApplicationException(“Document Handler requires a DocumentId”);
            }
        }

        #region IHttpHandler Members

        public void ProcessRequest(System.Web.HttpContext context)
        {
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.BufferOutput = false;
            
            Document document = Document.GetDocumentByDocumentId(this.DocumentId);

            byte[] buffer = document.UnzippedBinary;
            context.Response.ContentType = document.ContentType;
            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
        }

        public bool IsReusable
        {
            get { return true; }
        }

        #endregion
    }
}

 Posted by at 4:31 am
Nov 062006
 

The starter kit already allowed individual file upload, and batch upload from a directory which requires files to be moved to Upload directory by FTP.  The starter kit also stores the images in the database. While I prefer this method for most files, I don’t prefer it for images. I changed a few methods to store the images in an images folder, and modified the image serving handler accordingly.  The album page load time is a fraction of what it was with images in the database.


I also created an XP Publishing Wizard that allows any user with credentials to create/choose an album, and upload images from Windows XP. The beauty of the XPPW is that it can resize the images before uploading. That way all of us with 10 megapixel cameras no longer have to spend any extra time resizing to prevent reaching a web host storage quota.


A few articles helped me figure this stuff out:



Creating the wizard was easy enough.  You first need to create a registry entry in the following format:


Windows Registry Editor Version 5.00

[HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExplorerPublishingWizardPublishingWizardProvidersYour Photo Gallery]
“displayname”=”Your Photo Gallery”
“description”=”Online Photo Albums”
“href”=”http://www.yoursite.net/XPPublish.aspx”
“icon”=”http://www.yoursite.net/favicon.ico”


Next is a single aspx page that accepts the files.  The page handles user login, album creation/selection, and accepts multiple files in a single form post.  All the hard work is done by some javascript methods that handle the XML sent from Windows XP.  The javascript looks like this:


        <script language=’javascript’>
            function startUpload()
            {
                var xml = window.external.Property(“TransferManifest”);
                var files = xml.selectNodes(“transfermanifest/filelist/file”);
                var albumId = document.getElementById(“Album”).value;

                for (i = 0; i < files.length; i++)
                {
                    var postTag = xml.createNode(1, “post”, “”);
                    postTag.setAttribute(“href”, “http://yoursite.net/XPPublish.aspx“);

                    postTag.setAttribute(“name”, “userpicture”);

                    var dataTag = xml.createNode(1, “formdata”, “”);
                    dataTag.setAttribute(“name”, “MAX_FILE_SIZE”);
                    dataTag.text = “10000000”;                    
                    postTag.appendChild(dataTag);
                    
                    var dataTag1 = xml.createNode(1, “formdata”, “”);
                    dataTag1.setAttribute(“name”, “btnUpload”);
                    dataTag1.text = “Save”;
                    postTag.appendChild(dataTag1);
                    
                    var dataTag2 = xml.createNode(1, “formdata”, “”);
                    dataTag2.setAttribute(“name”, “hidAlbumId”);
                    dataTag2.text = albumId;
                    postTag.appendChild(dataTag2);

                    files.item(i).appendChild(postTag);
                }
                
                var uploadTag = xml.createNode(1, “uploadinfo”, “”);
                uploadTag.setAttribute(“friendlyname”, “Family Photo Gallery”);
                var htmluiTag = xml.createNode(1, “htmlui”, “”);
                htmluiTag.text = “http://yoursite.net/Personal/Albums/Photos.aspx?AlbumID=” + albumId;
                uploadTag.appendChild(htmluiTag);

                xml.documentElement.appendChild(uploadTag);

                window.external.Property(“TransferManifest”) = xml;
                window.external.SetWizardButtons(true,true,true);
                document.getElementById(“divContent”).innerHtml = xml;
                window.external.FinalNext();
            }

            function OnBack()
            {
                window.external.FinalBack();
                window.external.SetWizardButtons(false,true,false);
            }

            function OnNext()
            {
                if (document.getElementById(“divLogin”))
                {
                    document.getElementById(“LoginArea_Login1_LoginButton”).click();
                }
                else
                {
                    startUpload();
                }
            }

            function OnCancel()
            {
            }

            function window.onload()
            {
                window.external.SetHeaderText(‘Photo Gallery’,’Your Photos’);
                window.external.SetWizardButtons(true,true,false);
            }
        </script>


In case you haven’t seen the XP Publishing Wizard in action, check out these screenshots:



 



 



 



 



 



 



 



 



 

 Posted by at 2:35 am
Jul 242005
 

Dmitri Khanine and Phil Carrillo author this fine article on Javascript RPC. Finally, an article that mentions separation of business and presentation logic, and implementing MVC in ASP.NET. We need more patterns and practices discussions, and a lot less “look what I can do” articles.


MSDN Article: Life without Refresh

 Posted by at 2:01 am
Jun 052005
 

Devloping an application using the System.Web.Mail.SmtpMail class to send email should not be as difficult as it always is. http://www.systemwebmail.com has a detailed and helpful collection of possible fixes for the dreaded “Could not access ‘CDO.Message’ Object” Exception. The one I most recently experienced was not listed. We thought it could be a relaying perrmission or something related to setting the SmtpServer name. It turned out to be a Windows 2003-specific problem related to Fixed Identity Impersonation for high security (isolated) applications using a low-privilege user account specific to the application.

The exception you see, almost always the useless excpetion, “Could not access ‘CDO.Message’ Object”, does not help you to troubleshoot. As you can see in the code below, generated using Lutz Roeder’s .NET Reflector, called by SmtpMail.Send(), the real exception is never thrown. It will throw the “Could not access ‘CDO.Message’ Object” exception no matter what exception was caught.


internal object CallMethod(object obj, string methodName, object[] args)
{
   object obj1;
   try
   {
      obj1 = SmtpMail.LateBoundAccessHelper.CallMethod(
         this.LateBoundType, obj, methodName, args);
   }
   catch (Exception exception1)
   {
      throw new HttpException(HttpRuntime.FormatResourceString(
         “Could_not_access_object”, this._progId), exception1);
   }
   return obj1;
}


The best advice given by http://www.systemwebmail.com is to loop through all of the InnerExceptions thrown when the useless exception is thrown. This will allow you to see the true exception and determine how to fix the problem.


In Windows 2003, you can have each application run in its own application pool using an application-specific user account. Depending on the privileges and group memberships of this user account, it may or may not have the correct permissions to be able to send mail using the machine’s SMTP server. To send mail, the user account needs to have write permissions on the directory named C:inetpubmailrootPickup. With those permissions, System.Web.Mail (.NET wrapper around CDOSys.dll) will create a .eml file in that directory. The mail gets sent when the SMTP service, with SYSTEM privileges, sees this file and sends the email.


If granting the necessary permissions is not possible for your application’s user account, you can bypass System.Web.Mail’s default CDO send method, using the Pickup directory, altogether. You can modify the mail message’s configuration fields to set the values used by CDO, regardless of the values set by System.Web.Mail.


You can send the mail message by going through the smtp port instead of the pickup directory (default). Change the sendusing configuration field from 0 (default – pickup directory) to 1 (port).


mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/sendusing”, 1);


If you still cannot send email, try using other CDO configuration fields to bypass the System.Web.Mail settings. Here are some other configuration fields. I would try them in this order:


// smtpauthenticate values (2=NTLM, 1=Basic, 0=None)
mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/smtpauthenticate”, 1);
mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/smtpserverport”, 25);
mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/smtpserver”, mailServer);
mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/sendusername”, username);
mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/sendpassword”, pw);
mailMsg.Fields.Add(“http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout”, 10);




Of course, all of this is only possible if you are using .NET Framework 1.1. The Fields property of the MailMessage class was not exposed in version 1.0 of the framework.

 Posted by at 2:02 am
Oct 172004
 

DropDownLists within a Repeater appear to lose or forget which item is selected before the page loads. This is not the case. Dynamically created DropDownLists inside of a Repeater create a rare obstacle. The SelectedItem is not being lost or forgotten. It only appears that way because the OnDataBinding event for the DropDownList is being called twice. Only when the RepeaterItem containing the DropDownList calls its own DataBind method is the data actually bound to the DropDownList. Consider the following code:

// UsingDropDownLists.aspx
115 <asp:Repeater id=”rptOne” runat=”server” OnItemCreated=”rptOne_ItemCreated”/>
116  <ItemTemplate>
117    <asp:Literal id=”litQuestion” runat=”server”/>
118    <asp:DropDownList id=”ddlResponse” runat=”server”/>
119    <br/>
120  </ItemTemplate>
121 </asp:Repeater>

// UsingDropDownLists.aspx.cs
211 protected void rptOne_ItemCreated(object sender, RepeaterItemEventArgs e)
212 {
213   if(e.Item.DataItem != null)
214   {
215     MyClass myClass = sender as MyClass;
216     (e.Item.FindControl(“litQuestion”) as Literal).Text = myClass.Question;
217     DropDownList ddl = e.Item.FindControl(“ddlResponse”) as DropDownList;
218     ddl.DataSource = GetResponseList(myClass.AvailableResponses);
219     ddl.Items.FindByValue(myClass.Response).Selected = true;
220     ddl.DataBind();
221     ddl.SelectedIndex = e.Item.ItemIndex%(ddl.Items.Count-1); //Random
222   }
223 }

Here is the sequence of events:

  1. Page_Load
  2. Data Retrieval/Bind Data to Repeater
  3. Repeater’s ItemCreated event fires for each RepeaterItem
  4. Data Retrieval/Bind Data to DropDownList
  5. OnDataBinding is fired from the DropDownList’s DataBind() (line 220). However, if you watch in debug mode, you will see that the DropDownList has no ListItems yet.
  6. DropDownList’s SelectedIndex is chosen (line 221). Although, at this point, the DropDownList still has no ListItems. The SelectedIndex call falls on deaf ears.
  7. RepeaterItem’s DataBind method performs an actual DataBind on the DropDownList. There are no selected items at this point.

RepeaterItem inherits from Control. If you look at Control’s DataBind() method in Lutz Roeder’s .NET Reflector, you will see that it performs a DataBind on each of its child controls. 


public virtual void DataBind()
{
   this.OnDataBinding(EventArgs.Empty);
   if (this._controls != null)
   {
      string text1 = this._controls.SetCollectionReadOnly(“Parent_collections_readonly”);
      int num1 = this._controls.Count;
      for (int num2 = 0; num2 < num1; num2++)
      {
         this._controls[num2].DataBind();
      }

      this._controls.SetCollectionReadOnly(text1);
   }
}


From this, we can see three things.



  1. The only safe time to choose the SelectedItem is after the binding of all objects within the Repeater. A perfect place to do this is in the RepeaterItem’s PreRender or in the Repeater’s PreRender. You have access to the same bound object from RepeaterItem.DataItem, and can find the DropDownList and choose the SelectedIndex, SelectedValue, or ddl.Items.FindByValue(response).Selected = true;
  2. The DataBind() call (line 220) is completely unnecessary and leads the programmer to believe that it has actually bound the data. This is untrue. Again, in debug mode, you will see that by line 221, there are still zero ListItems in ddl.Items.
  3. The same obstacle will arise for all Controls derived from ListControl. This includes CheckBoxList, DropDownList, ListBox, and RadioButtonList. The same solution will apply to these controls within a Repeater.

 Posted by at 2:03 am