« April 2004 | Main | June 2004 »

May 28, 2004

The Right Way to Rotate JPEGs

The combination of the FromFile, RotateFlip and Save methods from System.Drawing.Image is an obvious approach when looking for functions to rotate JPEGs by 90 degrees, but there are reasons for not doing it this way:

      public static void SimpleRotate90(string inFile, string outFile, bool clockwise) {

            using (Image image = Image.FromFile(inFile)) {

                  image.RotateFlip(clockwise ? RotateFlipType.Rotate90FlipNone : RotateFlipType.Rotate270FlipNone);

                  image.Save(outFile);

            }

      }

Why isn’t this a good solution? Here’s why:

1.      The saved image is typically much larger than the original.

2.      While the image displays fine in IE and some other tools, Photoshop 8 complains about an “invalid jpeg marker” in the file and won’t open it.

3.      It’s slower than it needs to be.

 

One solution is to let the Save method do more of the work by passing it an Encoder parameter that tells it to transform the image as it is saved.

The code looks like this:

      using System.Drawing;

      using System.Drawing.Imaging;

 

      ...

 

      public static ImageCodecInfo GetImageCodecInfo(ImageFormat format) {

            foreach (ImageCodecInfo info in ImageCodecInfo.GetImageEncoders())

                  if (info.FormatID == format.Guid) return info;

            return null;

      }

 

 

      public static void Rotate90(string inFile, string outFile, bool clockwise) {

            using (Image image = Image.FromFile(inFile)) {

                  EncoderParameters eps = new EncoderParameters(1);

                  EncoderValue v = clockwise ? EncoderValue.TransformRotate90 : EncoderValue.TransformRotate270;

                  eps.Param[0] = new EncoderParameter(Encoder.Transformation, (long) v);

                  image.Save(outFile, GetImageCodecInfo(ImageFormat.Jpeg), eps);

            }

      }

 

Comments about this implementation:

1.      The saved image is about the same size as the original.

2.      Photoshop 8 seems happy.

3.      The using statement guarantees that image.Displose() gets called which guarantees the inFile gets released.

4.      The helper function GetImageCodecInfo uses the ImageFormat class instead of relying on mime type strings.

 

 

 

May 25, 2004

Quicken 2004 has bugs and Intuit's support is painful

Being a long time quicken user there are some operations I’ve come to dread. Switching from manual account reconciliation to automated reconciliation is one of them.

Sure enough, after downloading a few months of transactions and attempting to resolve the duplicate transactions I was left in a situation where either the reconciliation balance could be made correct or the register balance could be made correct, but not both at the same time. Reconciliation seemed to have the better of it, since getting the register to balance required either modifying the accounts opening balance or adding an adjusting transaction.

The thing that seems to screw it up is deleting reconciled transactions. Why this is a hard thing for the poor program I have no idea.

What really compounds the injury however is the response of Intuit’s customer support. Perhaps it’s the only way they’ve found to insulate themselves from the hordes of people calling with problems, but the “pay first ask later”, “it’s not a bug it’s a feature and we charge for help with features” approach is extremely frustrating when facing the simple fact that the program either can’t add or that it’s keeping numbers to itself that it isn’t showing you on the screen. I don’t care how you slice it; it’s a bug, and a rather inexcusable one in a piece of accounting software.

Fortunately during a previous interaction with customer support I acquired a bit of Quicken magic: Holding the control and shift keys down while selecting the FileàValidate operation causes “Super Validation” to run.

Sure enough, the “super validation” log reported “removing 9 records not in register”. You might ask: “If they weren’t in the register, where were they removed from?” But I was much too happy restoring my opening balances to the original values and getting on to more interesting things in life than double checking Intuit’s accounting software.

 

 

Automating Visual Studio builds

Found the NAnt style task and the VSConvert.xsl XSLT for generating NAnt build files from Visual Studio project files.

It failed to properly convert non-local references. There is a <lib> element for the <csc> task that could be used or you can use fully qualified path names.

I modified the directory names used to avoid conflict with those used by the IDE. i.e. nantbin instead of bin.

A brief attempt at getting the VSNAntAddin working failed using both the IDE build and NAnt build procedures. Didn’t pursue it at this time.

 

May 23, 2004

Details on behavior of forms authentication RequireSSL attribute

Setting RequireSSL to true causes authentication to require that all requests uses https; but it does NOT automatically switch to https when redirecting to the loginUrl page. If you don’t start out using https to access protected pages you are still transferred to the http flavor of your login page but login will not appear to work. What appears to be happening is this: authentication succeeds but does not end up adding a valid authentication cookie to the session (because https isn’t being used), you are redirected to the original protected content page, because there’s no cookie you are re-redirected back to the login page.

Therefore the practical consequence of RequireSSL is not to require SSL for login but rather to require it for all access to protected pages.

It would be nice if you could set forms authentication to force SSL for login and then have the redirectUrl return to the previous access mode (https or http).

See this post for more information.

 

May 21, 2004

NT Security Classes for .NET

May 20, 2004

John Robbins BugSlayer Tips

Tough morning for competitive ideas… OurPictures and BlogJet

Walt Mossberg reviewed OurPictures which is close to one idea I’m developing.

A fellow blogger pointed me at BlogJet, which reminded me to get cranking on TonesNotes.

 

May 19, 2004

ASP.NET Forms Authentication with SSL/https

So you read the available documentation an put together the basic Forms authentication framework for an ASP.NET application (for which there’s a good article by Abel Banda on O’Reilly) and you say to yourself, boy it really sucks that these passwords are being sent over the internet unencrypted; which leads to the next logical step of using SSL for the login.aspx page.

The steps to follow are these:

1.      Add a server certificate to the web site root folder (Internet Information ServicesàDefault Web SiteàPropertiesàDirectory SecurityàServer Certificate…)

2.      Create folder in your web app for which you will require SSL access. Call it “ssl”.

3.      Move your login.aspx file to ssl/login.aspx.

4.      Make sure the NTFS security settings on the web application folder includes the anonymous IIS user account (IUSR_machine typically).

5.      Disable Integrated Windows authentication and make sure Anonymous access is enabled for the web app (Internet Information Servicesàweb app folderàPropertiesàDirectory SecurityàAnonymous access and authentication controlàEdit…)

6.      Require SSL for your ssl folder (Internet Information Servicesàssl folderàPropertiesàDirectory SecurityàServer Certificate…àcheck the “Require Secure Channel” box.)

7.      Modify the loginURL attribute of the Forms element in your web.config file to reference the new login.aspx location with an absolute url beginning with “https://”.

8.      Test your application.

At this point if you’re like me, a few things will begin to annoy you. First you’ll be reminded that you can’t debug under Visual Studio 2003 without Integrated Windows authentication. That’s okay. Leave it off for now. Just open a browser and manually try to hit your test page.

Again, if you’re like me, you should be getting a 401.2 error attempting to get the login.aspx page.

You might even try directly accessing the login page from the browser with https – no good. And you might try changing the loginUrl back to a relative url (“ssl/login.aspx”) and requesting your test page with https. Wow! That works!?! Hmm… You might also test what happens if you change the “deny users=”?”” to “deny users=”bob””. Again it works accessing each page with https, but you’ve given up forcing the redirect to the login page. If you actually tried any of these things, put things back the way they were and move on to the solution.

Add this in front of your existing <system.web> element in the web.config file:

<location path="ssl/login.aspx">

     <system.web>

         <authorization>

             <allow users="?" />

         </authorization>

     </system.web>

 </location>

 

This block tells the authorization logic to explicitly allow the anonymous user to access the ssl/login.aspx page and overrides the blanket deny element that comes later. There seems to be some special case code that handles relative login URLs but fails on absolute login URLs.

Change your loginUrl attribute back to an absolute “https://”  url and the authentication should work.

One annoyance remains: The ReturnUrl passed to the login page doesn’t include the request scheme (the “http://” part) so you end up stuck in SSL after you login.

So I tried taking charge of the redirect URL with the following code. Thanks to Scott Hanselman for the xml timeout hack, and yes, it needs better packaging for production:

System.Xml.XmlDocument x =new System.Xml.XmlDocument();

x.Load(Request.PhysicalApplicationPath + "web.config");

System.Xml.XmlNode node = x.SelectSingleNode("/configuration/system.web/authentication/forms");

int timeout = int.Parse(node.Attributes["timeout"].Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);

 

string userData = "Place application specific data for this user here.";

 

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(

      1,

      UserName.Value,

      System.DateTime.Now,

      System.DateTime.Now.AddMinutes(timeout), // Should be timout from web.config

      PersistCookie.Checked,

      userData,

      FormsAuthentication.FormsCookiePath);

 

string encTicket = FormsAuthentication.Encrypt(ticket);

 

Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

 

string url = FormsAuthentication.GetRedirectUrl(UserName.Value, PersistCookie.Checked);

 

Uri uri = Request.Url;

 

url = uri.Scheme.Replace("https", "http") + "://" + uri.Host + url;

 

Response.Redirect(url, true);

 

That does the trick but generates a very annoying Security Alert dialog (“You are about to be redirected to a connection that is not secure.”).

To avoid this warning which is intended to remind naïve users that they are being taken away from the protection of https by the never-to-be-trusted server you must arrange for the client to request the shift back to non-SSL traffic.

Instead of the “Response.Redirect(url, true)” at the end of the previous block of code, replace it with:

(FindControl("RedirectUrl") as System.Web.UI.HtmlControls.HtmlInputHidden).Value = url;

 

And merge the following bit of code into the login.aspx file:

            <script language="javascript">

            <!--

            function DoOnLoad() {

                  if (Form1.RedirectUrl.value)

                        window.navigate(Form1.RedirectUrl.value);

            }

            -->

            </script>

      </HEAD>

      <body onload="DoOnLoad()">

            <form id="Form1" method="post" runat="server">

                  <input type=hidden id="RedirectUrl" name="RedirectUrl" runat="server" />

 

That should do it. Nothing could be simpler…. J

The following notes are from Jason Loader,

2005-04-08

Hi Tone,

You know, you're the only person whose posted a solution to this problem I could find. Anyway, I was getting a windows login prompt/401.2 error (I was using Anonymous/Integrated Authentication on IIS), which was odd, as my application uses role-based security, but I'd already given anonymous/all users access to the login page via a location element. I tried what you suggested, but it still didn't work (it did get rid of the windows login prompt though), I couldn't find an explicit solution to the problem, but it would appear that the answer is actually in the Duwamish 7.0 sample.

You need to give the login page subdirectory it's own web config file that gives access to it for all users (that's all that needs to be in there) if you want to hard code the absolute url in the forms loginurl e.g.

In the application web.config file, you'd put -

<forms loginUrl="https://[your server]/[your application directory]/Secure/login.aspx" name="AuthCookie" timeout="30" slidingExpiration="true" path="/" protection="All">

</forms>

In the Secure subdirectory web.config file, you'd put -

<system.web>

  <authorization>

    <allow users="*" />

  </authorization>

</system.web>

then remove the location section for the login page from the main app config file (the bit below) -

<location path="Secure/Logon.aspx">

  <system.web>

    <authorization>

      <allow users="?"/>

    </authorization>

  </system.web>

</location>

From what I can tell, it's probably something to do with ASP.NET not picking up the authentication from the config file for the sub directories correctly when using absolute urls, as it works when you use relative urls and SSL for the whole site.

However, you still had to change the config file if you moved the directories, changed the server name etc. With a bit of playing about, I found that you can force Forms Authentication to use SSL without hard-coding HTTPS in the loginURL by using the following code in globals.aspx -

Sub Application_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.EndRequest

Dim responseURI As System.Uri

Dim responseURIBuilder As System.UriBuilder

Dim pageName As String

' check to see if we're being redirected to the login page

If Not Response.RedirectLocation Is Nothing AndAlso Response.RedirectLocation.Length > 0 Then

' load the redirect into the uri

responseURI = New System.Uri(Request.Url,

Response.RedirectLocation)

pageName = GetPageFromURL(responseURI.AbsolutePath).ToLower()

If pageName = "login.aspx" AndAlso responseURI.Scheme <> "https"

Then

' force it to be HTTPS

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "https"

' remove the port number from the url and write it back

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

ElseIf pageName <> "login.aspx" AndAlso responseURI.Scheme = "https" Then

' force it to be HTTP

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "http"

' remove the port number from the url and write it back

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

End If

End If

End Sub

GetPageFromURL is a function which gets the page from a URL string, there's plenty of examples out there if you need to do the same.

What Duwamish does is automatically prefix absolute urls in the aspx files so you can turn SSL on and off through web.config, but I prefer to do that in the vb code so I can use relative links. One thing I haven't done is check that the login page is always being requested over secure sockets and if not force it to be, but you can get round that through IIS configuration (just in case you don't configure IIS and someone tries to access the login page directly). The next step is to make this configurable, like Duwamish is, and possibly picking up the HTTP/HTTPS ports if you've changed them (I've only tried it on default ports with IE).

Cheers,

Jason Loader

Software Developer, GVAS

Wealth Management Software PLC

E-mail: jason.loader@wms-plc.com

 

2005-04-08

one thing to note is that the duwamish sample doesn't actually work by using an absolute url in the loginURL, it uses a relative one and then absolute urls for it's links on the pages and redirects in the code. I am suprised about the Forms Authentication not behaving correctly though, as I thought they would just do what I'm doing and simply re-write the response.redirectlocation (although that would probably cause problems as it redirects using relative paths, which may not work if you'd redirected to a different site. maybe that's something to play about with later). i also found that if you're re-writing the url, you don't even need a config file in the subdirectory.

when i get round to rolling this into our application, if there's any changes or further issues i'll give you a shout.

Cheers

Jason

 

2005-04-11

there's a bit of a glitch in the solution i sent you. if you try to open the project in vs.net you sometimes get an error to do with redirect from a non-secure to a secure connection, which then stops the project from opening. the reason is that when vs.net opens the project, it tries to get a file called get_aspx_ver.aspx, which it looks like it dynamically creates to check if ASP.NET is running on the server. The reason it fails is due to forms authentication and the code in globals.asax. As we haven't been authenticated yet, we're redirected to the login page, but our code forces it to be via HTTPS, which VS.NET can't handle. There are several solutions to this -

1. Remove the forms authentication from web.config before opening the project and then put it back in.

2. Delete the contents of the projects bin directory before opening.

3. Put the following in web.config to allow access to the temporary file -

<location path="get_aspx_ver.aspx">

<system.web>

<authorization>

<allow users="?"/>

</authorization>

</system.web>

</location>

4. Add a condition into our code to always use HTTP for the above page.

Sub Application_EndRequest(ByVal sender As Object, ByVal e As

System.EventArgs) Handles MyBase.EndRequest

' check to see if we're being redirected to the login page

Dim responseURI As System.Uri

Dim responseURIBuilder As System.UriBuilder

Dim pageName As String

If Not Response.RedirectLocation Is Nothing AndAlso Response.RedirectLocation.Length > 0 Then

' load the redirect into the uri

responseURI = New System.Uri(Request.Url,

Response.RedirectLocation)

pageName =

RetrieveRequestPage(responseURI.AbsolutePath).ToLower()

If RetrieveRequestPage(Request.Url.AbsolutePath).ToLower() <> "get_aspx_ver.aspx" _

AndAlso pageName = "login.aspx" _

AndAlso responseURI.Scheme <> "https" Then

' force it to be HTTPS

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "https"

' remove the port number from the url

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

ElseIf pageName <> "login.aspx" AndAlso responseURI.Scheme = "https" Then

' force it to be HTTP

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "http"

' remove the port number from the url

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

End If

End If

End Sub

I'd go with 4, but you'll have to do one of the others first to be able to open the project to make the change (and you may need to restart the web service too)...

Cheers

Jason

Troubleshooting ASP.NET authentication, authorization, and security issues

May 17, 2004

ViewState, LoadViewState, and dynamically added UserControls

Dynamically adding UserControls (ascx files and classes derived from System.Web.UI.UserControl) gets tricky when you start needing the controls re-instantiated at postback time.

For this discussion, I start with a conventional Web Form page (aspx file and class derived from System.Web.UI.Page) where the Page_Load method contains logic to create and add a variety of System.Web.UI.Control derived instances to the page’s Controls collection. The Page_Load implementation should only do the construction if IsPostPack is false because the context that determines how many and/or which controls to create should come from the ViewState for a post back.

Here are a few things to keep in mind:

1.      LoadViewState gets called before Page_Load and only on post back (i.e. when the request contains a hidden __VIEWSTATE form field.)

2.      If the correct structure of controls exists at the end of the Page derived class’ LoadViewState method then their state will automatically be restored.

3.      The content of the savedState argument to LoadViewState is just the state for the current control. It does not include the state for child controls.

4.      The Page class’ LoadViewState implementation restores any ViewState[string] assignments that were made.

 

A simple way to have the state of dynamically created controls restored on post back is to do the following:

1.      At the end of the Page_Load implementation, store just enough information in ViewState to recreate the same structure on post back. E.g. ViewState[“DynamicControlCount”] = 6;

2.      Override the LoadViewState method in the derived Page class.

a.       First call base.LoadViewState(savedState). This will restore the page’s ViewState. E.g. the value of ViewState[“DynamicControlCount”].

b.      Use ViewState[“DynamicControlCount”] to recreate the prior control structure.

 

That’s it! After calling the derived page class’ LoadViewState method, the framework will call the LoadViewState methods on each child control in a fixed traversal order.

 

XmlSerializer ctor failure in ASP.NET context

Using the XmlSerializer class to deserialize an object from its XML representation from an assembly invoked in an ASP.NET context on Windows Server 2003 fails with a file not found error.

The problem is that the “INTERNET SERVICE” account doesn’t have write access to the c:\winnt\Temp folder.

This is a recognized problem.

 

May 13, 2004

Creating Windows Services

Summary

Here’s the summary of what to remember when creating a new Windows Service. For more information on the process of discovering these tips read the details below.

1.      By default, a Windows Service project is missing an installer. From a service design view, right click select “Add Installer”.

2.      By default, the ServiceProcessInstaller component is configured to use a “User” account, change it to either “LocalService” or “LocalSystem” depending on security concerns.

3.      By default, a setup project does not invoke the custom installers contained within assemblies added to it. From the setup project’s Custom Actions Editor, right click select “Add Custom Action…” and select the primary output from the service application.

4.      When having difficulties, use the installutil tool to obtain detailed installation progress logging output.

Details

I started this note when what should have been a simple process, creating the shell for a new Windows Service project, began taking much too long.

Conceptually the goal isn’t complicated:

1.      Create an EXE containing the service implementation following the Visual Studio 2003 Windows Service project template.

2.      Create an installer for the service.

3.      Install it.

4.      Verify that it appears in the list of installed services.

The first time around I made changes to the default names of the various components as appropriate for a real project. When this didn’t work I tried again and left every name at its default value. I created a new empty solution, added a Windows Service project, opened the design view on Service1.cs, right clicked and selected “Add Installer”, added a Setup project to the solution consuming the primary output of the WindowsService1 project.

This results in the following names being assigned:

1.      WindowsService1: The name of the service project.

2.      WindowsService1.exe: The executable containing the service implementation.

3.      WindowsService1: The service project namespace.

4.      Service1: The service class.

5.      Service1: The “ServiceName” assigned in InitializeComponent to instances of class Service1.

6.      ProjectInstaller: The name of the class created by the “Add Installer” command.

7.      Service1: The “ServiceName” assigned to the serviceInstaller1 installer within ProjectInstaller.

It all builds smoothly but when I “Install” the Setup1 project nothing appears in the installed service list (Administrative ToolsàServices).

Next I tried the “installutil” tool from the service project’s bin\Debug folder. The first time I executed it with the command line “installutil *.exe” because I’m lazy. This results in the cryptic error message “Exception occurred while initializing the installation: System.ArgumentException: Invalid directory on URL..”

A much better error message might have been: “Installation file not found: *.exe”. It seems that installutil doesn’t expand wildcards.

Trying again with an explicit filename, “installutil WindowsService1.exe”, worked much better. It puts up an account credentials dialog because the default Account property of the serviceProcessInstaller1 component has a value of “User”. When I entered my user name without a preceding domain name the installation failed, but at least it failed with a good error message. Trying again with a “domain\username” succeeded and now I have a “Service1” showing up in the service list (don’t forget to hit refresh).

Now to work backwards and figure out how to make the production service work:

1.      Ran “installutil /u WindowsService1.exe”.

2.      Changed the Account property of serviceProcessInstaller1 to “LocalService”.

3.      Ran “installutil WindowsService1.exe”

That got rid of the account credentials dialog.

1.      Ran “installutil /u WindowsService1.exe”

2.      Rebuilt the Setup1 project

3.      Verified Service1 was not installed

4.      Ran “Install” on the Setup1 project.

Silent failure.

Manually added “Primary output from WindowsService1 (Active)” to each Custom Actions category in the Setup1 project:

1.      Select the Setup1 project in the Solution Explorer.

2.      Click on the Custom Actions icon in the Solution Explorer toolbar.

3.      Right click on “Custom Actions” and select “Add Custom Action…”

4.      Expand the Application Folder (double click)

5.      Select “Primary output from WindowsService1 (Active)”

6.      OK.

7.      Rebuild and test.

That does it.

 

 

May 01, 2004

Transhumanism, It’s ok to see that good isn’t good enough for everyone.

Then again if we’re in an every accelerating whirlpool towards The Singularity within our lifetimes, perhaps its time to really start paying attention.

Transhumanism is the active pursuit of human improvement beyond what’s normal through technology.

 

So who would I know who was in Orkut?

www.orkut.com

 

Vernor Vinge’s Singularity Paper