FTP Upload in .NET - You ain't need no libraries

on June 28th, 2008 at 3:06pm 11 responses

Lately I've been asked about doing FTP related stuff in .NET .

There appear to be many "Ftp Clients" out there on CodeProject and other sites.

I really don't get 'em.

They are simple a leaky abstraction on top of straightforward BCL classes.

 

So what my solution is?

   1:  public void Upload(string server, int port, string targetFolder, string fileName, string username, string password, bool isActive)
2: {
3: var url = string.Format(
4: "ftp://{0}:{1}{2}/{3}", server, port, targetFolder, fileName)
5: using (var ftp = (FtpWebRequest)WebRequest.Create(url))
6: {
7: ftp.Credentials = new NetworkCredential(username, password);
8: ftp.KeepAlive = false;
9: ftp.UseBinary = true;
10: ftp.Method = WebRequestMethods.Ftp.UploadFile;
11: if (isActive)
12: ftp.UsePassive = false;
13:  
14: using (var writer = new BinaryWriter(ftp.GetRequestStream()))
15: writer.Write(File.ReadAllBytes(fileName));
16: }
17: }

 

This basic notepad code covers most of the functionality in FtpWebRequest. You can easily set Binary/ASCII, Active/Passive, Credentials and whatnot.

 

Alternatively, in some cases you can just spawn a ftp.exe process with a simple ftp script. Anyway you can rid yourself from unneeded leaky abstractions.

FileBinderAttribute to ease FileUpload in MonoRail

on June 28th, 2008 at 12:18pm , , , 2 responses

Following Scott Hanselman's post on FileUpload in ASP.NET MVC, I'll add here a few bits on doing that in MonoRail.

 

First, as MonoRail is an extension on top of plain ol' ASP.NET, just as ASP.NET MVC is, you can do the exact same thing - i.e iterate over Request.Files, and use a mocked Files collection for test or something.

 

But as the action parameters binder is very smart, and easily extensible, it's even nicer to just bind the posted data to a HttpPostedFile, using a FileBinder:

 

   1:  [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
2: public class FileBinderAttribute: Attribute, IParameterBinder
3: {
4: public int CalculateParamPoints(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
5: {
6: var key = parameterInfo.Name;
7: return context.Request.Files[key] != null ? 10 : 0;
8: }
9:  
10: public object Bind(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
11: {
12: var key = parameterInfo.Name;
13: return context.Request.Files[key] as HttpPostedFile;
14: }
15: }

 

So a custom binder is an attribute, that implements IParameterBinder, a two methods interface:

  1. CalculateParamPoints() - is for helping the framework correctly guess the best candidate of the action overloads. If the file exists in the request then it makes an action with a HttpPostedFile parameter with the name of the file upload name, a better candidate than an overload with a String parameter with the same name.
  2. Bind() - well, kinda' self explanatory.

 

so now you're action can look like this:

   1:  public void Save([FileBinder] HttpPostedFile myfile)
2: {
3: if (myFile != null)
4: {
5: // do stuff with the file
6: }
7: }

 

Cool? well actually what really is cool is that binding to HttpPostedFile is baked into MonoRail to begin with - so you don't even need this FileBinderAttribute at all ! you can simply

   1:  public void Save(HttpPostedFile myfile)
2: {
3: if (myFile != null)
4: {
5: // do stuff with the file
6: }
7: }

 

So why did I show you that?

Testablility.

Since HttpPostedFile is not easily mockable* (cuz it's bloody sealed and not new-able), you should do what you always do when in need to bypass one of these un-testable hard-to-test* sealed classes: Adapter pattern. Introduce IHttpPostedFile, and supply your own HttpPostedFile encapsulating the built in one.

 

so:

1: [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
2: public class FileBinderAttribute: Attribute, IParameterBinder
3: {
4: public int CalculateParamPoints(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
5: {
6: var key = parameterInfo.Name;
7: return context.Request.Files[key] != null ? 10 : 0;
8: }
9:  
10: public object Bind(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
11: {
12: var key = parameterInfo.Name;

13: var file = context.Request.Files[key] as HttpPostedFile;
14: return file == null ? null : HttpPostedFileAdapter(file);
15: }
16: }

 

and

   1:  public void Save([FileBinder] IHttpPostedFile myfile)
2: {
3: if (myFile != null)
4: {
5: // do stuff with the file
6: }
7: }

 

 

 

 

 

 

* Yes Roy, I know you can throw some TypeMock magic at it

Can you spot the bug?

on June 26th, 2008 at 3:40pm 3 responses

 

DISCLAIMER:

If you are a potential client of mine, or rather a current one, please stop reading NOW, as it's one of these embarrassing "I am sometimes too stupid" posts.

 

 

 

Oh my.

 

That's how my 3am code can look like:

 

   1:  var page = filter.Page.GetValueOrDefault(1);
2: var pageSize = filter.PageSize.GetValueOrDefault(30);
3: var firstResult = (page - 1) + pageSize;

 

It's for NHibernate style paging (i.e. firstResult is for a 0 based index)

 

Spotted (thanks to DbAwareIntegrationTests) and fixed at 6pm

Fabio and Ayende On Caching

on June 23rd, 2008 at 1:14pm , 0 responses

A quick ripoff from NHibernate's users group:

 

Fabio Maulo:

The base concepts to understand are (my opinion):
- *The Cache is not the panacea of performance.*
- Don't use the Cache like the base of your app; add the management of Cache at the end of your development process to increase the performance only where you really need do it.
- Implementing a method named GetAll is, in most cases, a bad idea; an acceptable mediation is PaginateAll(pageSize).
- InMemoryFilter can have less performance than filter trough RDBMS (especially when you intent to do it trough Cache with a large amount of entities).
- Take care on what happen to the memory usage of your app when you are using Cache.

 

Ayende:

The cache is not magic, and should not be treated in such a fashion. I refuse to use the 2nd level cache in my applications until I have a perf problem that can't be solved by creating smarter queries.
Think about the cache as band aid, and good design as avoiding the need for that.

 

And I say Hallelujahs

Castle.Tools.* in Castle Contrib's repository has moved a bit

on June 22nd, 2008 at 2:24pm , , , , 0 responses

The tools (various small helper libraries) are now under http://svn.castleproject.org:8080/svn/castlecontrib/Tools/

 

what's in there:

  1. Castle.Tools.StaticMapGenerator - That one creates a typed tree representing js/css/image files on the site's filesystem.
    more here:
    http://kenegozi.com/Blog/2008/01/17/staticmapgenerator-for-asp-dot-net-first-teaser.aspx
    Which reminds me that I need to add .swf files to the mix ...
  2. Castle.Tools.SQLQueryGenerator -  That one is for building plain old SQL strings in a typed and intellisense friendly way.
    more here:
    http://kenegozi.com/Blog/2008/01/27/already-added-stuff-to-sql-query-generator.aspx
  3. Castle.Tools.QuerySpecBuilder/ - The new kid in the block. That's a tool that is used to build specs programmatically, which would later be translated to a SQL string for your flavour of DAL. The generated SQL is mostly ANSI compliant, apart from the Paging syntax which is TSQL only currently, but at some point I'll add an extension point to allow other syntaxes.
    I also have an external thing to make it extremely useful with NHibernate's ISQLQuery.
    I'll blog about all that when I'll have a little time.

Simple String Hashing in .NET

on June 22nd, 2008 at 1:34pm , 0 responses

I've been asked about it several times lately, so I'll just put here an oldie that I've been using for a few years now untouched:

 

   1:  // MIT license
2: // Copyright 2005-2008 Ken Egozi
3: //
4: // Permission is hereby granted, free of charge, to any person obtaining a copy
5: // of this software and associated documentation files (the "Software"), to deal
6: // in the Software without restriction, including without limitation the rights
7: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8: // copies of the Software, and to permit persons to whom the Software is
9: // furnished to do so, subject to the following conditions:
10: //
11: // The above copyright notice and this permission notice shall be included in
12: // all copies or substantial portions of the Software.
13: //
14: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20: // THE SOFTWARE.
21:  
22: using System;
23: using System.Collections.Generic;
24: using System.Text;
25: using System.Security.Cryptography;
26: using System.Collections;
27:  
28: namespace KenEgozi.CryptographicServices
29: {
30: public static class Hashing
31: {
32: private static Hashtable hashAlgorithms = Hashtable.Synchronized(new Hashtable());
33:
34: /// <summary>
35: /// Hashing a given string with SHA2.
36: /// </summary>
37: /// <param name="data">Data to hash</param>
38: /// <returns>Hashed data</returns>
39: public static string HashData(string data)
40: {
41: return HashData(data, HashType.SHA256);
42: }
43:
44: /// <summary>
45: /// Hashing a given string with any of the supported hash algorithms.
46: /// </summary>
47: /// <param name="data">Data to hash</param>
48: /// <param name="hashType">Hashing algorithm to use</param>
49: /// <returns>Hashed data</returns>
50: public static string HashData(string data, HashType hashType)
51: {
52: HashAlgorithm hash = GetHash(hashType);
53: byte[] bytes = (new UnicodeEncoding()).GetBytes(data);
54: byte[] hashed = hash.ComputeHash(bytes);
55: StringBuilder sb = new StringBuilder(64);
56: foreach (byte b in hashed)
57: sb.AppendFormat("{0:x2}", b);
58: return sb.ToString();
59: }
60:  
61: private static HashAlgorithm GetHash(HashType hashType)
62: {
63: if (!hashAlgorithms.ContainsKey(hashType))
64: hashAlgorithms.Add(hashType, CreateaHashAlgorithm(hashType));
65: return hashAlgorithms[hashType] as HashAlgorithm;
66: }
67:  
68: private static HashAlgorithm CreateaHashAlgorithm(HashType hashType)
69: {
70: switch (hashType)
71: {
72: case HashType.MD5:
73: return new MD5CryptoServiceProvider();
74: case HashType.SHA1:
75: return new SHA1Managed();
76: case HashType.SHA256:
77: return new SHA256Managed();
78: case HashType.SHA384:
79: return new SHA384Managed();
80: case HashType.SHA512:
81: return new SHA512Managed();
82: default:
83: throw new NotImplementedException();
84: }
85: }
86: }
87:  
88: public enum HashType
89: {
90: MD5,
91: SHA1,
92: SHA256,
93: SHA384,
94: SHA512
95: }
96: }

 

Not beautiful, however useful.

 

You can download this file from here (just remove the .txt - the server doesn't serve .cs files directly)

 

btw, the colouring of the source was made with the help of http://www.manoli.net/csharpformat/, even though I had to do some manual tweaking to make it work with this blog. If colours of reserved words, comments etc. do not appear, then please refresh your browser's cache to get the updated css

The Fox Is Hungry

on June 20th, 2008 at 3:28pm 6 responses

My impression on Firefox 3 by now:

  1. It looks nicer.
  2. It still eats up all of my poor machine's memory

The fox is hungry

Download Firefox 3

on June 18th, 2008 at 11:13am 0 responses

 

I really don't need to add anything, right?

 

download-button-primary

 

 

 

Installed it myself today. Appear to be a bit faster, and to eat up less memory.

 

The Google Toolbar and FireFTP were updated automatically.

 

I needed to manually re-install FireBug and IE-Tab

 

btw, does anybody know where did the Back and Forward buttons has gone to? Im using the keyboard usually, but sometimes (especially during in-office-lunch-time) I need to click the Back thing with the mouse

 

UPDATE: it appear that as part of the upgrade, FF3 has inherited my customised toolbars and that's why it was looking weird.  So I right-clicked on the toolbar -> customise, the clicked "Restore Default Set". All the buttons came back, then I rearranged the toolbar to my liking.

Naming Interfaces

on June 17th, 2008 at 6:16am , 4 responses

An innocent question raised by Ayende has started an interesting debate on the comments.

 

In short (read it all there - don't be lazy)

Which interface name is better?

a. IRecognizeFilesThatNeedToBeIgnored

b. IIgnoreFilesSpecification

with a single method: ShouldBeIgnored(string file);

 

Some were in favour of a, some in favour of b.

The interesting thing is that many has offered a third option:

c. IFileFilter

 

Let's group these things:

  1. Options a. and b. => Narrow and very specific interfaces. You can guess exactly what a class implementing them can do.
  2. Option c. => a general-purpose interface. You'd know that a class that implement this type of interface is doing stuff with files, but it's kinda vague. Can it filter a file out of a list of files? can it filter the content of a file? can it do other kinds of filtering on some kinds of files?

Personally I couldn't care less which one of the first type will be used. I slightly in favour of b., as I think funny names are good. The compiler cares nothing about names, but the human mind would remember the purpose well, and a newcomer would pick it up quickly.

 

The second group (IFileFilter) is not good. It might get filled with a lot of methods that do file filtering.
and if it's not, I think it should reflect the intention of the implementing class.
Since multiple interfaces per class are allowed, it's ok to have specialised ones.

AspView Nostalgia

on June 14th, 2008 at 8:38am , 0 responses

I've just stumbled upon an old post on this very blog.

 

In the post I actually quote a comment I have left on Ayende's blog. The nice excerpt from there:

 

Maybe having a ViewEngine that's use c# or VB.NET instead of boo (not the WebForm ViewEngine which suck, but somthing similar to Brail)

 

This was on 26/10/2006 ...

 

Three weeks later (on 14/11/2006) I've announced AspView, after starting working with the first bits for my employer at the time, SQLink

 

Two days later I have made a first release public.

 

on 01/12/2006 I was granted a Write access to castle/contrib - AspView started gaining some recognition from the community

 

The rest is history. You can follow on the AspView tag here

 

What's there to come? stay tuned and find out

That's the way to hire

on June 14th, 2008 at 7:31am 0 responses

There's this startup company named DotSpots. They are hiring, and as a Fizz-Buzz test they have come up with challenges for prospective candidates to solve.

 

I currently am not into looking a new employer, but just for the sake of the puzzle, I wish I had some spare time to knock off these challenges. On first look they appear to require some sort of a backtracking greedy algorithm, so that could have been a great opportunity to get messy with F# ...

 

 

(via Roy)


Follow

Statistics

Posts count:
447
Comments:
950