Tuesday, December 21, 2010

Aspects and AOP

Aspects and AOP

Aspect Oriented Programming (AOP) is a paradigm that separates supporting functionality from business logic. It is useful for addressing Cross Cutting Concerns.

AOP is generally implemented by defining code that executes before, around, and/or after a point in business code. For example, you could define some code to execute (which is known as advice) every time a method is accessed. This code could log access, check permissions, pretty much anything you want it to do.

In the .NET world, AOP is accomplished via Attribute Programming, using a 3rd party assembly for functionality. The biggest example I found in the community was PostSharp, which I used to implement security checks in sample code. PostSharp will intercept calls to a method, property, class, etc and perform the advice as needed. So a pseudo-example might be:

<SecurityAspect(RoleEnum.Manager)> _
Public Sub GiveRaiseTo(Employee as Employee)

So what's with the SecurityAspect attribute?

Well, it would look something like this:

Public Class SecurityAspect
    Inherits MethodInterceptionAspect

Private _role As RoleEnum

    Public Sub New(Role as RoleEnum)
        _role = Role
    End Sub


 

    Public Overrides Sub OnInvoke(args as MethodInterceptionArgs)
        If Not User.Role.Equals(_role) Then
            Throw New InvalidAccessException("You are not allowed here!")

        Else
            MyBase.Invoke(args)

End If
End Sub

End Class

So what is happening here? Whenever any consuming code calls GiveRaiseTo(), the Aspect is going to execute and test the User Role against the Role that was passed in, and throw an exception if they don't match. That means, in this instance, only Managers can access the GiveRaiseTo() method.

Read more on PostSharp here.

Thursday, November 11, 2010

DataGridView WordWrap

To get cell text to word wrap is easy once you know the combination of properties to set:

DataGridView > AutoSizeRowsMode = AllCells
DataGridView > Columns > *Your Column* > Default Cell Style > Wrap Mode = True
DataGridView > Columns > *Your Column* > AutoSizeMode = DisplayedCells

Friday, October 29, 2010

Using UsageMode to Determine Design Time or Run Time

During design time editing of forms with custom user controls on them, strange errors began popping up relating to StructureMap. The issue is well documented on the internet, and is caused by controls firing the constructor when their parent form is opened in design mode. (A great explanation is provided here.) Since we had data access code in the constructor of the custom control which needed StructureMap initialized, but the Bootstrapper had not yet been called, the code would error out.

In order to work around this issue, I began testing for System.ComponentModel.UsageMode.Runtime(or DesignTime).

Thursday, September 30, 2010

Visual Studio Keyboard Shortcuts

I will be adding to this as I find them. Keep in mind these can be configured, and may not be the same on all systems...

Expand Region Under Cursor: CTL + M, CTL + O
Toggle Region Under Cursor: CTL + M, CTL + M

Monday, September 13, 2010

BinaryFormatter & “Invalid number of fixups”

While working with clsClaim (which inherits from clsGenericCollection(Of clsNCPDP)) on the Billing Screen, I was receiving the following error when deserializing:

The ObjectManager found an invalid number of fixups. This This usually indicates a problem in the Formatter.

The issue here is that, when binary serialization occurred, the object type was saved with the binary data. In this case, the object type was Classes.clsGenericCollection`1[Classes.clsNCPDP]. When deserialization occurs, the object type is attempted instantiated. Because we changed namespaces, this object is no longer valid.

The solution was to create a SerializationBinder object that dictates which type to bind to. The interesting code is:

Public
Overrides
Function BindToType(ByVal assemblyName As
String, ByVal typeName As
String) As System.Type

If typeName.Contains("Classes.clsNCPDP") Then

typeName = typeName.Replace("Classes.clsNCPDP", "BioWoRx.BusinessLogic.clsNCPDP")


 

End
If


 

If typeName.Contains("Classes.clsGenericCollection") Then

typeName = typeName.Replace("Classes.clsGenericCollection", "BioWoRx.BusinessLogic.clsGenericCollection")


 

End
If


 

Return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName))


 

End
Function


 

This code is used in the application like so:


 

theFormatter.Binder = New GenericCollectionOfNcpdpBindingRedirect()

claim_fields = CType(theFormatter.Deserialize(theStream), clsGenericCollection(Of clsNCPDP))

Tuesday, September 7, 2010

Git and CURL

Whenever I first set up a .git for a machine, I always get this error:

"Error setting certificate verify locations:"...

The solution was found here - and thank you very much! It is basically issuing the command:

git config --system http.sslcainfo \bin/curl-ca-bundle.crt

Wednesday, August 25, 2010

ASP.NET Code Delimiters

I have been looking for these forever...

<% is the ASP.NET Page Render Function initial delimiter
<%# is the databinding ASP.NET Page Render Function initial delimiter.
<%= is the Response.Write ASP.NET Page Render Function initial delimiter
<%-- is the Comment ASP.NET Page Render Function initial delimiter

Credit where credit is due
.

Tuesday, August 24, 2010

EF and the MultipleActiveResultSets Property

Today I converted our EF models to use Configurator (custom configuration module) instead of app.config. The code looked something like this (keep in mind, this is StructureMap initialization):

config.For(Of Enterprise.eJournal.Model.IsADataContext)() _

.Use(Of Enterprise.eJournal.ContextContainer)() _

.Ctor(Of
String)("connectionString") _

.Is(String.Format("metadata=res://*/Enterprise.eJournal.Model.ContextContainer.csdl|res://*/Enterprise.eJournal.Model.ContextContainer.ssdl|res://*/Enterprise.eJournal.Model.ContextContainer.msl;provider=System.Data.SqlClient;provider connection string=""{0}"";", Configurator.Instance.BioWoRxConnectionString))


 

The BioWoRxConnectionString setting is a standard SQL Server connection string. Still, I was receiving this error:

"There is already an open DataReader associated with the Command which must be closed first."

Turns out, the MultipleActiveResultSets property has to be included in the connection string for EF to work properly, so I did this:

config.For(Of Enterprise.eJournal.Model.IsADataContext)() _

.Use(Of Enterprise.eJournal.ContextContainer)() _

.Ctor(Of
String)("connectionString") _

.Is(String.Format("metadata=res://*/Enterprise.eJournal.Model.ContextContainer.csdl|res://*/Enterprise.eJournal.Model.ContextContainer.ssdl|res://*/Enterprise.eJournal.Model.ContextContainer.msl;provider=System.Data.SqlClient;provider connection string=""{0};MultipleActiveResultSets=True"";", Configurator.Instance.BioWoRxConnectionString))


 

There you go, problem solved!

Thursday, August 19, 2010

Reconnecting to Team Foundation

For the past few days, Team Foundation source control was acting strangely – as in, when I edited a code file, it did not want to automatically check out the file, but instead asked me whether to edit in memory or make the file writable on the disk. I would then go through Team Explorer and check the file out directly from Source Control view, and make my changes, then attempt to check in. The Check In button was enabled, but I could not click it. So what the heck is going on here?

Well, at one point during the week, IIS on the Team server had been restarted. I had the project open, and was coding at that time, so Team flipped to Off-Line mode, and I continued on my way. But when the server came back online, Team did not automatically flip to On-Line mode. So, to correct it, select the Solution in Solution Explorer and go File > Source Control > Go Online.

That fixed it for me.

Friday, August 6, 2010

Unit Testing and Incomplete Tasks

OK, I'm starting to write failing unit tests for tasks I haven't completed in my project. It seems to be the only way I can keep track of what has to be done, because I can't be bothered to pick up a pen and paper...

Monday, July 26, 2010

Entity Framework and Custom Stored Procedures

*WARNING*
I didn't want to put this at the end of the post in case you don't get that far, but just be aware...
*If you udpate from the database "for any reason", EF will overwrite your custom CommandText.*
Let that be a lesson to you... Now...

So our database has plenty of valuable business logic in the stored procedures - things that have to happen when, say, we want to save Customer information (yes, I know it shouldn't be there, but it is pre-existing, so what can I do?!?). Entity Framework provided functionality to facilitate this, in the form of Modification Functions. This post isn't about describing what they are, but how I got them to work with our current stored procedures...

OK - so the first thing of note was that many of the operations on business objects for the role we are coding to don't have associated CRUD. For example, if we want to Update (the U in CRUD), it doesn't mean that we should be able to Create (the C), Read (the R), or Delete (the D). But, because of the stored procedure support in EF, if you define one Modification Function for C_UD, you have to provide them all (notice this doesn't apply to Read).

So Modification Function UpdateFunction is set to a real stored procedure:
<UpdateFunction FunctionName="Customer_Update">

while the others are not:
<DeleteFunction FunctionName="usp_NoAction"/>
<InsertFunction FunctionName="usp_NoAction"/>

Alright, so that was pretty easy, even though that had to be hacked together by hand. Now when role calls Customer Insert, it will call usp_NoAction, no harm done.

The next instance was tricky. Many of the stored procedures use ModifiedOn as a sort of concurrency check. Pass in the ModifiedOn that you received, and if no one has updated the record since you grabbed it, the update will continue. In that instance, the ModifiedOn parameter will act as an output parameter, returning the new ModifiedOn date. In addition, the stored procedure may return a RowsAffected integer, telling how many rows were affected. For Insert operations, the stored procedure may return the ID of the newly created row.

So, EF doesn't have the capacity to work with InOut parameter types, even though that is what will be automatically generated in your function import statement. So you have two options: In or Out in the SSDL section of the edmx file.

In is obvious, and works as you would expect. It specifies that the parameter is inbound into the stored procedure. It looks like this (we're leaving out the rest of the definition for the time being):
<Parameter Name="ModifiedOn" Type="datetime" Mode="In"/>

Even though ModifiedOn is an InOut in the procedure, it is declared as an In in the SSDL. We can now pass the value in to it.

OK, we're good so far...

Next is the RowsAffected parameter. It will be declared as an Out, because that is exactly what it is. EF doesn't expect you to pass a value into that mapping, it is an output only.
So now we move on to the CSDL section of the edmx file. Our stored procedure is ready for use as a Modification Function, so we give it a try. Maybe it works, but it doesn't pass the ModifiedOn back to us, because ModifiedOn only works as an In parameter. So our object is now out of date. What do we do?

Here's where CommandText comes in...

CommandText is an element that can be used in the Function element of the SSDL section to pass custom SQL to the database. So we can declare something like this:
<CommandText>
DECLARE @rows_affected int;
DECLARE @modified_on DATETIME;
SET @modified_on = @Modified;
EXEC pfc_Customer_Update
@CustomerID = @CustomerID
, @Modified = @modified_on OUTPUT
, @Adjustment = @Adjustment
, @RowsAffected = @rows_affected OUTPUT;
SELECT @modified_on AS Modified;
</CommandText>

So what does this do for us?

It declares two variables that we are going to use as InOut (or Out) in the stored procedure. Then we set the @modified_on variable to the value of the passed in ModifiedOn property. We use that to execute the stored procedure, then we select the @modified_on variable out after the fact because it will reflect the new ModifiedOn variable. We SELECT that out into a column called Modified.
In the CSDL section, we change the Modification Function to look like this:
<UpdateFunction FunctionName="Customer_Update" >
<ScalarProperty Name="ID" ParameterName="ReferralRx" Version="Current" />
...
<ResultBinding Name="ModifiedOn" ColumnName="Modified"/>
</UpdateFunction>

So that pretty much gets us where we want to be. What that does is use the Modified column (which was set from the @modified_on variable) from the stored procedure (which is now our custom SQL thanks to the CommandText element) and bind it to the ModifiedOn property of the Customer, so that is now updated when the object is saved.

The ID scenario can be solved in pretty much the same way. One note though, your Modification Function declaration can have a RowsAffectedParameter property - which is why I mentioned that above. That property can be set to the @RowsAffected parameter of the stored procedure. Easy. However, I did have an issue where the @RowsAffected parameter was returning 0, even though the row *was* being updated. I assume this was caused by some of the logic in the stored procedure. When the 0 is returned, EF throws an exception. I fixed it by removing the RowsAffectedParameter property from the Modification Function declaration.

Friday, July 23, 2010

Expression vs. Func for Data Selection

So it happened like this...

I built a DataContext object and wanted a generic selection function called Get that would allow me to pass in the type-safe query arguments like this:

'// Pseudo-code
Get( Patient Where Patient.ID == ? )

That's cool, right? Because then, any Entity can go in the "Patient" spot, and that Entity's Property could go in the "Patient.ID" spot, and any reasonable value could go in the "?" spot.

So first, because I want to define the Entity that the get is for, I have to use a type argument. So the Get signature becomes:

'// Pseudo-code
Get(Of EntityT)(Where EntityT.Property == ?)

Right. Now, how to be able to select any Property on the EntityT we've designated? Like this:

'// Pseudo-code, but becoming more real
Get(Of EntityT)(Func(Of EntityT,Boolean) == ?)
* Func is a delegate that accepts an argument of EntityT type, and returns a boolean.

So far, so good. So, the nice thing is, Func(Of EntityT,Boolean) accepts the query value automatically, because of the nature of it wanting to return a boolean. So this definition will suffice:

'// Real code, but not complete yet...
Get(Of EntityT)(Func(Of EntityT,Boolean))

because it can be used like this:

Get(Of Patient)(Func(x) x.PatientID.Equals(0))

Get method knows because of its signature that the Func will accept an EntityT (in this case, Patient) and return a boolean. So all we do is use x as a placeholder for the EntityT object. Lambda.

See what happened? Because the Get method accepts a Func that returns a Boolean, the statement x.PatientID.Equals(0) will return the True/False-ness of the comparison.

Alright, great, we're done, right? Well, not exactly...

When using this in the DataContext, I would do something like:

Function Get(Of EntityT)(Criteria as Func(Of EntityT,Boolean)) as IQueryable(Of EntityT)
Return CreateQuery("[" + EntityNam + "]").Where(Criteria).AsQueryable

Something is wrong here already. Why do I have to cast what should be an IQueryable(Of EntityT) to an IQueryable? Because I missed something very important.

When I passed in the Criteria Func to the Where clause, Where selected the best overload based on the Criteria argument type. What was the best overload? IEnumerable. The IEnumerable Where accepts a Func. So what's wrong with that?

Well, instead of getting the delayed execution of LINQ by being able to build up my query string, the call to IEnumerable Where was causing the query to execute *before* the Where was applied, meaning that the *entire table data* was being returned in order to satisfy that one Where clause.

I noticed the side effect in performance of the form (gee, ya think?!? I'm lazy loading data from like 7 tables for this form). That means ALL THE DATA from each of the 7 tables was being brought into memory, so a filter could be applied to each of them. Wow. Bummer.

After some thought, I realized that I wanted to control the selection of the overloaded where by providing the correct argument type to Where. I want the IQueryable Where. Looking at the documentation on this, IQueryable Where signature is:

Where(Of TSource)(Expression(Of Func(Of TSource, Boolean))

Notice that?!? It wants an *Expression* of a Func. Of course! Why did I not realize that already?!?

The whole point of IQueryable is to get that delayed execution, so you can build up your query before delivering it to the store. Because I was not providing an Expression, I was instructing the query to execute, then apply my Func parameter.

So my final Get signature ended up being:

Get(Of EntityT)(Expression(Of Func(Of EntityT,Bool))

It is exactly the same as the IQueryable Where signature, took me long enough to get there!

By the way, the form load time is just a *fraction* of what it was! Beautiful!

Friday, July 16, 2010

State Pattern

A really helpful article on the State Pattern - those Los Techies really have it under control.

DataGridView Adds Extra Columns

The AutoGenerateColumns property is not available on the design surface of VS, only in code. What a pain. So, to prevent columns from displaying:

Public Sub New() '// or whatever
my_data_grid_view.AutogenerateColumns = False

End Sub

Thursday, July 8, 2010

RhinoMocks and InternalsVisibleTo

Well, when I first found this tip, I thought the name DynamicProxyGenAssembly2 was, you know, like a placeholder for my assembly name. But no, it actually means exactly what it says. This is the full explanation.

MenuStrip - Items Not Visible

Yes - YES. This is what I was looking for. When I added a MenuStrip to an MDI application, none of the menu items were showing up. That's because I had to set the AllowMerge property on the MenuStrip to False.

Granted, I don't know what that means yet, but I know how to solve the issue now...

Thursday, June 17, 2010

Expanding Config Files with Rake & Albacore

It took me forever to find this, but I finally did. Here is a great post which either directly states or points to how to use Rake and Albacore to build config files as part of the build process.

I had been searching for this information for a long time...

Wednesday, June 16, 2010

RhinoMocks - Method Signature & Null Reference Exception

OK, so it took me a while to figure this one out...

I would compile my project, would work fine, but when I ran the test, I would get "Object reference not set to the instance of an object" exception. I thought for the longest time it was my Mock object, but it wasn't.

The cause was because my method signature that I was stubbing was different than what I defined in my stub. So reflection couldn't find the method I was telling Rhino to stub.

Another interesting tidbit, the way I arranged my Given statements, I set an expectation on a Repository that method Get(int) would be called, then later in the Given I set the value of int. The mock was not prepared to handle "any" int, but only the one I set with the Expectation. Once I moved the assignment of int above the Mock Repository, it worked as expected.

As a side note, the order of the Given statements in the Feature determine the order the methods in the Spec are run. Interesting...

Monday, June 14, 2010

StructureMap Constructor Arguments

Here is how to pass dynamic StructureMap constructor arguments.

Sunday, June 13, 2010

PowerShell "dir" Command

I don't know why it took me so long to find the syntax for this command, but finally did:

dir -recurse -filter **

Thanks to this post for making it clear.

Thursday, June 10, 2010

YAML for Configuration Files

This post is a little introduction to using YAML to generate config files.

VB.NET and Lambdas

Man, VB.NET can be such a bummer...

Here's a description of how to get shorthand notation to work in VB.NET. It's no C#, but at least it's a workaround.

Wednesday, June 9, 2010

Launchy and VS

OH. MY. GAWD. These are exciting times. I've just discovered, thanks to the comments in this post, that I can have Launchy index *.sln files in a directory, then launch the file by using <Something_In_Solution_Name>, Enter.

That is cool.

Cucumber Tags and CI

This is a cool post showing how to use the tagging feature of Cucumber to prevent certain tests from running. It also describes how to make Rake ignore those features and scenarios.

Rake Continuous Build

Here is a great *quick* tutorial for using Rake for building apps, and some other handy tips.

Tuesday, June 8, 2010

Vim and Windows - Vindows

Here is a blog post with some great information regarding configuring VIM for a Windows installation. Saved me a bunch of time trying to figure out where VIM is looking for the configuration file.

BackgroundWorker and Exception Handling

Working on a new product that had an Exception that is not being caught by the UnhandledExceptionHandler. The cause seems to be stated in this blog post. The solution is:

1. Do not put any Try-Catch logic in the background process
2. Test the RunWorkerCompletedEventArgs Error property, which is the Exception from the process.

Thursday, June 3, 2010

Ruby Installation on Windows

Thanks to this post, I realized that prompts started from Launchy did not respect the Path update when Ruby was installed. Restarting Launchy made the path change stick.

Tuesday, June 1, 2010

Thursday, May 27, 2010

Web Server Return Codes

Well, I don't claim to be a web guy, but I feel like I should have known the web server return codes long ago...

Monday, May 24, 2010

COM Interop

Here is a discussion on COM Interop - I don't know if this applies to my ESRI ArcObjects issues, but it clears up some issues anyway...

Revenge of the ArcObjects

OK, so the .NET assemblies that make up the ArcObjects library are just a collection of COM wrappers - so Reflector doesn't help you figure out what a method does, or how its arguments fit into the scheme of things.

Thank goodness for ESRI's lack of documentation!

Thursday, May 20, 2010

Assembly Binding

Oh, thank you Scott Hanselman, for showing me how to troubleshoot my assembly binding issues! You have saved me hours and hours of frustration!

Here's a summary:

Before Binding - what will assembly bind to? Use Reflector
During Binding - what did I try to bind to? Use Fusion Log Viewer
After Binding - What did you actually load? Use Process Explorer.

Thank you, thank you, thank you...

Tuesday, May 11, 2010

nServiceBus, ArcObjects

Trying to get nServiceBus working in the context of an ArcObjects application, in order to send changes to the Situs layer to the AS/400 for processing.

I changed the namespace of the project, and had to re-register it using the command:

regasm /tlb:LandfileWorkspaceExtension.tlb LandfileWorkspaceExtension.dll /codebase

That made it work again...

Some other notes to think about...

Even though the main Extension assembly (LandfileWorkspaceExtension.dll) can be debugged based on the information provided during regasm registration, all the dependent assemblies had to be copied into the ArcMap directory. Otherwise, the Extension failed Initialization without any error message because ArcMap could not resolve the dependencies.

nServiceBus has to be configured in ArcMap.exe.config because ArcMap is the host process for the nServiceBus assemblies. Forgetting the config file caused "No Endpoint Configured" errors.

See the Installation section of this page for installing NServiceBus.Host.exe as a Windows Service: http://www.nservicebus.com/GenericHost.aspx

Monday, April 26, 2010

Connecting to SDE Personal Edition

Thanks a bunch to this post, I was able to connect to SDE Personal:

Thursday, April 22, 2010

Assembly Version vs. File Version

Oh, so that's how it works...

http://support.microsoft.com/kb/556041

The Assembly version should remain fixed during development so that assemblies can easily overwritten, without changing any references.

The File version is never used by the .NET framework, but is for file system version display.

So why can't I add the * to my File version then?

Writing Patches for MSI

I haven't actually used this yet, but I have searched for it before, so I'm putting it here so I remember where it is. It describes how to write patches for applications installed with MSI.

Tuesday, April 20, 2010

ESRI ArcObjects IObjectInspector

Working on an Object Inspector extension for Landfile project. Having some difficulty, it doesn't appear that it is easy to handle the editing of features through a custom extension.

Here are the steps to implement.

01. Implement IObjectInspector, IClassExtension, and IFeatureClassExtension in your custom inspector form.

02. In the code for the Inspect method, use the user32 SetParent and ShowWindow methods to set the parent of the default inspector to your custom window and show the window. This was a little tricky because of the syntax of SetParent, and the architecture of ArcMap. The correct usage (finally!) was:

SetParent(DefaultObjectInspector,CustomObjectInspector)
ShowWindow(CustomObjectInspector)

03. Build the project and register the type with regasm.

04. Set the Class Extension using ArcObjects IFeatureWorkspaceSchemaEdit AlterClassExtensionCLSID method, passing the CLSID of your custom extension.

05.

Friday, April 16, 2010

Outlook Tagging

I haven't installed it yet, but tagging in Outlook seems promising - I hope it delivers!

http://www.taglocity.com/index.html

Monday, April 12, 2010

DB2 Frustration

During development of the Landfile application, I had the need to call a stored procedure in a DB2 (AS/400) environment based on multiple points selected in an ArcMap interface. If a single point was selected, the procedure would run fine. However, if multiple points were selected, the first execution would complete, but the process would hang thereafter.

In working with the AS/400 guru, we came to discover that the stored procedure was not exiting after the first call, which was causing the process to hang. It also attempted to modify the Library List that was loaded by the first iteration of the process. Once he killed that stored procedure after every call, it worked as expected. Not terribly performant, but at least working.

Friday, April 9, 2010

MSI and File Versions

Got stung again by File Versioning in MSI. If the file version in the installer isn't greater than the file version on the server, the file will not be reinstalled. I thought the File Version property in the project properties set this version for files, but apparently it does not - so my file changes are not being deployed, even though I am continuously incrementing the File Version property.

So what I found was the REINSTALLMODE option for msiexec. Installing the MSI like this did the trick:

msiexec /i <MsiName> REINSTALLMODE=a

Here is the list of REINSTALLMODE options.

Wednesday, April 7, 2010

ASP in ASP.NET Context

An interesting project for those of us who still have to muck with Classic ASP, this compiles Classic ASP to ASP.NET.

Monday, March 15, 2010

Landfile Transfer Notes

Although we are not quite finished with the CMS project, we are now double-dipping into the Landfile Transfer project. I wanted to make some notes in case I forget what I've done...

First time I attempted to build the existing Phase I Landfile project, there were cwbx.dll reference errors, "The system cannot find the file specified." I had originally referenced the cwbx.dll in place in the IBM folder in Program Files, but now I want to put the dll in the lib directory of the project so I stop losing it. I copied the dll there and made a reference in the project, but got the error that it wasn't a valid dll. So I had to make an Interop for it using tblimp from the Visual Studio command prompt. I renamed the original dll cwbx.org.dll so that I could call the interop assembly cwbx.dll and not have to change the Imports statements in the code before I even started coding again. The build then succeeded.

Friday, March 12, 2010

Code Smells

Stumbled on this great little page listing code smells and possible fixes.

Friday, March 5, 2010

The Rules Engine and the Command Engine

I ran into a little issue the other day...

The CMS application I am working on needed a URL builder, which combined a root directory, an optional subdirectory, and a file name. Aspects of the URL must be validated after construction, such as not allowing invalid characters.

I created two CmsUrl engines - one that processes commands, and one that processes rules. The command engine required a concrete object, since the commands modified the URL. However, this became an issue because ICommand demands an Execute method with no parameters and no return value. Therefore, the only way to get my URL string into the Command is in the constructor. Additionally, command executing cannot return a value, so the command must be acting on a reference or global string of some sort. Right? (I read a post somewhere [StackOverflow?] that said simulating reference pointers in C# was accomplished by passing a reference to the containing object, which got me to thinking on different terms.)

In most situations, I think of commands as acting on a data store. The command encapsulates all information needed to make a change on the data store. The data store exists outside the command, obviously, but the command knows how to get to it and modify it. When it comes to strings, they are usually passed by value. To relate it to the data store example, it would be like copying the entire database into the command for modification.

Well, what I ended up doing was Dependency Injection - injecting the engine into the command, and the engine holds the URL to be modified. The command then acts on the URL in the engine. The Process method on the engine has an output parameter which returns the modified URL (correct extension added to the file, etc). That seemed to simulate the data store example.

With the Rule engine, it's only job is the throw Exceptions if the rules aren't met. There is no modification of the string. So it was implemented as a static class. I don't know if that was right or not, to make one static and the other concrete, but that's what I did...

Thursday, March 4, 2010

Generic Interface Interesting-ness

I wanted to set up a generic extension method that would bind a Repeater to the type T Items that I passed in. The definition was:

public static void bind_with<T>(this Repeater repeater,INavigation<T> navigation)
{
if(navigation.Items.Count() > 0)
{
repeater.DataSource = navigation.Items;
repeater.DataBind();

}

}


So obviously, T has to have the Items collection. My interface looked like this:

interface INavigation<T>
-- IEnumerable<T> Items

My Navigation classes (TopNavigation and SideNavigation) already had the Items collection, so I just tacked the interface on to the class declaration, and it passed the compilation test.

Now for the interesting part... when I cast the input to the correct type, I do not have to use the generic method signature as it is redundant. So I call the method like this:

bind_with((SideNavigationItem)e.Item.DataItem)

The method casts SideNavigationItem to INavigationItem<SideNavigationItem>, which has the collection of Items<SideNavigationItem>. No need to use the generic signature.

But even further still, I can also use this:

bind_with((SideNavigation)e.Item.DataItem)

which implements INavigation<SideNavigationItem>, and is also cast the same way. The type parameter defines the type of the Items collection, so it works both ways. I was also able to use:

bind_with(site.SideNavigation).

Cool!

I overloaded the method in order to use it with CmsPage.Articles collection, which is not INavigation at all. I guess I could use interface ICmsCollection, and then have only one method, but I don't know...