I recently added a maintenance form to our website which allows a user to add and delete entries to a list of banner ads stored in a simple XML file. Previously we've just been maintaining them manually by editing the XML directly and copying the associated banner images into a folder on the website. When you delete an ad, I decided to NOT delete the associated image since they may be reused (and I wanted to avoid forcing the user to upload the image again). However, we'd still like to periodically clean out this folder and "archive" the images so they're not cluttering up the selection screen.

I basically needed some code which would get me a list of files in the banner images folder which were not referenced in my banner XML file. It can be kind of clunky to iterate through XML but they've made it much easier with the introduction of LINQ. I fired up LINQPad (which, BTW, is an AWESOME free tool for testing out LINQ code) and tried out a few ideas. As a side note, it looks like Intellisense is now available if you purchase a copy of LINQPad.

http://www.linqpad.net/

I started with querying the filesystem to get a list of files:

  

DirectoryInfo info = new DirectoryInfo(@"X:\inetpub\wwwroot\images\banner");

FileInfo[] files = info.GetFiles();

 

files.Dump();

 

.Dump() is an extension method available in LINQPad which dumps out the results of the query (we haven't actually used LINQ yet to do anything).

Here's what it looks like:

FileInfoLinq

You might notice that Directory contains a DirectyInfo element. If you click on the down arrow it will expand out these values as well.

So I now had a list of files, I wanted to then get a list of images referenced in my banner file. Here's the format of the XML file:

<News>   
   <NewsItem>
       <Title>Supertooth3-banner.gif</Title>
       <Image>/images/banner/Supertooth3-banner.gif</Image>
       <Height>183</Height>
       <Link>/PortalView.aspx?navto=/Supertooth3-banner.gif</Link>
       <Date>July, 19th, 2003</Date>
       <Target></Target>
   </NewsItem>   

  

 

I haven't really been using Title for anything besides the name of the image file, so I was able to take a bit of a shortcut here and use it for my comparison. To pull out the list of images used in the XML, I wrote this query:

XDocument doc = System.Xml.Linq.XDocument.Load(@"X:\inetpub\wwwroot\adv.xml");

doc.Dump();

 

var news = from item in doc.Descendants("Title")

           orderby item.Value

           select item.Value;

news.Dump();

 

Which produces this:

XmlTitlesLinq

I noticed that the banner images folder contained a bunch of other files that I really didn't want to consider for filtering, so I needed to narrow my query to files that had specific extensions. C# doesn't really have a direct equivalent for INLIST, but we can do this a slightly different way for the same effect.

First I define an array of valid extensions, then (inside the where clause of the LINQ query) I check to see if this list of file types contains the filetype of the file I'm currently evaluating. It's a bit backwards, but it's simple and it works.

string[] fileTypes = { ".jpg", ".gif", ".png" };

var imgFiles = from file in files

               where (fileTypes.Contains(file.Extension))

               orderby file.Name

               select file.Name;

 

imgFiles.Dump();

 

Now I've got a list of files from my XML and a list of files from the banner images folder. I want to get a list of files from the banner images folder that aren't in the XML list. I do this via a final query:

var extra = from singleFile in imgFiles

            where !(news.Contains(singleFile))

            select singleFile;

extra.Dump();   

This returns my extra files. Now I can just use this list to move my images into an archive folder periodically.

If I put it all together, I end up with this:

 

XDocument doc = System.Xml.Linq.XDocument.Load(@"X:\inetpub\wwwroot\adv.xml");

DirectoryInfo info = new DirectoryInfo(@"X:\inetpub\wwwroot\images\banner");

FileInfo[] files = info.GetFiles();

doc.Dump();

files.Dump();

 

var news = from item in doc.Descendants("Title")

           orderby item.Value

           select item.Value;

news.Dump();

string[] fileTypes = { ".jpg", ".gif", ".png" };

var imgFiles = from file in files

               where (fileTypes.Contains(file.Extension))

               orderby file.Name

               select file.Name;

 

imgFiles.Dump();

 

var extra = from singleFile in imgFiles

            where !(news.Contains(singleFile))

            select singleFile;

extra.Dump();   

Links:

http://www.linqpad.net/


 
Categories: .NET | C# | LINQ

November 26, 2008
@ 08:50 PM

I thought this was an interesting question that has come up a couple of times in the last few days over on the Universal Thread. In VFP you can create a subclass of a form, customize it, then configure VFP to use this subclass as the "template" for any new forms you create (by going into Tools > Options > Forms and setting the base class).

The question was, how do I do this in .NET?

It's pretty easy - create your form and add any controls, methods, properties, etc. that you want. Then go to File > Export Template. An Export Template Wizard will open. Change the selection to an "Item template" and click Next.

Export Template Wizard

 

On the next screen put a checkmark next to the item(s) you want included in the template. If you have other dependencies (like external classes) you can also select them here. Then click Next again.

Export Template Wizard Select Item to Export

Now you can select any external references you want to include. For example, if you created a project which includes a set of base controls that you used on your form, you may want to select them here (so when you use this template these references will automatically get added to your project). Click Next.

Export Template Wizard Select Item References

Finally you can select an icon that is associate with this template, enter the template name, description, and it's output location. I'd suggest leaving the "Automatically import the template into Visual Studio" option checked. If you don't like the output location, unfortunately you can't change it here. You have to change it via Tools > Options > Projects and Solutions. Finally click Finish.

Export Template Wizard Select Template Options

At this point, when you do an "Add New Item" you should be able to scroll to the bottom of the templates and see "My Templates" - the new template you created should exist there.

Although I showed this for a WinForms application, this can actually be used with any class (and for ASP.NET applications). For example, I have a base business object class I use. Every time I create a new one I just select a template based on a subclass of it to create the new one.


 
Categories: .NET | Visual Studio

I received this error on a page which had previously been working. That morning I had updated our site, so I was expecting some issues, but this error doesn't really tell you much. We have a maintenance screen which displays a list of records in a grid. When you click on an edit link the grid converts into an "in place" editor (a UI metaphor I thought I'd like when I initially put the form together but which, as it turns out, I really hate).

A user had reported that when they clicked on a button which saves this record that it wasn't closing and updating the list. We are using Telerik web controls for most of the site so I figured it had to do with them since I updated a number of pages, including our master page, to start using the RAD AJAX version of the controls instead of their previous version. It seems this error was being caused because I had added an instance of the RadAjaxManager control to the master page, and I already had an instance of a AjaxManager (older version) on the content page itself. Both managers were conflicting with one another, which caused this error.

All I needed to change was the "RadAjaxManager" markup code to a "RadAjaxManagerProxy" and that resolved this particular error.


 
Categories: ASP.NET

November 8, 2008
@ 10:19 AM

I needed to sign back up for online access to one of my bank accounts. I used to have this all automated through Quicken but at some point it broke and I never seemed to get around to fixing it. I couldn't remember the account password so I requested it to be changed. They had a cool step where they had me select which phone number I had registered with the account would be called. As soon as I hit "Next" they (well, their automated system) was calling. They wanted me to confirm the fact that I was expecting the call and to enter a code which was shown on the screen. As soon as I did that it allowed me to continue on to change my password.

That was pretty cool - a pretty painless way of ensuring I'm really who I say I am. Imagine how disappointed I was when I started typing in my new password:

Bank Password

Really? No special characters and I'm limited to 12 characters for access to my bank account? (sigh). My LinkedIn account has a more secure password than this. Lame.


 
Categories: Soapbox

I'm working on an application which is going to use WCF heavily for communications between a client side application and a server-based service application. One aspect of the application that I've been ignoring during testing was WCF authentication. During testing the application has been running on my local network. I had enabled connection based authentication (ex.SSL) and it is using TCP as it's binding since I do a lot of callback messaging. In the back of my mind I realized that when it's deployed the client will be on a different network, communicating over the internet, so there may be some issues with the current security model.

A week ago I finally got around to looking at it and it completely breaks when the client isn't local, since it was using Windows authentication to do it's authentication. Microsoft has a nice manual which walks through the various ways of configuring security in WCF, which you should choose (and why) under various scenarios. Scenarios, Patterns, and Implementation Guidance for Web Services Enhancements (WSE) 3.0 from their patterns & practices team. Based on a number of factors, it suggested I use SSL along with message based security. I'm not going to be hosting this in IIS, so HTTPS wasn't an option for the SSL connection. I also wasn't using Windows authentication and didn't want to deal with some of the issues of the other credential types (IssuedToken, Digest, etc.). So I decided on UserName.

I now had two things to fix - first, I needed to implement my own UserName authentication scheme (again, I like making my life hard so I wasn't using the default authentication provider in ASP.NET that used SQL Server). This turns out to be pretty simple by inheriting from the UserNamePasswordValidator class in the System.IndentityMode.Selectors namespace and overriding the Validate method:

using System;
using System.Collections.Generic;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Text;

namespace MySampleApp.Server

{

    /// <summary>

    /// This class is responsible for validating the username/password

    /// used by the connecting WCF client.

    /// </summary>

    /// <remarks>

    /// <para>

    /// The server is configured to use this class in the Service Behavior

    /// serviceCredentials section of the config file.

    /// CustomUserNamePasswordValidatorType is set to SampleValidator

    /// UseNamePasswordValidationMode is set to Custom

    /// </para>

    /// </remarks>

    /// <developer>Paul Mrozowski</developer>

    /// <created>10/13/2008</created>

    public class SampleValidator : UserNamePasswordValidator

    {

        public override void Validate(string userName, string password)

        {

            // TODO: Finish, this is just here to test out the idea.

            if (userName != "user" || password != "pass")

                throw new SecurityTokenException("Unknown user.");

        }

    }

}

 

If the passed in username/password is invalid, throw an exception. Easy. I added this new class to my server project, then modified the app.config file to let WCF know to use this for authentication.

<bindings>
      <nettcpbinding>
          <binding name="StandardServerBinding" maxbuffersize="8192000" 
              maxreceivedmessagesize="8192000" listenbacklog="5000" maxconnections="1000"> 
              <readerquotas maxdepth="24" maxstringcontentlength="8192000" maxarraylength="8192000" 
               maxbytesperread="8192000" maxnametablecharcount="8192000" />
              <reliablesession inactivitytimeout="01:00:00" />
              <security mode="Message">                  
                  <message clientcredentialtype="UserName" />
              </security>
          </binding>
      </nettcpbinding>
  </bindings>
        <behaviors>
            <servicebehaviors>
                <behavior name="RegisterBehavior">					
                    <servicedebug includeexceptiondetailinfaults="true" />
                    <servicemetadata />
                    <servicethrottling maxconcurrentcalls="48" maxconcurrentsessions="5000" 
                      maxconcurrentinstances="5000" />
                    <servicecredentials>
                        <usernameauthentication usernamepasswordvalidationmode="Custom" 
          customusernamepasswordvalidatortype="MySampleApp.Server.SampleValidator,MySampleApp.Server" 
                          cachelogontokens="true" cachedlogontokenlifetime="01:00:00" />
                    </servicecredentials>					
                </behavior>
                <behavior name="ThrottlingBehavior">
                    <servicethrottling maxconcurrentcalls="48" maxconcurrentsessions="5000" 
                        maxconcurrentinstances="5000" />
                </behavior>
            </servicebehaviors>
        </behaviors>		
        <services>
            <service name="MySampleApp.ServerProcess" behaviorconfiguration="RegisterBehavior">
                <endpoint name="PrimaryEndpoint" contract="MySampleApp.Server.Contracts.IServer" 
                   binding="netTcpBinding" address="net.tcp://localhost" 
                   bindingconfiguration="StandardServerBinding" />
                <endpoint name="MEXEndpoint" contract="IMetadataExchange" 
                   binding="mexHttpBinding" address="http://localhost/MEX/" bindingconfiguration="" />
            </service>
        </services>

You add a <serviceCreditials\userNameAuthentication section to the ServiceBehavior and specify a Custom userNamePasswodValidationMode. I tell it to use the full name of the class, the second part of that after the comma tells it which assembly it's located in. In the netTcpBinding you can see that I've enabled security on the message, and told it to use the UserName authentication type.

        <bindings>
            <netTcpBinding>
                <binding name="NewBinding0" maxBufferSize="8192000" maxReceivedMessageSize="8192000">
                    <readerQuotas maxDepth="24" maxStringContentLength="8192000"
                        maxArrayLength="8192000" maxBytesPerRead="8192000" maxNameTableCharCount="8192000" />
                    <reliableSession inactivityTimeout="01:00:00" />
                    <security mode="Message">
                        <transport clientCredentialType="None" protectionLevel="None" />
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </netTcpBinding>

I've set the security mode to Message and clientCredentialType to UserName as well. Now I needed to somehow get my client application to pass the user name and password. I had used Visual Studio and let it build my client proxy, so I thought I'd be able to set this immediately after I created the proxy, ex:

InstanceContext context = new InstanceContext(this);

 

this.m_client = new ServerClient(context);

this.m_client.ClientCredentials.UserName.UserName = "user";

this.m_client.ClientCredentials.UserName.Password = "pass";

Unfortunately, that doesn't work - I was getting "Object is read only" when I attempted to do this. From some searching I was able to find out that this can occur when the connection has already been opened, but in my case I hadn't done that yet. I spent a bunch of time trying to figure out why it wouldn't work and finally ended up putting the code inside of the proxy class VS had generated by editing the constructor and setting things up there.

public ServerClient(System.ServiceModel.InstanceContext callbackInstance) :

        base(callbackInstance)

{       

    base.ClientCredentials.UserName.UserName = "user";

    base.ClientCredentials.UserName.Password = "pass";      

}

At this point, I thought I was all set to go. I fired things up and it immediately faulted the connection. I opened up the WCF Service Configuration Editor (C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\svcconfigeditor.exe) and enabled tracing on both the server and client and re-ran things so I could capture a trace. Once that was done I opened up the WCF Svc Trace Log Viewer (which is part of the Windows SDK, mine is located at: C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\SvcTraceViewer.exe). After looking through the log, it appeared to be failing with a "The security protocol cannot secure the outgoing message" error.

Again, I did a bunch of searching, reading, and testing and finally realized that it wanted to encrypt the connection but couldn't. I mentioned earlier that I decided to use SSL for the connection, but when I was working on this I didn't realize that it didn't magically decide how to do that. It looked like I could use an X509 certificate to enable encryption, but I wasn't at all thrilled with the prospect of trying to get that cert. install on client machines. From the couple of times I've seen it needed, it never seems to run particularly smoothly (when it works, it just works, but when it fails, you have no idea why it's failing). So I essentially wanted to be able to load my cert. at runtime instead of loading it into the Windows cert. store. I could buy a real cert., but for this app. it just wasn't necessary. VS includes some tools for self-signing certificates so I looked into that instead.

If you open a Visual Studio command prompt (hiding in the Visual Studio Tools subdirectory of Visual Studio on the Start menu), it sets up the pathing so you can use the various command line tools. The first thing I needed to do was create a key for the server:

makecert -r -pe -n "CN=RCS Solutions, Inc." -b 01/01/2008 -e 12/31/2099 -sky exchange 
Where:
Server.cer -sv Server.pvk Server.cer - the certificate (public key)
Server.pvk - the private key

When it runs, it prompts for a password used to encrypt the private key. You can leave it blank, but it's suggested you fill it in (so I did).

Next I decided to merge the public and private key into a single file with a PFX extension. It requires you to re-enter the password you used to encrypt the private key (or pass in in the command line, which is what I did).

pvk2pfx.exe -pvk Server.pvk -spc Server.cer -pfx Server.pfx -pi mycertpassword

At this point I added all three files to my project. I also needed to get my proxy to use this cert. Since it's not a real certificate (meaning there isn't a chain of trust established between my cert at some known/trusted provider) I had to disable certificate validation. Yeah, pretty secure stuff ;-)

           ServiceHost host = null;

           try

           {

               // Specifically not calling Dispose() on host since it also calls Close and if we've

               // disposed after calling Close that will cause errors.

               host = new ServiceHost(typeof(ServerProcess));

 

               string dir = System.IO.Directory.GetCurrentDirectory();

 

               X509Certificate2 cert = new X509Certificate2(dir + "\\Server.pfx", @"mycertpassword");

               host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;                               

               host.Credentials.ServiceCertificate.Certificate = cert;

 

               host.Open();

 

I repeated the above sequence for my client, creating it's own public/private key. I added the same code to configure the cert, except I put it in the pre-generated proxy constructor.

public ServerClient(System.ServiceModel.InstanceContext callbackInstance) :

        base(callbackInstance)

{       

    base.ClientCredentials.UserName.UserName = "1";

    base.ClientCredentials.UserName.Password = "2";

 

    // TODO: Fill in with correct credentials

 

    string dir = System.IO.Directory.GetCurrentDirectory();

    X509Certificate2 cert = new X509Certificate2(dir + "\\Client.pfx", @"mycertpassword");

    base.ClientCredentials.ClientCertificate.Certificate = cert;       

    base.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;         

 

}

 

I thought I was good to go, so I started everything back up. It failed with the same error as before. Yet more searching revealed that I needed to include a copy of the server's certificate inside of the client's app.config (I have no idea why, it seems like it should happen automatically as part of it's public key exchange). The problem was getting it in the format it required. I tried a few of the various ways that were suggested, but didn't have much luck with them. I finally regenerated the client side proxy using the svcutil command line app. and pointing it to a MEX (Metadata EXchange) endpoint and it magically created the certificate value I needed. I cut and pasted that code into my production proxy.

       <client>
            <endpoint address="net.tcp://localhost/" binding="netTcpBinding"
                bindingConfiguration="PrimaryEndpoint" contract="Server" name="PrimaryEndpoint">
                <identity>
			<certificate encodedValue="AwAAAA(long value removed)" />                    
                </identity>
            </endpoint>

When I restarted everything it was finally able to open a connection. I still need to figure out how to get my WCF service and IIS to share the same port, but I'm making some progress at least.


 
Categories: WCF