# Saturday, December 10, 2011

I've just done all 4 of the exams for .Net 4.0 MCPD Web Developer. Overall, I quite enjoyed it. I even learned a bit.

Programmer certifications, and particularly the Microsoft ones, get a lot of criticism. In theory they prove someone is proficient in the technology. In practice, by itself a certificate is unreliable.

Microsoft's Partner program insists that companies have staff with certifications. The body shops put their new recruits through a brief course so they can pass and then be sold on. Often, they memorised the questions and answers from internet dumps. It's disappointing to google the exam codes: apart from the Microsoft syllabus, every link is a site selling the answers.

If you're a consultant, or you're going to change jobs, experienced programmers probably have to do the exams too. If you work for a Microsoft Partner you have to do it, and HR departments filter their CVs using it. Really, the best measure of proficiency is years of experience and types of projects. That's real-world practical knowledge. Okay, I would say that, having been doing this for 25 years. But, as the company I work for needs the certificate points, and they're paying, I'm happy to do the exams.

For .net 1 we had to do exams for ASP, windows forms and web services, but most .net programmers then only worked in one or two of those- certainly never windows forms and ASP. I was actually quite impressed by the .Net 2 foundation exam, which covered a lot of basics: collections, threads, tracing, serialization, globalization, encryption. Specific projects may not involve some of it, but a good .net programmer should know almost all of it (even if you have to google the details). Unfortunately they dropped it for .Net 4, going back to technology areas (asp, web services) just like the original .net 1 exams.  Now for .net 4 "web" we have asp, wcf and data access (ado/entity framework). Broadly I agree you'd expect all web developers to know those topics, so that's not too bad.

One big problem with certification questions is that they pick on obscure APIs and ASP controls. Now, questions on the validation controls is probably a good basic requirement, but frankly the gridview events are a nightmare anyway, so memorizing them for an exam is painful. The .net 4 asp exam wasn't too bad although there was some API questions that in real life you'd just google. One of the jQuery questions annoyed me too, using an unusual API detail (I think an older syntax) when they could have used a better, clearer replacement.

The older exams had sections devoted to things nobody ever uses. On the asp .net 2 exam it was mobile controls, and web parts (cloned from Sharepoint, and never used outside Sharepoint). Fortunately there seems less of that in the .net 4 exams, although there's still some asp ajax library stuff in there, plus some dynamic data. The big problem was that jQuery and MVC have moved on since the exam was written. All the questions were about MVC 2, not 3. It's not that the questions were obsolete (there was nothing much about webform views to confuse those who use Razor), it's just that the pace of change is making exams less relevant.

The other exams had little used topics too. The WCF exam covered MSMQ, which I've never seen used in real life. The data access exam was more problematic. Basic ADO is useful, but there was still material about little used dataset features like constraints and dataRelations. There was not much on Linq2Sql, perhaps just as well, but most of the exam was Entity Framework. I know EF is used out there, but I've yet to encounter EF and it seems to be in second place to NHibernate at least for ORM data access. So I did learn something for this exam, and yes, I passed the exam as a novice with no real world experience of Entity Framework. On the other hand, as an experienced programmer I'd feel comfortable doing EF (even if I'd rather be doing NHibernate.)

Two of the exams - the WCF and the ASP Pro exam - had "testlets" with a case study and a small number of questions about the scenario. The WCF was over-elaborate, with source listings and xml, but for all that I thought it was a little more realistic and actually quite enjoyable.

To study, I used the Microsoft Training Kit books, MSDN and for the areas I was less familiar with - mostly EF and some aspects of WCF - a little practice. The WCF exam didn't have a book, so I had to use the .net 3.5 one and look up a couple of subjects (routing and discovery). I skimmed the bits I knew well (the asp/ web side generally). Generally I found you needed a bit more knowledge than was in the books, but only a few things were really obscure. Overall, studying for the exam was interesting and useful. Learning about WCF routing and discovery and EF, things I haven't actually used, did bring me more up-to-date with the .net 4 stack.

A pity Microsoft's "congratulations" email contains a link to a blog that closed 2 years ago.

posted on Saturday, December 10, 2011 10:15:55 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Friday, July 08, 2011
A brief recap of ASP.Net cache:

//in MVC use HttpRuntime.Cache or HttpContext.Cache
//in webforms Application is the original cache without expiration rules
var category = HttpRuntime.Cache["Category"as CategoryModel;
if (category == null)
{
     category = _dataAccess.Find(1);
     HttpRuntime.Cache["Category"] = category;
     // ...or...
     //monitor some files and/or other cache items
     var cd = new CacheDependency(new[] { @"C:\triggerFolder\" }, new[] { "OtherCacheItem" });
     HttpRuntime.Cache.Insert("Category1", category, 
               cd, //dependencies or null
               DateTime.Now.AddMinutes(5), //absolute expiration (or Cache.NoAbsoluteExporation)
             Cache.NoSlidingExpiration //sliding expiration (timespan)
     );
}
Mocking this in tests (especially for MVC) is a bit ugly (.Net 3.5sp1 has System.Web.Abstractions including HttpContextBase, but caching isn't included)

Now in .Net 4 we can reference System.Runtime.Caching.dll. And the really nice thing is this will run outside Asp.Net.
//get the static "default" cache. You can have multiple named caches.
ObjectCache cache = MemoryCache.Default;
//you can't store null in the cache
var category = cache["Category"as CategoryModel;
if (category == null)
{
    category = _dataAccess.Find(1);
    cache["Category"] = category;
    // ...or...
    var policy = new CacheItemPolicy();
    policy.AbsoluteExpiration = DateTime.Now.AddMinutes(5);
    //monitor some files and/or other cache items
    policy.ChangeMonitors.Add(
        new HostFileChangeMonitor(new List<string> { @"C:\triggerFolder\"})
        );
    //synchronize with another cache item
    policy.ChangeMonitors.Add(
        cache.CreateCacheEntryChangeMonitor(new [] { "OtherCacheItem"})
        );
    cache.Add("Category1", category, policy);
}
For simple caching (no change monitors) you don't even need mocking in your tests - in fact, you can test your caching with a real cache. You can move caching down into your library classes that may be called from web pages, tests, WPF apps and consoles.



posted on Friday, July 08, 2011 10:57:57 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Friday, June 10, 2011
T4 preprocessed templates are a neat way of generating text at run time, which can be deployed to machines without Visual Studio. Here's MSDN

Here's a simple template, ClassWriter.tt, which must be marked CustomTool = "TextTemplatingFilePreprocessor" in properties (not "TextTemplatingFileGenerator" which is a normal T4).
<#@ template language="C#" #>
<#@ import namespace="System.Linq" #>
<#@ parameter type="Generator.Model.Table" name="table" #>
using System;
 
namespace <#= table.Namespace #>
{
    [Serializable]
    public class <#= table.Name #>
    {
<#  foreach(var column in table.Columns.Where(c=> !c.Hidden)) { #>
        public virtual <#= column.Type #> <#= column.Name #> { get; set; }
<#    } #>
    }
}

At development time, Visual Studio generates the class in the corresponding namespace which you can then call (yeah, it generates code for generating code...).

Notice we're passing a parameter, which has to have the full namespaced name (even "string" has to be "System.String").

The generated class is partial and the parameters are property getters with a backing field. MSDN suggests that to pass in your parameters you should manually code a partial class with conventional properties or a constructor. Actually, there's an easier no-code way. Use the Session property (which is just a Dictionary<string, object>) which you can use with an Initialize() method. Like this:

//create the class generated by TextTemplatingFilePreprocessor 
var generator = new ClassWriter();
//create a session dictionary, fill it and initialize
generator.Session = new Dictionary<stringobject>();
generator.Session.Add("table", table);
generator.Initialize();
//transform!
var text = generator.TransformText();
The key things to watch out for:
  • you must create the Session dictionary (it's not initialized internally)
  • you must call Initialize() after it's populated.

You can also use System.Runtime.Remoting.Messaging.CallContext but weirdly you must still initialize the Session dictionary (Initialize checks Session first, then CallContext).
 
You may be tempted to reuse the T4 template class like this.
//don't do this
var generator = new ClassWriter();
foreach (var item in list)
{
    generator.Session = new Dictionary<stringobject>();
    generator.Session.Add("table", item);
    generator.Initialize();
    var txt = generator.TransformText();
    WriteText(item, txt);
}
The template class actually uses an internal StringBuilder called GenerationEnvironment. So each call returns everything you wrote before. You can't actually reset the StringBuilder (although you can append to it with Write overloads). So, always create the template class within the loop.



posted on Friday, June 10, 2011 1:50:55 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, May 02, 2011
Strong named assemblies cannot reference assemblies which aren't strong named.

Decompile with ildasm and recompile with ilasm using your key.

Default ILDASM and ILASM locations as of .Net 4.0

"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe" ClassLibrary.dll /out:ClassLibrary.il
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" ClassLibrary.il /res:ClassLibrary.res /dll /key:myKey.snk /out:ClassLibrary.dll

posted on Monday, May 02, 2011 2:50:05 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Saturday, April 09, 2011

My Codeplex project, Database Schema Reader, has a new version.

I needed to create a SQLite database was a replica of a parent SQL Server database. The existing project could easily read the schema, and the code generation tools could give me the table DDL and the insert SQL. It was just a matter of executing the SQL and creating the database file.

So I created another simple Windows Forms UI. The SqlWriter class needed some tweaks for SQLite support (and I added integration tests).

For fun, I decided to see if it could also support Microsoft's latest version of SQL Server CE 4.0.

SQL Server CE 4.0 is, like SQLite, an in-process database which can be XCOPY deployed. And unlike previous versions, it is easily able to run ASP.Net websites (it's the default database behind WebMatrix). And 4.0 provides the standard ADO GetSchema which my Database Schema Reader uses (3.5 throws a Not Implemented exception).

It has some of the same limitations as SQLite, such as no stored procedures (good riddance), and no output parameters. But it also has additional limitations. In SQLite you can batch together multiple SQL statements in a single line and execute them in one command. You can't in SQL Server CE.

Unlike SQLite's weakly typed and very simple data types, SQL Server CE 4.0 has most of the standard data types as SQL Server Express and full versions. Unfortunately, this was the big problem. If your table is defined with an IDENTITY primary key, you can't insert the data row with the same primary key, and subsequent foreign key relationships are broken. It's a pretty critical limitation for my scenario.

SQL Server CE 4 only supports a subset of SQL Server data types. The one data type it didn't support, which my database (and later versions of Northwind) use, is NVARCHAR(MAX). You have to use the horrible old NTEXT data type. I could have written a DDL provider that translates varchar max to ntext, but I don't think it's worth the effort.

So my SQL Server CE creation program has major limitations, compared to the SQLite version. It's built into CopyToSQLite.exe, and auto-detects if you've installed SQL Server CE, but you have to have a very simple database for it to work.

SQL Server CE 4 also doesn't support the ADO Sync framework which allows you to synchronize a disconnected database to a parent SQL Server database. Even SQL Server 2008 R2 Management Studio doesn't support it (you have to use Visual Studio 2010 with SP1).

The new FETCH /OFFSET syntax for paging, that will be SQL Server 11, is very nice though. I'm looking forward to that.

posted on Saturday, April 09, 2011 8:05:41 PM (Romance Daylight Time, UTC+02:00)  #    Comments [1]
# Thursday, April 07, 2011
VS 2010 web.config transformations are great.
Change your development web.config which looks like this:

<?xml version="1.0"?>
 
<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
 
  <system.web>
    <compilation debug="true" targetFramework="4.0">

by adding a Web.DeployTest.config file which looks like this:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
 
  <connectionStrings>
    <add name="ApplicationServices" connectionString="Data Source=StaticVoidSqlServer;Initial Catalog=Northwind;Integrated Security=SSPI;" providerName="System.Data.SqlClient"
         xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>
 
</configuration>

Only by default it's tied up with the build configurations (Debug, Release etc) and the Web deploy story.
But I don't want a web deployment package. I want:
  • a normal "Release" configuration build
  • a directory containing all the website files that I can XCopy deploy (like old school Visual Studio 2008 publish)
  • the web.config transformed with my custom "DeployTest" name.
Doing it this way means the web.DeployTest.config is not automatically nested under the web.config (and I should mark it as BuildAction=None).
But that's my requirements. So I wrote an MSBuild file.

Batch file (build.bat)

First here's a standard batch file, "build.bat", to launch it (I want MSBuild 4.0):
%systemroot%\Microsoft.Net\Framework\v4.0.30319\MSBuild.exe build.proj  /t:Release & pause

MSBuild script (build.proj)


<?xml version="1.0" encoding="utf-8" ?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Release">   <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />   <PropertyGroup>     <!-- properties that are used in this build file - referenced as $(PropertyName) -->     <ProjectName>StaticVoid</ProjectName>     <DeployConfiguration>DeployTest</DeployConfiguration>     <PublishPath>$(MSBuildProjectDirectory)\..\Publish\</PublishPath>     <OutputPath>$(PublishPath)\StaticVoid\</OutputPath>     <PackagePath>$(PublishPath)\StaticVoidPackage\</PackagePath>     <TransformInputFile>..\StaticVoid\Web.config</TransformInputFile>     <TransformFile>..\StaticVoid\Web.$(DeployConfiguration).config</TransformFile>     <TransformOutputFile>$(OutputPath)\Web.config</TransformOutputFile>     <ImageResourcesPath>..\ImageResources</ImageResourcesPath>   </PropertyGroup>   <ItemGroup>     <ImageResources Include="$(ImageResourcesPath)\*.jpg" />   </ItemGroup>   <!-- targets -->   <Target Name="PublishWebsite">     <Message Text="Publishing Website" />     <RemoveDir Directories="$(PublishPath)"/>     <!-- do a deploy -->     <MSBuild Projects="..\StaticVoid\StaticVoid.csproj" Properties="Configuration=Release;OutputPath=$(PackagePath);DeployOnBuild=true;DeployTarget=PipelinePreDeployCopyAllFilesToOneFolder;AutoParameterizationWebConfigConnectionStrings=false;_PackageTempDir=$(OutputPath)"/>   </Target>   <Target Name="Transform">     <!-- transform the web.config -->     <TransformXml Source="$(TransformInputFile)"                   Transform="$(TransformFile)"                   Destination="$(TransformOutputFile)" />   </Target>   <Target Name="BuildWebsite" DependsOnTargets="PublishWebsite">     <!-- we don't need the deployment package, we wanted the published files to copy manually -->     <RemoveDir Directories="$(PackagePath)"/>     <!-- copy the unmanaged resources -->     <Copy SourceFiles="@(ImageResources)" DestinationFolder="$(PublishPath)images" SkipUnchangedFiles="true" />   </Target>      <Target Name="Release" DependsOnTargets="BuildWebsite; Transform;">   </Target> </Project>
 

A little explanation

The MSBuild task for the website project (StaticVoid.csproj) has a whole set of extra properties set which make it do a deploy.
(Broken here to be easier to read:)
    <MSBuild Projects="..\StaticVoid\StaticVoid.csproj" Properties="Configuration=Release;
OutputPath=$(PackagePath);
DeployOnBuild=true;
DeployTarget=PipelinePreDeployCopyAllFilesToOneFolder;
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=$(OutputPath)
"/>

The regular deployment package is written to $(PackagePath). I don't care about that, so I delete it.
The actual directory and files that I wanted are written to $(OutputPath) using the _PackageTempDir property.

For the transform, note the msbuild xml must have Project ToolsVersion="4.0" and
<UsingTask TaskName="TransformXml" 
AssemblyFile
="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
And then the transformation:
    <TransformXml Source="$(TransformInputFile)"                   Transform="$(TransformFile)"                   Destination="$(TransformOutputFile)" />
with $(TransformFile) defined as
<TransformFile>..\StaticVoid\Web.$(DeployConfiguration).config</TransformFile>
Simply changing the DeployConfiguration property lets me have test and production builds which transform things nicely.

Update: the old _CopyWebApplication still works too

In Visual Studio 2008 you could use this task:
    <MSBuild Projects="..\StaticVoid\StaticVoid.csproj"
             Targets="ResolveReferences;_CopyWebApplication"
             Properties="Configuration=Release;
                         WebProjectOutputDir=$(OutputPath);
                         OutDir=$(OutputPath)\bin\" />
This still works in Visual Studio 2010's MSBuild (and you don't have a package directory to delete).
The transforms can still be done manually.


posted on Thursday, April 07, 2011 12:34:35 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, November 01, 2010

A long time ago I wrote my database schema reader. It was 2005, .Net 2.0 was just out and I was learning about the new features. .Net 2.0's ADO DbProviderFactory had a nice idea, GetSchema, to get database independent schema information. But the GetSchema collections were slightly different for each database, and they were data tables. So I wrote a simple facade over them to get proper classes.

Initially I used it to generate CRUD stored procedures, and then to generate data access code, even including NHibernate mappings. I even did some (simple) database conversions. All this was quite icky, and each time I did the code-gen a different way, but the core facade worked well.

After getting a couple of queries about the extracts I'd already posted, I thought I might as well put up the entire source code. So here's the codeplex project with the source code, and even some basic SQL code-gen.

I picked Codeplex just because it's more .Net-centric than Google code or github, plus I'm more familiar with TFS and Subversion source control. It was pretty easy. I don't expect many downloads (if any!), but maybe a couple of people can steal the relevant bits of source code.

Check out the database schema reader codeplex project.

posted on Monday, November 01, 2010 12:59:33 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Sunday, June 06, 2010

One of the really useful Visual Studio add-ins is Smart Paster. It adds a "Paste As." context menu that allows you to paste in the clipboard text as a comment, a correctly quoted string or a string builder.

smartpaster

There are versions for VS 2003, 2005 and 2008. But not 2010.

Sometimes you can just copy in the dll and addin file into the VS 2010 Addins folder (.\Documents\Visual Studio 2010\Addins) and edit the addin file (it's just xml) to say "10.0" instead of "9.0". But that doesn't work for SmartPaster - VS 2010 shows an error and insists on disabling the addin.

The VS 2008 download includes the source, so I tried to upgrade it.

It turns out the problem is when it creates the context menus it sets the CommandBarButton.FaceId property (to show an image next to the text). But in VS2010 that throws a DeprecatedException.

Ok, simple fix, but the original source is old code with a fairly high WTF-per-line ratio (well, it was written 2004, .Net 1.1). Before long I had ported it from VB.Net to C# (thanks Telerik) and rewritten large parts (mostly refactoring with Coderush). I simplified by dropping the "regionize" stuff (never use it), the VB support and the configuration form. Here's my code- you can create a new Extensibility Addin project, replace the Connect class and add the SmartPaster class- see below.

It's still a port, so certainly not as clean as something just written from scratch. And perhaps VS2010 has nicer ways of doing all these things now the code window is a WPF control - the EnvDTE objects are ugly and hard to use. Anyway, thanks to Alex Papadimoulis for the original code.

Update: download the binary and unzip into your Addins folder. 

Update 2: source and binary are also on Codeplex. (It's exactly the same as shown here.)


   1:  using System;
   2:  using System.Collections;
   3:  using EnvDTE;
   4:  using EnvDTE80;
   5:  using Extensibility;
   6:  using Microsoft.VisualStudio.CommandBars;
   7:   
   8:  namespace SmartPaster2010
   9:  {
  10:      ///<summary>The object for implementing an Add-in.</summary>
  11:      ///<seealso class='IDTExtensibility2' />
  12:      public class Connect : IDTExtensibility2
  13:      {
  14:          private readonly ArrayList _pasteAsButtons;
  15:          private readonly SmartPaster _smartPaster;
  16:          private CommandBarPopup _pasteAsPopup;
  17:   
  18:          ///<summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
  19:          public Connect()
  20:          {
  21:              _pasteAsButtons = new ArrayList();
  22:              _smartPaster = new SmartPaster();
  23:          }
  24:   
  25:          ///<summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
  26:          ///<param term='application'>Root object of the host application.</param>
  27:          ///<param term='connectMode'>Describes how the Add-in is being loaded.</param>
  28:          ///<param term='addInInst'>Object representing this Add-in.</param>
  29:          ///<seealso class='IDTExtensibility2' />
  30:          public void OnConnection(object application, ext_ConnectMode connectMode, 
object addInInst, ref Array custom)
  31:          {
  32:              _applicationObject = (DTE2)application;
  33:              _addInInstance = (AddIn)addInInst;
  34:   
  35:   
  36:              //check for the commands
  37:              bool cmdExists = false;
  38:              foreach (Command cmd in _applicationObject.Commands)
  39:              {
  40:                  if (cmd.Name.EndsWith("PasteAsComment", StringComparison.OrdinalIgnoreCase))
  41:                  {
  42:                      cmdExists = true;
  43:                      break;
  44:                  }
  45:              }
  46:   
  47:              try
  48:              {
  49:                  if (!cmdExists)
  50:                  {
  51:                      AddPasteAsCommands();
  52:                  }
  53:   
  54:                  if (connectMode == ext_ConnectMode.ext_cm_Startup && _pasteAsPopup == null)
  55:                  {
  56:                      //Add items to the Context (Right-Click) Menu
  57:                      //find the position of the &Paste command
  58:                      int position = 0;
  59:   
  60:                      CommandBar codeWindow = _applicationObject.CommandBars["Code Window"];
  61:   
  62:                      for (int i = 1; i <= codeWindow.Controls.Count; i++)
  63:                      {
  64:                          if (codeWindow.Controls[i].Caption == "&Paste")
  65:                          {
  66:                              position = i;
  67:                              break;
  68:                          }
  69:                      }
  70:   
  71:                      //add the popup menu "Paste As...", which may already be on the menu
  72:                      _pasteAsPopup = (CommandBarPopup)codeWindow.Controls.Add(
(int)MsoControlType.msoControlPopup, 1, Type.Missing, position + 1, Type.Missing);
  73:                      _pasteAsPopup.Caption = "Paste As ...";
  74:                      AddPasteAsButtons();
  75:                  }
  76:   
  77:              }
  78:              catch (Exception ex)
  79:              {
  80:                  System.Diagnostics.Debug.WriteLine(ex.Message);
  81:              }
  82:          }
  83:   
  84:          private void AddPasteAsCommands()
  85:          {
  86:              //no configure or regionize because I never use 'em
  87:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsComment", "Paste As Comment", "Pastes clipboard text as a comment.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  88:   
  89:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsString", "Paste As String", "Pastes clipboard text as a string literal.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  90:   
  91:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsStringBuilder", "Paste As StringBuilder", "Pastes clipboard text as a stringbuilder.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  92:          }
  93:   
  94:   
  95:          private void AddPasteAsButtons()
  96:          {
  97:   
  98:              //now the buttons
  99:              CommandBarButton pasteAsButton;
 100:   
 101:              //add "Comment"
 102:              pasteAsButton = AddCommandButton();
 103:              pasteAsButton.Caption = "Comment";
 104:              pasteAsButton.TooltipText = "Inserts clipboard with each line prefixed with a comment character";
 105:              pasteAsButton.Click += PasteAsComment;
 106:              _pasteAsButtons.Add(pasteAsButton);
 107:   
 108:              //add "String"
 109:              pasteAsButton = AddCommandButton();
 110:              pasteAsButton.Caption = "String";
 111:              pasteAsButton.TooltipText = "Inserts enquoted clipboard text with line breaks and other characters escaped";
 112:              pasteAsButton.Click += PasteAsString;
 113:              _pasteAsButtons.Add(pasteAsButton);
 114:   
 115:              //add "StringBuilder"
 116:              pasteAsButton = AddCommandButton();
 117:              pasteAsButton.Caption = "StringBuilder";
 118:              pasteAsButton.TooltipText = "Inserts enquoted clipboard text built up by a stringbuilder.";
 119:              pasteAsButton.Click += PasteAsStringBuilder;
 120:              _pasteAsButtons.Add(pasteAsButton);
 121:   
 122:          }
 123:   
 124:          private CommandBarButton AddCommandButton()
 125:          {
 126:              var pasteAsButton = (CommandBarButton)_pasteAsPopup.Controls.Add((int)MsoControlType.msoControlButton);
 127:              //in 2010, CommandBarButton.FaceId throws a DeprecatedException.
 128:              //pasteAsButton.FaceId = 22;
 129:              pasteAsButton.Visible = true;
 130:              return pasteAsButton;
 131:          }
 132:   
 133:          #region "PasteAs Handlers"
 134:   
 135:          ///<summary>
 136:          ///Occurs when the user clicks the PasteAsString button.
 137:          ///</summary>
 138:          ///<param name="ctrl">
 139:          ///Denotes the CommandBarButton control that initiated the event. 
 140:          ///</param>
 141:          ///<param name="cancelDefault">
 142:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 143:          ///</param>
 144:          private void PasteAsString(CommandBarButton ctrl, ref bool cancelDefault)
 145:          {
 146:              _smartPaster.PasteAsString(_applicationObject);
 147:          }
 148:   
 149:   
 150:          ///<summary>
 151:          ///Occurs when the user clicks the PasteAsComment button.
 152:          ///</summary>
 153:          ///<param name="ctrl">
 154:          ///Denotes the CommandBarButton control that initiated the event. 
 155:          ///</param>
 156:          ///<param name="cancelDefault">
 157:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 158:          ///</param>
 159:          private void PasteAsComment(CommandBarButton ctrl, ref bool cancelDefault)
 160:          {
 161:              _smartPaster.PasteAsComment(_applicationObject);
 162:          }
 163:   
 164:          ///<summary>
 165:          ///Occurs when the user clicks the PasteAsStringBuilder button.
 166:          ///</summary>
 167:          ///<param name="ctrl">
 168:          ///Denotes the CommandBarButton control that initiated the event. 
 169:          ///</param>
 170:          ///<param name="cancelDefault">
 171:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 172:          ///</param>
 173:          private void PasteAsStringBuilder(CommandBarButton ctrl, ref bool cancelDefault)
 174:          {
 175:              _smartPaster.PasteAsStringBuilder(_applicationObject);
 176:          }
 177:          #endregion
 178:   
 179:          #region "Exec"
 180:   
 181:          ///<summary>
 182:          /// Implements the Exec method of the IDTCommandTarget interface.
 183:          /// This is called when the command is invoked.
 184:          ///</summary>
 185:          ///<param name='commandName'>
 186:          ///        The name of the command to execute.
 187:          ///</param>
 188:          ///<param name='executeOption'>
 189:          ///        Describes how the command should be run.
 190:          ///</param>
 191:          ///<param name='varIn'>
 192:          ///        Parameters passed from the caller to the command handler.
 193:          ///</param>
 194:          ///<param name='varOut'>
 195:          ///        Parameters passed from the command handler to the caller.
 196:          ///</param>
 197:          ///<param name='handled'>
 198:          ///        Informs the caller if the command was handled or not.
 199:          ///</param>
 200:          ///<seealso class='Exec' />
 201:          public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
 202:          {
 203:              handled = false;
 204:              if ((executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault))
 205:              {
 206:                  handled = true;
 207:                  switch (commandName)
 208:                  {
 209:                      //case "SmartPaster.Connect.Configure":
 210:                      //    //show the config form
 211:                      //    SmartPasterForm myForm = new SmartPasterForm();
 212:                      //    myForm.ShowDialog();
 213:                      //    //since config may have changed, show/hide buttons
 214:                      //    EnableContextMenuButtons();
 215:   
 216:                      //    break;
 217:                      case "SmartPaster.Connect.PasteAsComment":
 218:                          _smartPaster.PasteAsComment(_applicationObject);
 219:                          break;
 220:                      case "SmartPaster.Connect.PasteAsString":
 221:                          _smartPaster.PasteAsString(_applicationObject);
 222:                          break;
 223:                      case "SmartPaster.Connect.PasteAsStringBuilder":
 224:                          _smartPaster.PasteAsStringBuilder(_applicationObject);
 225:                          break;
 226:                      //case "SmartPaster.Connect.PasteAsRegion":
 227:                      //    _smartPaster.PasteAsRegion(_applicationObject);
 228:                      //    break;
 229:                      default:
 230:                          handled = false;
 231:                          break;
 232:                  }
 233:              }
 234:          }
 235:          #endregion
 236:   
 237:          #region Standard Template Stuff
 238:          ///<summary>
 239:          ///Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.
 240:          ///</summary>
 241:          ///<param name="disconnectMode">The disconnect mode.</param>
 242:          ///<param name="custom">The custom.</param>
 243:          ///<seealso class="IDTExtensibility2"/>
 244:          public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
 245:          {
 246:              if (_pasteAsPopup != null && 
 247:                  (disconnectMode == ext_DisconnectMode.ext_dm_UserClosed || disconnectMode == ext_DisconnectMode.ext_dm_HostShutdown))
 248:              {
 249:                  _pasteAsPopup.Delete(true);
 250:              }
 251:          }
 252:   
 253:          ///<summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
 254:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 255:          ///<seealso class='IDTExtensibility2' />        
 256:          public void OnAddInsUpdate(ref Array custom)
 257:          {
 258:          }
 259:   
 260:          ///<summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
 261:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 262:          ///<seealso class='IDTExtensibility2' />
 263:          public void OnStartupComplete(ref Array custom)
 264:          {
 265:          }
 266:   
 267:          ///<summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
 268:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 269:          ///<seealso class='IDTExtensibility2' />
 270:          public void OnBeginShutdown(ref Array custom)
 271:          {
 272:          }
 273:   
 274:          private DTE2 _applicationObject;
 275:          private AddIn _addInInstance;
 276:          #endregion
 277:   
 278:          ///<summary>
 279:          ///Queries the status.
 280:          ///</summary>
 281:          ///<param name="commandName">Name of the command.</param>
 282:          ///<param name="neededText">The needed text.</param>
 283:          ///<param name="statusOption">The status option.</param>
 284:          ///<param name="commandText">The command text.</param>
 285:          public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus statusOption, ref object commandText)
 286:          {
 287:              if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
 288:              {
 289:                  if (commandName.StartsWith("SmartPaster.Connect"))
 290:                  {
 291:                      if (((_applicationObject.ActiveDocument != null)) && ((_applicationObject.ActiveDocument.Object("TextDocument") != null)))
 292:                      {
 293:                          statusOption = vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported;
 294:                      }
 295:                      else
 296:                      {
 297:                          statusOption = vsCommandStatus.vsCommandStatusSupported;
 298:                      }
 299:                  }
 300:                  else
 301:                  {
 302:                      statusOption = vsCommandStatus.vsCommandStatusUnsupported;
 303:                  }
 304:              }
 305:          }
 306:      }
 307:  }

Here's the SmartPaster class.

   1:  using System;
   2:  using System.IO;
   3:  using System.Text;
   4:  using System.Windows.Forms; //clipboard
   5:  using EnvDTE;
   6:  using EnvDTE80;
   7:   
   8:  namespace SmartPaster2010
   9:  {
  10:      /// <summary>
  11:      /// Class responsible for doing the pasting/manipulating of clipdata.
  12:      /// </summary>
  13:      internal sealed class SmartPaster
  14:      {
  15:          /// <summary>
  16:          ///  Convient property to retrieve the clipboard text from the clipboard
  17:          /// </summary>
  18:          private static string ClipboardText
  19:          {
  20:              get
  21:              {
  22:                  IDataObject iData = Clipboard.GetDataObject();
  23:                  if (iData.GetDataPresent(DataFormats.Text))
  24:                      return Convert.ToString(iData.GetData(DataFormats.Text));
  25:                  return string.Empty;
  26:              }
  27:          }
  28:   
  29:          #region "Stringinize"
  30:          /// <summary>
  31:          /// Stringinizes text passed to it for use in C#
  32:          /// </summary>
  33:          /// <param name="txt">Text to be Stringinized</param>
  34:          /// <returns>C# Stringinized text</returns>
  35:          private static string StringinizeInCs(string txt)
  36:          {
  37:              //c# quote character -- really just a "
  38:              const string qChr = "\"";
  39:   
  40:              //sb to work with
  41:              var sb = new StringBuilder(txt);
  42:   
  43:              //escape appropriately
  44:              //escape the quotes with ""
  45:              sb.Replace(qChr, qChr + qChr);
  46:   
  47:              //insert " at beginning and end
  48:              sb.Insert(0, "@" + qChr);
  49:              sb.Append(qChr);
  50:              return sb.ToString();
  51:          }
  52:          #endregion
  53:   
  54:          #region "Commentize"
  55:          /// <summary>
  56:          /// Commentizes text passed to it for use in C#
  57:          /// </summary>
  58:          /// <param name="txt">Text to be Stringinized</param>
  59:          /// <returns>C# Commentized text</returns>
  60:          private static string CommentizeInCs(string txt)
  61:          {
  62:              const string cmtChar = "//";
  63:   
  64:              var sb = new StringBuilder(txt.Length);
  65:   
  66:              //process the passed string (txt), one line at a time
  67:              //the original was horrible WTF code
  68:              using (var reader = new StringReader(txt))
  69:              {
  70:                  string line;
  71:                  while ((line = reader.ReadLine()) != null)
  72:                  {
  73:                      sb.AppendLine(cmtChar + line);
  74:                  }
  75:              }
  76:   
  77:              return sb.ToString();
  78:          }
  79:          #endregion
  80:   
  81:          #region "Stringbuilderize"
  82:          private static string StringbuilderizeInCs(string txt, string sbName)
  83:          {
  84:              //c# quote character -- really just a "
  85:              const string qChr = "\"";
  86:   
  87:              //sb to work with
  88:              var sb = new StringBuilder(txt);
  89:   
  90:              //escape \,", and {}
  91:              sb.Replace(qChr, qChr + qChr);
  92:   
  93:              //process the passed string (txt), one line at a time
  94:   
  95:              //dump the stringbuilder into a temp string
  96:              string fullString = sb.ToString();
  97:              sb.Clear(); //lovely .net 4 - sb.Remove(0, sb.Length);
  98:   
  99:              //the original was horrible WTF code
 100:              using (var reader = new StringReader(fullString))
 101:              {
 102:                  string line;
 103:                  while ((line = reader.ReadLine()) != null)
 104:                  {
 105:                      sb.Append(sbName + ".AppendLine(");
 106:                      sb.Append("@" + qChr);
 107:                      sb.Append(line.Replace("\t", "\\t"));
 108:                      sb.AppendLine(qChr + ");");
 109:                  }
 110:              }
 111:   
 112:              //TODO: Better '@"" + ' replacement to not cover inside strings
 113:              sb.Replace("@" + qChr + qChr + " + ", "");
 114:   
 115:              //add the dec statement
 116:              sb.Insert(0, "StringBuilder " + sbName + " = new StringBuilder(" + txt.Length + ");" + Environment.NewLine);
 117:   
 118:              //and return
 119:              return sb.ToString();
 120:          }
 121:          #endregion
 122:   
 123:          /// <summary>
 124:          /// Inserts text at current cursor location in the application
 125:          /// </summary>
 126:          /// <param name="application">application with activewindow</param>
 127:          /// <param name="text">text to insert</param>
 128:          private static void Paste(DTE2 application, string text)
 129:          {
 130:              //get the text document
 131:              var txt = (TextDocument)application.ActiveDocument.Object("TextDocument");
 132:   
 133:              //get an edit point
 134:              EditPoint ep = txt.Selection.ActivePoint.CreateEditPoint();
 135:   
 136:              //get a start point
 137:              EditPoint sp = txt.Selection.ActivePoint.CreateEditPoint();
 138:   
 139:              //open the undo context
 140:              bool isOpen = application.UndoContext.IsOpen;
 141:              if (!isOpen)
 142:                  application.UndoContext.Open("SmartPaster");
 143:   
 144:              //clear the selection
 145:              if (!txt.Selection.IsEmpty)
 146:                  txt.Selection.Delete();
 147:   
 148:              //insert the text
 149:              //ep.Insert(Indent(text, ep.LineCharOffset))
 150:              ep.Insert(text);
 151:   
 152:              //smart format
 153:              sp.SmartFormat(ep);
 154:   
 155:              //close the context
 156:              if (!isOpen)
 157:                  application.UndoContext.Close();
 158:          }
 159:   
 160:          #region "Paste As ..."
 161:   
 162:          /// <summary>
 163:          /// Public method to paste and format clipboard text as string the cursor 
 164:          /// location for the configured or active window's langage .
 165:          /// </summary>
 166:          /// <param name="application">application to insert</param>
 167:          public void PasteAsString(DTE2 application)
 168:          {
 169:              Paste(application, StringinizeInCs(ClipboardText));
 170:          }
 171:   
 172:          /// <summary>
 173:          /// Public method to paste and format clipboard text as comment the cursor 
 174:          /// location for the configured or active window's langage .
 175:          /// </summary>
 176:          /// <param name="application">application to insert</param>
 177:          public void PasteAsComment(DTE2 application)
 178:          {
 179:              Paste(application, CommentizeInCs(ClipboardText));
 180:          }
 181:   
 182:   
 183:          /// <summary>
 184:          /// Public method to paste format clipboard text into a specified region
 185:          /// </summary>
 186:          /// <param name="application">application to insert</param>
 187:          public void PasteAsRegion(DTE2 application)
 188:          {
 189:              //get the region name
 190:              const string region = "myRegion";
 191:   
 192:              //it's so simple, we really don't need a function
 193:              string csRegionized = "#region " + region + Environment.NewLine + ClipboardText + Environment.NewLine + "#endregion";
 194:   
 195:              //and paste
 196:              Paste(application, csRegionized);
 197:          }
 198:   
 199:          /// <summary>
 200:          /// Public method to paste and format clipboard text as stringbuilder the cursor 
 201:          /// location for the configured or active window's langage .
 202:          /// </summary>
 203:          /// <param name="application">application to insert</param>
 204:          public void PasteAsStringBuilder(DTE2 application)
 205:          {
 206:              const string stringbuilder = "sb";
 207:              Paste(application, StringbuilderizeInCs(ClipboardText, stringbuilder));
 208:          }
 209:   
 210:          #endregion
 211:      }
 212:  }
posted on Sunday, June 06, 2010 5:30:02 PM (Romance Daylight Time, UTC+02:00)  #    Comments [3]
# Friday, April 02, 2010
"Covariance" = specified or MORE derived type

So upcasting to a more basic class in polymorphism is covariant.         
object
s = new string('a', 3);


But you couldn't do it for IEnumerables, even though it seemed safe. Well, now in .net 4 you can.

IList<string> stringList = new List<string>(); //will not compile in .net 1-3, but does in net 4 IEnumerable<object> objectList = stringList;
Trivia: since .net 1, arrays were covariant.
object[] data = new string[3];
data[0] = "s";
data[0] = 1; //no compiler error- just a runtime ArrayTypeMismatchException
Now IEnumerable<T> and IQueryable<T> are covariant.

IList<Cat> cats = new List<Cat>();
//you can cast to IEnumerable (or IQueryable)
IEnumerable<Animal> pets = cats;
//but you cannot cast to the read-write classes
//IList<Animal> animals = cats; //compiler error
Covariant interfaces must be read-only (and not value types).

Contravariance = specified or LESS derived type
 
var cats = new List<Cat>();
//predicate is contravariant
cats.Find(HasFur); //private static bool HasFur(Animal animal)
//so is IComparable
cats.Sort(SortByName); //private static int SortByName(Animal x, Animal y)

posted on Friday, April 02, 2010 11:10:48 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]