mo.notono.us

Tuesday, September 13, 2011

Getting the schema of a dataset from a webservice

I found the following in my Blogger Drafts folder from two-thousand-and-freaking-five.  Since I had completely forgotten about this and never published it, I figured I'd do so now...

1/20/2005:
In a comment to : Evil = WebService DataSet; //Fix this, Gary McDonald mentions:
You are familiar with the auto-generated WSDL obtained by calling your ASMX with the ?wsdl request, so try ?schema=xxx where xxx is the name of the returned XSD.

For example, if I have an ASMX at http://www.tempuri.org/MyService.asmx and It has a Method named Foo that returns a typed dataset named Bar, I can get the schema from http://www.tempuri.org/MyService.asmx?schema=Bar
I wasn't aware of that. Cool.

Labels: , , , ,

Tuesday, January 11, 2011

"Microsoft has completely lost the web development community."

Last year Mark Pilgrim released a free e-book/site called “Dive Into Html5” (http://diveintohtml5.org/).  The site/book has served as a valuable resource on a recent Html5 project we’re working on here at AIS, and I have frequently gone back for details on topics such as local storage and canvas.  It is an excellent book for any bleeding edge web developer.  It is so choice. If you have the means, I highly recommend picking one up.

This week, Mark posted his observations on how publishing a free e-book (which is also purchasable in print format) works well for him, and that it gives great insight into what parts of the book are being read, and by whom. He then makes the following observation:

6% of visitors used some version of Internet Explorer. That is not a typo. The site works fine in Internet Explorer — the site practices what it preaches, and the live examples use a variety of fallbacks for legacy browsers — so this is entirely due to the subject matter. Microsoft has completely lost the web development community. (emphasis mine)

I forwarded this internally within AIS, and a nice debate ensued.  One common complaint was the hyperbole of the statement, and I agree; a more accurate line would likely be "Microsoft as a browser vendor has lost significant mindshare in the bleeding edge web development community."

Personally one of the things I love about Html5 (using the term the way the hypers would – to mean modern web development with client-driven UI interactions using JavaScript, CSS(3) and some HTML5 semantics) is that it has in some ways unified the web development community:  The debate a few years ago was about JSP vs .NET vs PHP vs Python vs Rails vs someotherservertechnology.  Folks from different camps seldom interacted and learned from each other.  With Html5, the backend processes are completely irrelevant, as long as they don’t muck with the Html (ASP.NET webforms is still a major sinner here, unfortunately) and developers using all sorts of backend software and operating systems are now adding to the collective knowledge, mostly working towards the common goal of getting as much functionality as possible, pushed to end users through mostly standards compliant browsers. 

For instance, our Html5 app is backed by ASP.NET MVC 2 and SQL server.  We do all our development on Windows, in Visual Studio – we’re looking to deploy to Azure.  Clearly we’re MS developers.  But we could just as well have done the app in Php against MySql running on linux and apache, and we’re taking cues from folks using python, java, Rails, Node.js, php and God knows what on the backend.

At the same time I haven’t used IE by choice for about 5 years, maybe more…

I was asked what I thought MS could do to gain back some developer mindshare – so here goes:

  • My thoughts are that if Html5 and the set of bleeding edge technologists that go with it are any kind of priority for MS,  they need to do some or all of the following:
  • Find a way to upgrade the legions of IE 6, 7  and 8 users to IE9.  This will obviously not be easy,  but they could do something similar to what Google did with Chrome frame (i.e. make IE9 a plugin for the older browsers),  or they could do something like the makers of the “IE Tab” Chrome and Firefox extensions do,  allow IE to be hosted inside Chrome,  and only activate it for certain sites.  Or let users install IE9 side by side with the older versions.   All of these would have as goal to encourage end users to use the latest possible browser for the task they need it for,  and to make them install IE9 instead of Chrome or Firefox.
  • Make IE9 the paragon of standards compliance.   (They are actually getting close to this...)
  • Bring IE9 to WP7 and whatever tablet software they're coming out with.
  • Reduce the focus of Silverlight as a browser plugin,  and make it more about web-deployed desktop apps.
  • Drastically improve the support for css and javascript in Visual Studio, including debugging and unit testing.   And give this toolset away in the form of VS Express.
  • Evolve the Dev tools in IE9 to become better than Chrome's inspector and the Firebug plugin.
  • Separate the IE development from Windows to allow quicker iterations
  • Do more things like the jQuery deal. The world of CSS is a mess (we desperately need mixins and code forks like those provided by media queries), MS could take the lead here…

The point is, whether Mark’s browser percentages are statistically valid as an indication of web developer’s preferences, or to what degree Microsoft is lagging/losing developer mindshare; these are not the pertinent questions.  The fact is that Microsoft is now not a leader in emerging web development areas – maybe they never were – but should they want to be, they need to take action. IE9 is shaping up to be a great browser, and they need to push it aggressively.

Labels: , , , , , , , , , , , , , , , , , , ,

Friday, June 11, 2010

How to Always Run Visual Studio As Administrator

To jum straight to the solution, click here

“Certain tasks, including debugging and creating local IIS applications, require that you start Visual Studio as a user with Administrative privileges. On Windows Vista, and Windows Server 2008 when not running as the built-in Administrator account, this requires right-clicking the Visual Studio 2008 icon in the Start Menu and choosing Run as administrator.

“To make this process easier, you can create a shortcut and check the Run this program as an administrator check box on the Compatibility tab of the shortcut properties.”
from Using Visual Studio 2008 with IIS 7 @ learn.iis.net

On the last few projects I’ve worked on, we’ve used IIS sites for our development (for a number of reasons I won’t detail here), and the need to open VS in admin mode has been a constant annoyance.  It’s like constantly getting bitten by a mosquito. Today I finally got annoyed enough to spend 5 minutes researching a solution.  (I know.  I procrastinate.)

The solution, or what seems to be working for me so far at least was found at sevenforums.com: How to Run a Program as an Administrator in Windows 7.  Some of these options I knew about, the one I hadn’t tried and which worked for me was this:

1. Right click on the program shortcut or program .exe file, then click on Properties, and on the Compatibility tab. (See screenshots below)
NOTE: If you are doing this while logged on as a standard user instead of an administrator, then you will need to also click on the Change settings for all users button and type in the administrator's password.

Run as Administrator-compatibility_mode1.jpgRun as Administrator-compatibility_mode2.jpg

2. To Always Run this Program as an Administrator -

A) Check the Run this program as an administrator box, and click on OK. (See screenshots above)

The key is to change the compatibility setting of the Visual Studio EXECUTABLE, not the shortcut to it.  I.e., on my laptop, I went to C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ and right-clicked devenv.exe and then proceeded as above.

I then had to add one more step – when I now clicked on a .sln file, nothing would happen.  It appears the default Open action couldn’t run, I assume, due to inadequate privileges.  To fix this, I right-clicked the .sln file, selected Open With –> Choose Default Program, and then selected Visual Studio, making sure Always use… was checked.

Presto – my .sln files now open asking to be run as admin, as do my jump list projects.

Itch scratched.

Labels: , , , , , , , ,

Sunday, May 09, 2010

Oops - “spellcheck any multiligual texts…”

So I was responding to an internal developer forum request for recommendations for a WYSIWYG html editor with spell check.  I was going to recommend Telerik’s Editor and related RadSpell component.  Not so sure any more…

multiligual spellcheck

Cobbler. Children.  All that.

Labels: , , , , ,

Thursday, April 08, 2010

Uri Properties

For the life of me I can never remember what each property of the Uri class is meant to return.  SnippetCompiler to the rescue:

public static void RunSnippet()
{
  Uri foo = new Uri("http://some.domain.com/folder/file.htm?param=val#frag");
    foreach(PropertyInfo pi in foo.GetType().GetProperties()){
      if (pi.CanRead) {
        WL("{0}: {1}", pi.Name, pi.GetValue(foo, null));
      }
    }
}

Make sure using System.Reflection; and using System.Web; are is included, and there you have it:

AbsolutePath: /folder/file.htm
AbsoluteUri: http://some.domain.com/folder/file.htm?param=val#frag
Authority: some.domain.com
Host: some.domain.com
HostNameType: Dns
IsDefaultPort: True
IsFile: False
IsLoopback: False
IsUnc: False
LocalPath: /folder/file.htm
PathAndQuery: /folder/file.htm?param=val
Port: 80
Query: ?param=val
Fragment: #frag
Scheme: http
OriginalString: http://some.domain.com/folder/file.htm?param=val#frag
DnsSafeHost: some.domain.com
IsAbsoluteUri: True
Segments: System.String[]
UserEscaped: False
UserInfo:

UPDATE:  Steve Michellotti just showed me how this works beautifully in LinqPad as well, just set the language to C# Statement(s), replace WL with Console.WriteLine and you’re good to go.

Labels: , , , ,

Wednesday, January 06, 2010

MVC Route Constraint to Exclude Values

For Album Credits I wanted to allow personalized urls of the format http://albumcredits.com/yournamehere.  This turned out to be quite an interesting routing exercise.

Since this is an MVC app, our standard url format is of the usual http://albumcredits.com/{controller}/{action}/{index} kind, and for some pages, I need to allow the url to simply specify the controller, defaulting the action to index – again, the usual ASP.NET MVC pattern.

I was familiar with the constraint parameter option for the AddRoute method, but had never studied it in much detail – we’d used it to limit certain indexes to be numeric, but that was all.  For the root-level personalized urls we needed a more robust constraint – specifically we needed to exclude any controller from the list of valid personalized Urls.

I first spent more time than I cared to trying to come up with a regular expression pattern that would NOT match the list of controller names – it looked something like this:
^(?:(?!\b(foo|bar)\b).)*$
(thanks to Justin Poliey/stackoverflow.com) where foo and bar, etc were the controller names to NOT match.

Not until after I got that to work did I think to google “mvc custom route constraint”.  Of course the MS MVC team was smarter than that – custom route constraints are really very straight forward…

For my purposes, I went with David Hayden’s approach – the code below is essentially the same as his, just with the logic reversed.

using System;
using System.Linq;
using System.Web;
using System.Web.Routing;

namespace AlbumCredits.Web
{
	/// <summary>
	/// Route constraint that returns true if the parameter value is not one of the excluded values.
	/// </summary>
	/// <example>A controller constraint like 
	/// <code>new { controller = new ExcludeValuesConstraint("foo", "bar") }</code>
	/// will match "blah" or "snort" but will not match "foo" or "bar".
	/// </example>
	public class ExcludeValuesConstraint : IRouteConstraint
	{
		private readonly string[] _excludeValues;
		/// <summary>
		/// Initializes a new instance of the <see cref="ExcludeValuesConstraint"/> class.
		/// Example: <code>new { controller = new ExcludeValuesConstraint("foo", "bar") }</code>
		/// will match "blah" or "snort" but will not match "foo" or "bar".
		/// </summary>
		/// <param name="excludeValues">The excluded values.</param>
		public ExcludeValuesConstraint(params string[] excludeValues)
		{
			_excludeValues = excludeValues;
		}

		/// <summary>
		/// Determines whether the URL parameter contains a valid value for this constraint.
		/// </summary>
		/// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
		/// <param name="route">The object that this constraint belongs to.</param>
		/// <param name="parameterName">The name of the parameter that is being checked.</param>
		/// <param name="values">An object that contains the parameters for the URL.</param>
		/// <param name="routeDirection">An object that indicates whether the constraint check is being performed when an incoming request is being handled or when a URL is being generated.</param>
		/// <returns>
		/// true if the URL parameter contains a valid value; otherwise, false.
		/// </returns>
		public bool Match(HttpContextBase httpContext, Route route, string parameterName, 
			RouteValueDictionary values, RouteDirection routeDirection)
		{
			return !(_excludeValues.Contains(values[parameterName].ToString(), StringComparer.InvariantCultureIgnoreCase));
		}
	}
}

I can now use this when setting up my Route Table like this:

	routes.MapRoute("PersonalizedUrl",
		/* for urls like  */ "{personalizedUrl}",
		/* route defaults */ new { controller = MVC.Profile.Name, action = MVC.Profile.Actions.IndexByPersonalizedUrl, personalizedUrl = string.Empty },
		/* where          */ new { personalizedUrl = new ExcludeValuesConstraint(ControllerNameArray) }
	);

Labels: , , ,

Friday, August 28, 2009

Making T4MVC comply with CLS

FXCop rule CA1014 tells you to mark your assembly as CLSCompliant. If you adhere to this, your T4MVC (as of build 2.4.01 at least) will throw compiler warnings saying stuff like

Identifier

‘xxxController._Actions’
’xxxController._Views’
’T4MVC._Dummy’

is not CLS-Compliant.

If you have 10 controllers and 50 views this will result in 61 warnings…

The reason is that these are public members that start with an underscore, which is a CLS no-no:

http://stackoverflow.com/questions/1195030/why-is-this-name-not-cls-compliant

 

To solve this, edit the T4MVC.tt file to mark the code with a [CLSCompliant(false)] attribute.  Once you start this, you’ll also find additional warnings from mebers that implement the now-explicitly-non-compliant members, but a few more [CLSCompliant(false)] attribute handles that. Full code in gist below.

Labels: , , , , , ,

Monday, August 24, 2009

Web Setup MSI woes on IIS7 + solution

Just a note to my future self and to anyone else who might stumble on this:

We created an MSI to install our MVC app, but the new test server refused to install it:

The Installer simply stopped, with an Installation interrupted message, and the application event log listed the following:

Windows Installer installed the product. Product Name: XXXXX. Product Version: x.y.z. Product Language: 1033. Installation success or error status: 1603.

The correct google search term here is: Installation success or error status: 1603.

It will lead you to the solution by Ben Noyce at NInitiative:

Long nights and story short, in order to install a web setup project on Windows Server 2008 and IIS 7, you need to install the IIS 6 Metabase Compatibility role service.

Thanks, Ben!

Labels: , , , , , , ,

Making T4MVC comply with StyleCop

On a current MVC project we’re also using the excellent T4MVC template by David Ebbo.  StyleCop however, thinks the generated code is well, less than perfect – it generates some 500 warnings at the moment. 

The solution to this is a simple choice between two options:

Fix the TT file to generate StyleCop compliant code, or exclude the generated T4MVC.cs class from StyleCop.

The pragmatic choice here is of course to exclude the file.  But how?

I first tried to add <ExcludeFromStyleCop>true</ExcludeFromStyleCop> to the Compile entry in the csproj file. Unfortunately that only works with builds from OUTSIDE Visual Studio.

Sergey Shishkin has the answers:

Encapsulating the code in a region that contains the string “generated code” does the trick, but even easier is to simply put a // <auto-generated /> comment at the top of the generated file – which of course means edit the TT file to stick it there.

Would be nice to see this included in the next release….

Labels: , , , , ,

Friday, July 31, 2009

ASP.NET MVC: TagBuilder Extension Methods

I wasn’t happy with the TagBuilder class, so I improved it… See gist below:

This kind of thing could be useful in MOSS WebPart development as well…

Labels: , , , , , , , ,

Wednesday, July 29, 2009

ASP.NET MVC: Corrected Moq based MvcMockHelpers

I've been reading the mostly excellent Pro ASP.NET MVC Framework book by Steven Sanderson, but when trying to implement some of his code in my current project (yay – not SharePoint for a change), I encountered a bug by omission.  In Chapter 8, page 248, he shows how to mock the HttpContext using Moq.  Works great for most urls, but bombs if you include a Query String parameter.
 
Steve Michelotti pointed me to what he uses: the Hanselman/Kzu based MvcMockHelpers class.  Unfortunately it too has some issues (at least as written in Scott’s old post), so below is an updated version.  Get it from github.
 
Use and abuse at will

Labels: , , , , , ,

Wednesday, March 11, 2009

ASP.NET 101 Reminder 2: ListItem[] is an array of Reference Types

..which means that if you fill one ListControl with the ListItem[] array, and then fill another ListControl with the same array, BOTH DropDownLists will be updated if you modify any ListItem (such as selecting it: ListItem.Selected = true;)

If you think of a ListControl as its HTML equivalent then this may be a bit confusing and bug-prone (and indeed, once rendered as HTML the controls are no longer in sync, it only happens server-side), but from an OO perspective it does make perfect sense.

It just makes me wish for easier and generic deep cloning of collections of objects…. Sigh.

Labels: , , , , , ,

ASP.NET 101 Reminder: ListControl.Text == ListControl.SelectedValue…

…and NOT ListControl.SelectedItem.Text

ListControl implements IEditableTextControl and thus ITextControl, which mandates the Text property.  For whatever reason (I’m sure it was a good one) the ASP.NET architects chose to make the Text property implementation get and set the ListControl.SelectedValue rather than ListControl.SelectedItem.Text.

 

Coincidentally, there is no ListControl.FindByText method: the following extension methods may come in handy:

/// <summary>
/// Performs a case insensitive comparison finding a ListItem by its text value, and selecting that item
/// </summary>
/// <param name="listControl">The list control</param>
/// <param name="text">The text to match</param>
/// <returns>The first matched, selected <see cref="SPListItem"/> or null if not found.</returns>
public static ListItem SelectFirstByText(this ListControl listControl, string text)
{
  // first clear any previous selection 
  listControl.SelectedIndex = -1;
		
  foreach (ListItem item in listControl.Items)
  {
    if (string.Equals(item.Text, text, System.StringComparison.OrdinalIgnoreCase))
    {
      item.Selected = true;
      return item;
    }
  }
  //if not found...
  return null;
}

/// <summary>
/// Performs a case insensitive comparison finding ListItems by their text value, and selecting those items
/// </summary>
/// <param name="listControl">The list control.</param>
/// <param name="text">The text to match.</param>
/// <returns>An integer array of matched indices.</returns>
public static int[] SelectByText(this ListControl listControl, string text)
{
  List matches = new List();
  // first clear any previous selection 
  listControl.SelectedIndex = -1;
  int i = 0;
  foreach (ListItem item in listControl.Items)
  {
    if (string.Equals(item.Text, text, System.StringComparison.OrdinalIgnoreCase))
    {
      item.Selected = true;
      matches.Add(i);
    }
    i++;
  }
  return matches.ToArray();
}

Labels: , , , , ,

Tuesday, July 29, 2008

SQL: Once again, for the record...

Performance is not a compelling reason to choose stored procedures over dynamic SQL.

At work today I was surprised to overhear a suggestion to dynamically create stored procedures in SQL, just "to take advantage of the added performance of stored procedures" over dynamic/ad-hoc/inline SQL.

So here we go again, for the record:

Performance of Stored Proc vs. Dynamic/Ad-hoc SQL

Actual, simplistic test by BlackWasp:

image
http://www.blackwasp.co.uk/SpeedTestSqlSproc.aspx

SQL Books On Line:

SQL Server 2000 and SQL Server version 7.0 incorporate a number of changes to statement processing that extend many of the performance benefits of stored procedures to all SQL statements. SQL Server 2000 and SQL Server 7.0 do not save a partially compiled plan for stored procedures when they are created. A stored procedure is compiled at execution time, like any other Transact-SQL statement. SQL Server 2000 and SQL Server 7.0 retain execution plans for all SQL statements in the procedure cache, not just stored procedure execution plans. The database engine uses an efficient algorithm for comparing new Transact-SQL statements with the Transact-SQL statements of existing execution plans. If the database engine determines that a new Transact-SQL statement matches the Transact-SQL statement of an existing execution plan, it reuses the plan. This reduces the relative performance benefit of precompiling stored procedures by extending execution plan reuse to all SQL statements.
http://msdn2.microsoft.com/en-us/library/aa174792(SQL.80).aspx

DeKlarit dev take:

People still thinks Stored Procedures are faster, even if there is much evidence that shows otherwise. Fortunately, when they go and ask the LinQ for SQL/Entities team they get the same answer than they get from me. They are not.
http://weblogs.asp.net/aaguiar/archive/2006/06/22/Stored-Procs-vs-Dynamic-SQL.aspx

There are lots of good reasons to use Stored Procedures, and lots of good reasons to use Dynamic SQL.  Performance is rarely a good reason to chooswe one way or the other, and if performance is  the decision factor often dynamic sql comes out on top, e.g. if you can use dynamic sql to eliminate an OR for example (as when using optional query parameters in an advanced search form).

Labels: , , ,

Tuesday, July 22, 2008

C#: String.Inject() - Format strings by key tokens

I generally prefer to use String.Format() instead of doing a bunch of string additions, but constantly find myself splitting the code onto multiple lines with an appended comment to keep track of the indices:

string myString = string.Format("{0} is {1} and {2} is {3}",
  o.foo, //0
  o.bar, //1
  o.yadi, //2
  o.yada //3
);

Wouldn't it be nice you could instead write something like this?:

string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);

Well, now you can - see the string extension method Inject below; it accepts an object, IDictionary or HashTable and replaces the property name/key tokens with the instance values.  Since it uses string.Format internally, it also supports string.Format-like custom formatting:

   1:  using System;
   2:  using System.Text.RegularExpressions;
   3:  using System.Collections;
   4:  using System.Globalization;
   5:  using System.ComponentModel;
   6:   
   7:  [assembly: CLSCompliant(true)]
   8:  namespace StringInject
   9:  {
  10:    public static class StringInjectExtension
  11:    {
  12:      /// <summary>
  13:      /// Extension method that replaces keys in a string with the values of matching object properties.
  14:      /// <remarks>Uses <see cref="String.Format()"/> internally; custom formats should match those used for that method.</remarks>
  15:      /// </summary>
  16:      /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
  17:      /// <param name="injectionObject">The object whose properties should be injected in the string</param>
  18:      /// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
  19:      public static string Inject(this string formatString, object injectionObject)
  20:      {
  21:        return formatString.Inject(GetPropertyHash(injectionObject));
  22:      }
  23:   
  24:      /// <summary>
  25:      /// Extension method that replaces keys in a string with the values of matching dictionary entries.
  26:      /// <remarks>Uses <see cref="String.Format()"/> internally; custom formats should match those used for that method.</remarks>
  27:      /// </summary>
  28:      /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
  29:      /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
  30:      /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
  31:      public static string Inject(this string formatString, IDictionary dictionary)
  32:      {
  33:        return formatString.Inject(new Hashtable(dictionary));
  34:      }
  35:   
  36:      /// <summary>
  37:      /// Extension method that replaces keys in a string with the values of matching hashtable entries.
  38:      /// <remarks>Uses <see cref="String.Format()"/> internally; custom formats should match those used for that method.</remarks>
  39:      /// </summary>
  40:      /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
  41:      /// <param name="attributes">A <see cref="Hashtable"/> with keys and values to inject into the string</param>
  42:      /// <returns>A version of the formatString string with hastable keys replaced by (formatted) key values.</returns>
  43:      public static string Inject(this string formatString, Hashtable attributes)
  44:      {
  45:        string result = formatString;
  46:        if (attributes == null || formatString == null)
  47:          return result;
  48:   
  49:        foreach (string attributeKey in attributes.Keys)
  50:        {
  51:          result = result.InjectSingleValue(attributeKey, attributes[attributeKey]);
  52:        }
  53:        return result;
  54:      }
  55:   
  56:      /// <summary>
  57:      /// Replaces all instances of a 'key' (e.g. {foo} or {foo:SomeFormat}) in a string with an optionally formatted value, and returns the result.
  58:      /// </summary>
  59:      /// <param name="formatString">The string containing the key; unformatted ({foo}), or formatted ({foo:SomeFormat})</param>
  60:      /// <param name="key">The key name (foo)</param>
  61:      /// <param name="replacementValue">The replacement value; if null is replaced with an empty string</param>
  62:      /// <returns>The input string with any instances of the key replaced with the replacement value</returns>
  63:      public static string InjectSingleValue(this string formatString, string key, object replacementValue)
  64:      {
  65:        string result = formatString;
  66:        //regex replacement of key with value, where the generic key format is:
  67:        //Regex foo = new Regex("{(foo)(?:}|(?::(.[^}]*)}))");
  68:        Regex attributeRegex = new Regex("{(" + key + ")(?:}|(?::(.[^}]*)}))");  //for key = foo, matches {foo} and {foo:SomeFormat}
  69:        
  70:        //loop through matches, since each key may be used more than once (and with a different format string)
  71:        foreach (Match m in attributeRegex.Matches(formatString))
  72:        {
  73:          string replacement = m.ToString();
  74:          if (m.Groups[2].Length > 0) //matched {foo:SomeFormat}
  75:          {
  76:            //do a double string.Format - first to build the proper format string, and then to format the replacement value
  77:            string attributeFormatString = string.Format(CultureInfo.InvariantCulture, "{{0:{0}}}", m.Groups[2]);
  78:            replacement = string.Format(CultureInfo.CurrentCulture, attributeFormatString, replacementValue);
  79:          }
  80:          else //matched {foo}
  81:          {
  82:            replacement = (replacementValue ?? string.Empty).ToString();
  83:          }
  84:          //perform replacements, one match at a time
  85:          result = result.Replace(m.ToString(), replacement);  //attributeRegex.Replace(result, replacement, 1);
  86:        }
  87:        return result;
  88:   
  89:      }
  90:   
  91:   
  92:      /// <summary>
  93:      /// Creates a HashTable based on current object state.
  94:      /// <remarks>Copied from the MVCToolkit HtmlExtensionUtility class</remarks>
  95:      /// </summary>
  96:      /// <param name="properties">The object from which to get the properties</param>
  97:      /// <returns>A <see cref="Hashtable"/> containing the object instance's property names and their values</returns>
  98:      private static Hashtable GetPropertyHash(object properties)
  99:      {
 100:        Hashtable values = null;
 101:        if (properties != null)
 102:        {
 103:          values = new Hashtable();
 104:          PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
 105:          foreach (PropertyDescriptor prop in props)
 106:          {
 107:            values.Add(prop.Name, prop.GetValue(properties));
 108:          }
 109:        }
 110:        return values;
 111:      }
 112:   
 113:    }
 114:  }

File downloads:

Labels: , , , , , , , , ,

Tuesday, June 24, 2008

.NET: What exactly does Trim() trim?

I found myself using the character specific String.Trim(params char[] trimChars) overload to remove some carriage returns and tabs when I realized I was reinventing the wheel again.  Not content with the basic Trim()'s intellisense ("Removes all occurrences of white space characters from the beginning and end of this instance") I dove into Reflector:

public string Trim()
{
    return this.TrimHelper(WhitespaceChars, 2);
}

TrimHelper is fairly obvious - it does the actual trimming of the characters.

The WhitespaceChars array (which is also used by TryParse() and some of the other TrimXxx() methods) is set in the String static constructor:

static String()
{
    Empty = "";
    WhitespaceChars = new char[] { 
        '\t', '\n', '\v', '\f', '\r', ' ', '\x0085', '\x00a0', ' ', 
        ' ', ' ', ' ', ' ', ' ', ' ', ' ', 
        ' ', ' ', ' ', ' ', '​', '\u2028', '\u2029', ' ', ''
     };
}

Not sure what some of those non-printable characters are, but I'm satisfied...

Labels: , , , ,

Thursday, January 10, 2008

LiveWriter: Testing CodeSnippet Plugin

Leo Vildosola has written a Code Snippet Plugin for Windows Live Writer.  It looks like it works quite nicely - here is the code from my Extension Methods Post:

/// <summary>
/// Creates a list based on the specified title, description, URL, Feature ID, template type, document template type, and options for displaying a link to the list in Quick Launch.
/// </summary>
/// <param name="listCollection">The list collection.</param>
/// <param name="title">The title.</param>
/// <param name="description">The description.</param>
/// <param name="featureId">The feature id.</param>
/// <param name="templateType">The template type.</param>
/// <param name="docTemplateType">The doc template type.</param>
/// <param name="quickLaunchOptions">The quick launch options.</param>
/// <returns>A GUID that identifies the new list.</returns>
public static Guid Add(this SPListCollection listCollection, string title, string description, string url, Guid featureId, 
    SPListTemplateType templateType, int docTemplateType, SPListTemplate.QuickLaunchOptions quickLaunchOptions)
{
    return listCollection.Add(title, description, url, featureId.ToString("B").ToUpper(), 
        (int)templateType, docTemplateType.ToString(), quickLaunchOptions);
}

(via Frank's World)

Labels: , , ,

C#/MOSS: Extension Methods: For Good and For Evil

Let me start with some good news:  C# 3.0/ASP.NET 3.5 works beautifully with SharePoint 2007, thanks to the forward-compatible nature of the .NET framework from version 2.0 onwards.

This compatibility opens up all sorts of avenues, one of them is the use of Extension Methods, replacing trusty old Utility classes.

Extension methods are amazingly simple, yet handy, essentially they allow you to "correct" the behavior of a class that you otherwise have no control over.  This can be used both for Good (adding useful methods to a poorly written API) and for Evil (making C# look like VB).  And of course it can also be used for sheer Anal Retentiveness:

For instance, I am not a fan of the SPListCollection.Add() method, especially the last two overloads.  Among other parameters these overloads take a string featureID(where the string represents a Guid), an integer templateType (where the integer represents a SPListTemplateType enum), and a string docTemplateType (where the string represents an integer).  WTF?  Why aren't these parameters in their native types?

So I created the following method inside a static class:

/// <summary>
/// Creates a list based on the specified title, description, URL, Feature ID, template type, document template type, and options for displaying a link to the list in Quick Launch.
/// </summary>
/// <param name="listCollection">The list collection.</param>
/// <param name="title">The title.</param>
/// <param name="description">The description.</param>
/// <param name="featureId">The feature id.</param>
/// <param name="templateType">The template type.</param>
/// <param name="docTemplateType">The doc template type.</param>
/// <param name="quickLaunchOptions">The quick launch options.</param>
/// <returns>A GUID that identifies the new list.</returns>
public static Guid Add(this SPListCollection listCollection, string title, string description, 
  string url, Guid featureId, SPListTemplateType templateType, int docTemplateType, 
  SPListTemplate.QuickLaunchOptions quickLaunchOptions)
{
  return listCollection.Add(title, description, url, featureId.ToString("B").ToUpper(), 
    (int)templateType, docTemplateType.ToString(), quickLaunchOptions);
}

Itch scratched. The properly typed overload now shows up in intellisense.

Labels: , , , , ,

Monday, October 08, 2007

Announcing squrl.us

Squrl.us (Short, Quick URLs) is a side project I've been putting together over some weekends and weeknights lately.  Essentially it is a tinyurl clone, but with added functionality, and a cuter look.

It's all really quite simple:  You enter a long and unruly url, press the Scurry button, and in return get a number of short, quick urls that are easy to remember, jot down, or communicate verbally.

For example, the url http://www.spiegel.de/panorama/leute/0,1518,510187,00.html is converted to

The squrl is just a redirection to the original link, the mobile link uses Skweezer.net to render a mobile-friendly version, the cache link goes to the Google cache for the url, and the translated link is to Google's automatic translation service (note this only works for non-English pages).

The whole thing is running on ASP.NET 3.5 with C# 3.0 and Linq (for the heck of it), and SQL Server Express 2005 (this may be migrated to MySQL 5.0 due to server constraints).

Go try it out!

Labels: , , , , ,

Wednesday, June 20, 2007

7 Reasons For ASP.NET 2.0 Developers to Use VS 2008

VS 2008 can target both the 2.0 and the 3.0 (as well as the new 3.5) versions of .NET. But if you're not using 3.5, why would you want to use VS 2008?

Direct from the horse's Scott Guthrie's mouth:

  1. JavaScript intellisense
  2. Much richer JavaScript debugging
  3. Nested ASP.NET master page support at design-time
  4. Rich CSS editing and layout support within the WYSIWYG designer
  5. Split-view designer support for having both source and design views open on a page at the same time
  6. A much faster ASP.NET page designer - with dramatic perf improvements in view-switches between source/design mode
  7. Automated .SQL script generation and hosting deployment support for databases on remote servers

I for one am looking forward to it.

Technorati tags:

Labels: , ,