CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Kyle Baley - The Coding Hillbilly

"We are stuck with technology when what we really want is just stuff that works" -- Douglas Adams

March 2008 - Posts

  • RedirectToAction in Filter Attributes

    Thankfully, ASP.NET MVC now has filters, seemingly called thus because that's what they are called in Monorail whereas "interceptors" would probably be a more appropriate name.

    So I did what I imagine ninety percent of people are going to use the thing for: created a filter to authenticate a controller/action. You know the one. If you aren't authenticated then redirect to the login page. It's a problem that apparently still hasn't been solved to everyone's satisfaction given the number of solutions out there.

    I turn to my natural resource, CodeCampServer, and check out their solution (off-topic code has been removed):

        public override void OnActionExecuting(FilterExecutingContext filterContext)
        {
            if(!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                filterContext.Cancel = true;                                           
                //no way to access RedirectToAction() or Url.Action() here....
                filterContext.HttpContext.Response.Redirect("~/login?redirectUrl=" + HttpUtility.UrlEncode(filterContext.HttpContext.Request.Url.PathAndQuery));
            }
        }

    Alas, my crest has fallen. No way to access RedirectToAction or Url.Action from within a filter attribute. They are protected or internal, I don't care which because neither will help me.

    Side note
    I'm surprised more people haven't mentioned one of the major reasons I see for not redirecting to hard-coded URLs. I'm developing on Windows XP and *not* using Cassini. Which means I've had to use .mvc in my routes as per the handy little comment in the default Global.asax.cs. But when I deploy to an IIS 7.0-ish environment, it's nice to know I can change the route table in one place and not have to worry about hard-coded URLs anywhere.

    So I'm not liking that little hard-coding in there. But here's where it's nice to have the actual source code for MVC (and on another side note: I'd reckon the CodePlex project should be named in such a way that it actually shows up when you search for "MVC" on that site, as opposed to the generic name it has now). What I want is what happens when I do a RedirectToAction.

    Taking a look through the RedirectToAction code, I came up with something I'm not going to show you. It worked like a charm but after I wrote it, I found an even better method from SteveSanderson which is a slight variation on the theme:

        public override void OnActionExecuting(FilterExecutingContext filterContext)
        {
            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                filterContext.Cancel = true;
                var requestContext = new RequestContext( filterContext.HttpContext, filterContext.RouteData );
                var values = new RouteValueDictionary(new { Action = "Index", Controller = "Login" });
                var vpd = RouteTable.Routes.GetVirtualPath(requestContext, values);
                var target = vpd.VirtualPath;
                var url = HttpUtility.UrlEncode(filterContext.HttpContext.Request.Url.PathAndQuery);
                filterContext.HttpContext.Response.Redirect(target + "?redirectUrl=" + url);
    
            }
        }

    Please note the relative nature of the term "even better". This is by no means ideal but my NOR (Naive Optimism Radar) tells me that this will get better.

    Final side note: Yes, there's nothing stopping me from using FormsAuthentication.LoginUrl instead of a controller action but c'mon, it's so 1.1.

    Kyle the Nouveau

  • Lighten up, you're having fun

    There are a number of reasons I use the nom de guerre that I do. Primarily, it's because I like being self-deprecating. Hard to bruise egos when the one you're mocking is yourself. But it also adds some levity to what appears to be an increasingly somber industry.

    The software industry has a tendency to take itself too seriously. Holy wars range from operating systems to programming languages to development platforms to frameworks to implementations of patterns. People take potshots at others for jumping on bandwagons, for being hypocritical, for being a "slave to tools", for their choice of text editor, for wearing white socks with shorts (hey, my feet sweat in Crocs, okay? It's a documented condition!).

    That's not to say we aren't valuable. Software can do some amazing things. And I'm not talking about carte blanche to be outright negligent in your work. It's good to be passionate about what you do and to foster that passion in others. But let's not imagine you are more important than you really are.holier_than_thounuts

    I'll teIl a small anecdote that involves JP Boodhoo, not because he falls into this category but because I consider him a pretty well-known person in our circles. Chances are, if you're reading this, you know him better than you know me and "holier than thou" is not generally a phrase you'll hear used to describe his attitude toward his work. At the South Florida Code Camp, I mentioned some technique I learned from him. I asked the crowd of about twenty to thirty if anyone had heard of him. No response. Granted some people may just not have put up their hands but I'll ignore them because they run counter to my argument.

    On this blog, I usually talk with more confidence than I feel. The reason being: there are better ways to do pretty much everything. And the reason for that: there are plenty of people who are smarter than I am. You're probably one of them. Or at least I like to assume that and have you prove me wrong.

    I'm far from an expert on anything (except Text Twist of which I AM LORD MASTER OF ALL!!!). But as a group, we often have a tendency to assume we know what's best for the client and even for each other. I've heard stories of shouting matches for something like how to implement "Print this page" functionality. And you could make a drinking game out of the number of snarky, condescending comments I read in blogs and discussion groups. You could get drunk by noon if you just limited it to variations on these:

    • No offense but...
    • You (totally) missed the point of...
    • I don't mean to be rude but...
    • I don't understand how you people... or I don't understand you people who... or basically any comment with the phrase "you people".

    (Sidebar: The last one actually makes me laugh whenever I read it. There's a running joke that's been in our family for years. When someone is being mocked mercilessly but good-naturedly (and believe me, this happens *VERY* often), the mockee responds with "I hate you people" prompting a hearty renewed round of laughter. We include it as a default response in all the reader polls in our all-but-defunct family newsrag and have even had t-shirts done up with the phrase on it accompanied by a family photo.)

    When it comes right down to it, I am paid to do what I'm told. Many clients will defer to my (or the team's) expertise for a good many decisions and in varying degrees. And I do make suggestions based on past experience and on professional opinion. But in the end, if the client says they don't have time to write unit tests, that is their decision to make and all I can do is give it the ol' college try and continue on under protest, but good-natured protest.

    And I'm fine with that. To borrow a line from Bill Cosby, "I've seen the boss's job. And I don't want it." Although the danger here is if you are held accountable for decisions that you didn't make. What can I say? People will do that. Best you can do is document your concerns and hope you don't go down with the ship. Maybe I've been lucky in my career but this hasn't ever been a concern for me.

    I'll close with some facts about you, personally. Apologies if I'm shattering egos. I'm a hillbilly. I deal in reality**:

    • You are not in charge
    • You don't decide what the application does
    • You don't decide when the application can be released
    • You don't get to hold back on demo'ing the application because "it isn't ready" (and in fact, it was in talking with someone about this very idea that prompted this post).

    Passion is good. Good-natured ribbing is nigh on the most entertaining form of communication known to developerkind. But condescension and self-importance are a major turn-off to this hillbilly.

    Kyle the Nihilist

    ** Hillbillies also deal in generalities. I'm well aware that there are exceptions. But still, you're not one of them.

  • Table-per-concrete-class in NHibernate?

    Normally when I have a problem I can't solve right away, I bang away at it until I have a half-assed solution that I can post here so that others can provide a better solution in the comments. It's how I've found about four methods for testing RedirectToAction, got tips for automating my deployments, and figured out the ConventionController. Quite frankly, I post here so that someone else can show me a better way to do things 'cause I've learned long ago my way ain't it.

    This time, however, I've searched and experimented and have come up empty. So there will be no facade that I know what I'm doing this time. I'll just plain admit that I can't get it to work. I suppose I could bitch and complain about how hard this is. That seems to be a common theme I've seen in many other places when someone can't figure something out. Whine about how it should be easier, then have some kid in tenth grade post a simple yet elegant solution in the comments within the first five minutes. Alas, I don't have the pride to assume that it's someone else's fault I can't get my work done.

    'Spose I should get to the problem at hand lest I lose potential problem-solvers.

    I have a domain object called Job. It contains a collection of Location objects. A Location is an abstract class with two implementations: RuralLocation and UrbanLocation. The heart of my problem is: How do I represent this in an NHibernate mapping?

    As defined in the documentation, I'm using a "table-per-concrete-class" strategy. I'll listen to arguments that it's not appropriate but consider this: Locations are value objects. They have no identity in and of themselves. A sample location would be NE-15-1-29-1W, which is a rural location (Northeast quarter section, section 15, township 1, range 29, 1 west of the prime meridian). There is no benefit to storing this as an individual entity in the database and attaching it to many jobs. Locations are essentially bandied around like money when you listen to the clients talk.

    The reason I'm not considering a "table-per-class" or "table-per-subclass" hierarchy is because the two classes have exactly zero fields in common. So in the former, we'd have a table where half the fields would be null in every single row. And in the latter, we'd have a Location table containing two fields, an ID and a LocationType, and nothing else. Maybe having such a table isn't too bad but it sure feels like I'm altering the schema for the sake of NHibernate.

    So for better or for worse, I have a "table-per-concrete-class". This is easy enough to represent in mapping files for each class individually as per the NHibernate documentation. Where I'm stuck is how to link these concrete classes back to the Job mapping file. Back when I had only the one location type, the mapping looked so:

    <bag name="Locations" cascade="all-delete-orphan">
          <key column="JobID" />
          <one-to-many class="Trilogy.Gunton.Model.RuralLocation" />
    </bag>

    Fairly straightforward and pretty much right out of the docs. But now, I need to map to an abstract Location class that has no representation in NHibernate. The docs mention the <any> element and Ayende has something similar but for the life of this hillbilly, the answer still eludes.

    So to my insightful and, I pray, generous readers: <ahem>....help!

    ### UPDATE ###

    Sorry folks, forgot to include relevant diagrams. Database diagram and class diagram are included below. The Locations property on Job is an IList<Location>. Click for larger versions:

    Gunton_DB Gunton

     

    Kyle the Unassisted

  • TestControllerBuilder in MvcContrib

    Yesterday, I cracked open the MvcContrib source code to see if it had....ummm...ok, I swear I was looking for something legitimate in there. But as soon as I updated the latest and opened the project, my eyes zeroed in on the TestControllerHelper class. So with a quick mutter of "oooh, shiny" to myself, I proceeded to what I knew would be the dearth of my productivity for the rest of my day.

    Sitting here this morning, I can report to you that I am now one step closer to being able to adequately (but still not quite usefully) test controller actions that include a RedirectToAction method. Here's a sample test:

    [ Test ]
    public void Test_redirect( )
    {
      TestControllerBuilder builder = new TestControllerBuilder( );
      var controller = builder.CreateController<LocationController>( );
      controller.TestRedirect( );
      Assert.That( builder.RedirectToActionData.ControllerName, Is.EqualTo( "Job" ) );
    }

    The CreateController method on my TestControllerBuilder uses Castle's DynamicProxy to create the controller. It also creates a ControllerContext using dynamic mocks from Rhino Mocks similar to the way Haackselman have already showed you and wires it up to the controller.

    An interceptor is also attached to the controller to intercept calls to RenderView and RedirectToAction. (Actually, it intercepts all calls but only these two have special handling.) Calls to either method will populate an appropriate object on the builder class. In the example above, I'm using the RedirectToActionData object.

    So far, this is all sweet and dandy like raccoon candy. I have indeed verified that it works as advertised. But at present, it seems to work only for controllers that have a parameterless constructor. Which is going to work for exactly none of my controllers.

    I have a version working that allows you to pass in constructor arguments to CreateController and will submit it as a patch for MvcContrib, assuming it hasn't already been addressed.

    Oh yeah, I remember! I wanted to see if MvcContrib had the Flash feature I saw added to (but not yet used in) CodeCampServer.

    Kyle the Forget-Me-Not

  • Serializing to client script

    I finally subscribed to Rick Strahl's blog because an inordinate number of my Googling ends up there. It's a little freakish the number of times I've hit his blog and had it solve the exact obscure problem I'm having.

    After subscribing and going through some recent posts, I stumbled on his very own Ajax Toolkit. Having had my fill of Ajax libraries of late, I scanned through it and noted it for later reference, paying particular attention to the JSON Serializer, a problem I was trying to tackle at the time. But again, I was kind of averse to frameworks so I moved on.

    Fast forward a few hours. I had been fighting with some client-side stuff dealing with a collection of items that I had planned to magically get from the server in JSON form and the time had come to actually implement that. The first attempt was courtesy of Scott Guthrie who outlined how to do it pretty specifically. At the end, he mentions the DataContractJsonSerializer class as a replacement for JavaScriptSerializer and I go about implementing it. Didn't get too far but wouldn't you know it, a Google for implementing DataContractJsonSerializer brings up Rick's blog on the first page and gosh darn if his explanation succeeded where others failed.

    But immediately, things start to look funny with the use of DataContractJsonSerializer. All of a sudden I have to start decorating all my domain objects with [DataContract] and [DataMember] attributes. But I started down the path because if I could justify adding attributes for that, it's not so hard to extend the argument to use ActiveRecord which I kind of got excited about because it would make things go that much faster.

    So I cut and pasted [DataMember]s until the bovines migrated to the abode and eventually got the sucker writing out a JSON string for my collection as advertised. Then I added an NHibernate mapping to the class...

    Until then, I had been playing with everything on the client only and I was dealing with an empty and unpersisted collection of objects. Now it was time to start dealing with persistence so the NHibernate mapping had to be done. It was then that I ran into the problem outlined here. Namely, even when you make the relevant NHibernate types known to the serializer, it still has problems deserializing using DataContractJsonSerializer. And even if it didn't, even someone as loose with attributes as I am has trouble justifying a reference to NHibernate from my domain project strictly so that the sucker can be converted into a string.

    At that point, I recalled Rick's West Wind Ajax Toolkit and the JSON Serializer within. And I'm happy to report that it works smashingly. No [DataContract] and [DataMember] attributes. No references to NHibernate from my domain. No workarounds to serialize NHibernate classes. It just plain works. So far at least. Though I'll admit that I had to turn off lazy loading on my object to make it work. A compromise I'm willing to live with for the moment because the collection I'm serializing consists of value objects so there's not much chance of getting too deep into the hierarchy.

    And the jury's still out on whether JSON is an easier format to deal with. Yes, it has advantages when dealing with it in client code. But the number of hoops I'm jumping through to get it into that format had better be worth it.

    Kyle the Circus Lion

  • NHibernate error while adding a collection to an object

    I'll keep this one fairly Google-able due to the amount of time it took me to figure it out sans a similar reference. Seasoned NHibernate veterans will likely guess the issue before they finish reading this sentence.

    Here's the error message I was getting from NHibernate:


    System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.

    at System.ThrowHelper.ThrowKeyNotFoundException()
    at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
    at NHibernate.Cfg.XmlHbmBinding.CollectionBinder.BindCollectionSecondPass(XmlNode node, Collection model, IDictionary`2 persistentClasses)
    at NHibernate.Cfg.XmlHbmBinding.CollectionBinder.<>c__DisplayClassd.<AddCollectionSecondPass>b__c(IDictionary`2 persistentClasses)
    at NHibernate.Cfg.Configuration.SecondPassCompile()
    at NHibernate.Cfg.Configuration.BuildSessionFactory()
    at Trilogy.Gunton.DataAccess.SessionBuilder.GetSessionFactory(Database selectedDatabase) in SessionBuilder.cs: line 72
    at Trilogy.Gunton.DataAccess.SessionBuilder.GetSession(Database selectedDatabase) in SessionBuilder.cs: line 21
    at Trilogy.Gunton.DataAccess.UnitOfWorkFactory.Create() in UnitOfWorkFactory.cs: line 19
    at Trilogy.Gunton.DataAccess.RepositoryBase`1.GetAll() in RepositoryBase.cs: line 19
    at Trilogy.Gunton.Tests.Integration.JobTypeRepositoryFixture.Should_retrieve_job_types() in JobTypeRepositoryFixture.cs: line 16


    This started happening after I added a collection to my object (which Karl Seguin's post was instrumental in getting set up). Here's what I added to the <class> element in the .hbm.xml file for the object (fingers crossed on the XML formatting):
      <bag name="Locations" generic="true">
           <key column="JobID" />
           <one-to-many class="RuralLocation" />
      </bag> 

    Pretty standard stuff to those in the know (of which I am still not). I also created the corresponding RuralLocation class and RuralLocation.hbm files to go along with them.

    So the above error would appear anytime I tried to get a session factory in NHibernate, which is pretty much the first thing that's done anywhere in the app. The ultimate solution: RuralLocation.hbm was not an embedded resource.

    This is something I will forget to do about 75% of the time I create a new .hbm file but usually the error is more "oh yeah"-inducing. Something like "Can't find class Trilogy.Gunton.Domain.RuralLocation" where you can tell pretty quickly it's not loading the file properly. I know this because the way I discovered the problem is by creating an integration test to save a RuralLocation on its own, outside the context of its parent object.

    In this case, because I went straight to using the new class in a collection within another object, the error was hair-tearingly cryptic. Luckily, I don't have much hair anyway so no harm done.

    Kyle the Eggheaded

  • Testing RedirectToAction in ASP.NET MVC

    Crikey, am I having a time testing RedirectToAction on my controller with the new MVC Preview! How much of it is me and how much is the beta-ness of the framework, I will leave to your fair and impartial judgement.

    I upgraded relatively easily from the December CTP. Here's a quick summary of what was involved:

    • Change the assembly references from System.Web.Extensions to the three new ones that are installed with the new preview (and I'd *really* like to know why that install takes so long if all it does is drop some files, add some shortcuts, and install a few VS templates. Might be time to upgrade the laptop.)
    • Drop the reference to MvcToolkit
    • Update the 80s-style square brackets to the new age curly braces. Kind of like the evolution of the Volvo.
    • Update the method used to add routes. Thanks to newly-required constructor arguments and the funky new RouteValueDictionary, they now look like this:
          RouteTable.Routes.Add(
              new Route( "{controller}.mvc/{action}/{id}", new MvcRouteHandler( ) )
                  {
                      Defaults =
                          new RouteValueDictionary( new { action = "Index", id = (string) null } )
                  }
              );
    • Update my call to RedirectToAction, again to use the RouteValueDictionary (which, judging from Reflector, we can only assume is a class-in-progress).
    • Update the web.config as I was told to.
    • Dropping the Html. from the beginning of all my ResolveUrl calls in the views.
    • Updated calls to various other methods that used to be part of MvcToolkit.

    I think that was about it. After that, the bad boy compiled and all the tests passed and it was on to my next challenge. Namely, drop the test-specific subclasses in favour of the extension methods mentioned by Haackselman in post and video form.

    It went pretty smoothly at first. I added the MvcMockHelpers class, incorporated a FakeViewEngine, and went about my merry way converting asserts that used testController.RenderedView to ones using fakeViewEngine.ViewContext.ViewName. Ditto for RenderedViewData to ViewData.

    Then came the last test. The one that checked to see if the action correctly called RedirectToAction. There is no property in the ViewContext to check to see which view was the eventual target. And after checking out the only post I could find on the subject (which has a lot more information on it today than it did two days ago when I went through this problem), I got an error in the test that turned out to be misleading:


    System.NullReferenceException: Object reference not set to an instance of an object.

    at Trilogy.Gunton.Web.MyRoute.GetRouteData(HttpContextBase httpContext) in MyRoute.cs: line 117
    at System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase context)
    at Trilogy.Gunton.Tests.Unit.Controllers.JobControllerFixture.Save_action_should_send_job_to_service() in JobControllerFixture.cs: line 150


    The MyRoute class is courtesy of Reflector and I used it to determine the problem. It was two days ago and I don't have the memory I used to but I'm pretty sure it was something deep within the bowels of the Route class. It's calling GetRouteData on the class to determine which URL to redirect to and that method has a call:

    IList source = SplitUrl(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo);

    And the AppRelativeCurrentHolyCowThisIsALongName and PathInfo properties have not been mocked. I think it has something to do with the fact that the controller's fake ControllerContext is created with an empty RouteData. Not sure how the RouteTable created in Global.asax.cs eventually translates into the RouteData (assuming it does) because by that time, I had enough fodder for this post (which is the only reason I took it as far as I did).

    Again, this is speculation but the net result is, RedirectToAction remains untestable by me. I've heard tell that there is a way but as it stands now, I've put some comments into my tests to remind me to revisit them once a better way exists.

    But if you're the type that's more anal about code coverage than best practices, using Response.Redirect instead of RedirectToAction works like a charm.

    Kyle the Undirected

  • Auto-generated vs. human-generated IDs

    Got myself a delicious problem but there is another discussion to be had so I will leave you hanging on that one until a later post.

    I've already hinted at the software I'm working on for the time being. It's essentially something to help manage a land surveying office but what it does isn't really the point. What *is* the point is that it has some major domain object that needs to be managed. In this case, it's a Job (to use the domain term).

    A brief history: Jobs in land surveying office have always historically been a paper-based entity. There is a lot of physical information to collate. Land titles, plans, deeds, even the sketches the crews make in the field. These are all key pieces of information that need to be kept organized in some fashion. And it's all either visual information or third party data that they have no control over (and sometimes both). So to think that all of this information can be moved into the digital world is a little ambitious at this point.

    So historically, they tend to organize them into a Job. And naturally, a job must be assigned a number. And typically, companies will use a numbering system that will convey some information in the number. This can be something relatively simple, like M070123 which indicates that this was the 123rd job in 2007 for the Montenegro office. Or it could be somewhat more complex like BLC-08-A31 which might mean it's a BLC (domain term, don't ask 'cause I don't know) from 2008 from zone A of the city and it's the 31st job in that zone for the year.

    However it is generated, the fact remains that they need to maintain a running list of these numbers in a "book" (another domain term, again don't ask 'cause I've never heard of it either) so that they can generate a new one easily. As a new job is ordered, the person recording it must take care to record a new number in the running list so that there are no duplicates.

    Now here comes the consultant (i.e. me, and seriously, do NOT ask about this one) to automate some of this data. And as I start working, the inevitable question arises: Why are we still using this archaic process of generating job numbers manually? Computers *love* generating IDs automatically. Databases do it natively. NHibernate can get you a GUID in less time than it takes to remember what the acronym stands for.

    And so it was that I made a suggestion: Why not ditch the current mechanism and start over with something more computer-friendly? Like starting with an ID of 1 and going up from there. They can still refer to jobs easily. And though we're losing that tiny bit of metadata embedded in the previous version, we can glean that information (and a whole lot more) in the form of reports on the system. My job's easier and they've saved money during development.

    This being a family business (and my family's business to boot), they have reservations but defer to the "expert". There's a lot of "well, there may be some pushback from the others but if you think it'll be easier, go for it." I'm happy at a job well-done.

    So confident am I that I think nothing of an e-mail from my brother asking me to contact one of the "others". That is, someone outside the family but who is actively going to *use* the system. She has some reservations but is leery about bringing them up. She's young, inexperienced. What could she possibly say to sway the mind of the big, bad consultant?

    "How do we enter in existing jobs?"

    An honest question that deserves an honest answer, rather than the back-pedalling one I gave. Which basically suggested we'll have a field for OldJobNumber to handle legacy job numbers. And even as I spoke the words, the whole idea kind of unravelled in my head.

    Because the old job number is important. They've got cabinets and cabinets filled with files referring to them. And all of a sudden, I'm suggesting they create a whole new filing system. And that's just the physical aspect. Even within the application I'm writing, I'd have to present the job number differently. I had visions of: if ( old job number exists ) show it, else show job ID peppered throughout the code.

    So I swallowed my pride and admitted I hadn't thought of that. After which case, the floodgates opened and it was admitted that no one was really looking forward to it.

    And after some questioning, it turns out there is a very good reason for this, though not an obvious one. There is something psychological in having metadata in the job number. Sitting in the office, you'll notice that they are constantly bandying job numbers about. "What's the status of job M080050?" "Where are the plans for job V070758?"

    And when they call the jobs out like this, they can make a mental filter as they try to remember which job it is. You can imagine the thought process: "B-07-C98, that's that BLC from late last year in the Soho area, I've got those plans right here." Compare that job number with another that has an ID of 78945 and it suddenly becomes a lot harder to create that mental filter. In essence, it's more than an identifier. It's also a name. And a filter. And even a kind of mini-report.

    The lesson learned: Don't subvert your client's domain with all this new-fangled computer jargon.

    The net result is job numbers will still be auto-generated but they will be generated in the form that they're used to. It's not quite as automatic as an auto-increment but it's still very much algorithmic and can be done somewhat easily by the application.

    But I have *seriously* simplified how they generate them for the sake of this post. The reality is the tasty problem I referred to in the opening paragraph. By way of foreshadowing (or even foreboding), they have not one, not two, but *six* methods of generating a number for a job based on various factors, some of which require intimate knowledge of a map of the area.

    Stay tuned!

    Kyle the Auto-generated

    Final closing point, because someone may comment on it, is that I have no intention of dropping the auto-incrementing ID. But like most IDs, it won't be as in-your-face as I originally expected.

  • Client-side scripting, eight years on

    There's a point to this and a request for suggestions at the end so don't be alarmed by the meandering I'm doing at the beginning.

    The Hillbilly is a bit of a black sheep in his family. I have three brothers, all of whom are land surveyors working in the family business in western Manitoba. My mother, a retired nurse, is also on the payroll as the Office Dictator (according to her business card anyway). It's an odd kind of environment out in their world, what I've often called "Deliverance Country". They have theme days like many other companies but you're more apt to hear them celebrate "Potty Mouth Friday", where they swear like sailors all day, rather than "Casual Friday". (We won't go into the details of Racial Slur Wednesday followed by Repentance Thursday.) And their day-to-day activity is that kind of controlled chaos inherent to small businesses that do actual work while the rest of us blither on about whether our corporate portal is using a colour scheme that won't offend the accounting department's hatred of pastel.

    They use an application I wrote nigh on eight years ago in classic ASP that makes heavy use of XMLHTTP and DHTML before it had a name that sounded less Klingon when pronounced phonetically. In it, there is a screen that creates a Job object (ok, it doesn't actually use objects because I was young and it was ASP but let's make an ass out of u and me for a minute).

    On that same screen, they can attach one or more locations to the Job as well as remove any existing ones. This is done using DHTML to add and remove table rows dynamically to a running list. Behind the scenes, it maintains an XML document containing all the job information, including the list of locations. On form submission, all the form elements are ignored and the XML document is the piece that gets processed.

    As I said, this was eight years ago. I'm in the process of updating this same application and my first order of business is to duplicate the functionality of the existing app (which is much more than what I've already described, except for a search feature). And I'm implementing the Edit Job screen and discovering that after eight years, there doesn't seem to be a better way of building this screen.

    Yes, there have been advances in scripting languages and techniques. I'm starting to use jQuery which is kilometers/miles faster than using the DOM natively but is not going to win over the hearts of any web developers looking into Javascript for the first time. It's a great framework for those of us familiar with how hard it is to do this stuff without it though.

    But I'm talking about the underlying method in which I would build this screen. However cleaned up it may be, there is still direct manipulation of HTML elements. There is still the storage and manipulation of data in the browser. And XML is still a very viable alternative for that storage. I have a feeling JSON might be more suitable but neither option is very palatable.

    For example, in C# 1.0, how would you find a particular element in a collection? Loop through the list until you find it. In C# 2.0: probably use the Find method on the list with a delegate. In C# 3.0: lambda expressions maybe.

    In Javascript today, as you did eight years ago, you loop through the list. Or you use selectSingleNode on an XML document, just as you did eight years ago.

    Now, jQuery does alleviate this quite a bit with some pretty advanced use of selectors. But in the end, you're still searching through an XML document. Or a JSON object which, despite the simpler syntax, still is kind of funky to traverse and add/remove items from.

    I'm not sure how my ideal way to build this screen would be exactly. I just have this feeling that, after eight years, it should be easier than it is.

    And maybe it is. Which brings me to the real reason for this post. How would *you* build such a screen? Remember, you're adding/removing items to a collection on the page client-side and submitting the data all at once. The items are not selected from a list, they are entered mostly free-form by the user (I'm paraphrasing seriously but don't want to go into the details of the section/township/range syntax). Think of it as creating a shopping list with some extra metadata at the top (e.g. name of the shopper, date the list was made, etc).

    Would you do DOM manipulation with jQuery or prototype or whatever? If so, how would you maintain the state of the object in the browser? Or would you have a postback everytime an item was added to the list and maintain it in a state on the server? Or is this something that Script# or something akin was born to deal with? Or...?

    I welcome any and all suggestions and would love to post a follow up to summarize them at a later date.

    Kyle the Client-Side

  • Passing objects to controller actions in ASP.NET MVC

    Here's the bad thing about ASP.NET MVC. Every little thing about it is bloggable mostly because every little thing is new to it. I'm half considering using Monorail just because everything in it is so well-documented, I wouldn't need to waste my time blogging about it. (Those of you about to regurgitate the holy war about why ASP.NET MVC has caused all this hoopla when Monorail has been around for so long, calm down. It's just software, for Jayzus' sake.)

    After lamenting the mechanisms I needed to use to pass information to a ControllerAction, Ben Scheirman turned me on to the ConventionController in MvcContrib. Apparently, it works well for pages that create new objects but not so much for ones that update existing objects.

    *EDIT*

    I originally had a whole long spiel done that included a history of my stored procedure-writing prowess and a PurportedSibling domain object. It was to explain that because of my genius way of setting up my service, I could use the ConventionController for both creates and updates.

    But the fact is, it had nothing to do with how I set up my service. It just works out of the box. By deriving my controller from ConventionController, I can create a view that includes:

        using ( Html.Form( "Save", "Job", FormExtensions.FormMethod.post ) )
        {%>
        <%=Html.Hidden( "job.Id", ViewData.Id ) %>
        Sibling Name: <%=Html.TextBox( "sibling.Name", ViewData.Name ) %>
        Is half sibling?: <%=Html.RadioButtonList( "sibling.IsHalfBrother", TRISTATE_ENUM, ViewData.IsHalfBrother ) %>
        Rumoured mammy: <%=Html.TextBox( "sibling.RumouredMammy", ViewData.RumouredMammy ) %>
        Rumoured pappy: <%=Html.TextBox( "sibling.RumouredPappy", ViewData.RumouredPappy ) %>
        <%=Html.SubmitButton( "Submit", "Save" ) %>
        <%}%>

    Here are the actions. The first launches this view for a new sibling. The second for updating an existing one. And the third saves in both cases.

        public void Create( )
        {
            RenderView( Edit", PurportedSibling.Null( ) );
        }
    
        public void Edit( int id )
        {
            PurportedSibling sibling = _siblingService.GetById( id );
            RenderView( "Edit", sibling );
        }
    
        public void Save( [Deserialize("sibling")] PurportedSibing sibling )
        {
            _siblingService.Save( sibling );
            RedirectToAction( new { Action = "SiblingUpdated", id = sibling.Id } );
        }

    Crisp and clean be how I like my controller actions. Compare the Save with what it looked like before:

    [ControllerAction]
    public void Save( int siblingId, string siblingName, TRISTATE isHalfSibling, string rumouredMammy, string rumouredPappy )
    {
    	_siblingService.Save( siblingId, siblingName, isHalfSibling, rumouredMammy, rumouredPappy );
            RedirectToAction( new { Action = "SiblingUpdated", id = sibling.Id } );
    }

    This was before I was about to add the part where you specify possible offspring of the siblings on the same screen, something that would have made this Save method more complicated than the domain itself, which is an homage to graph theory in and of itself.

    In any case, I'll leave the implementation of the Save method to your imagination in both cases. (Hint: It is much nicer in the new version, let me tell you!)

    Notice how the new version also doesn't have a [ControllerAction] attribute. That's another feature of the ConventionController which will make it much easier to manage when the next CTP of ASP.NET MVC comes out which removes the need for the attribute.

    Back to the ConventionController. I suspect a good chunk of the magic has to do with NHibernate being able to discern whether the object is new or updated based on the ID that is passed to it. But even if you don't use NHibernate, this same technique could be used. I.E. Check if the ID of the object is 0. If so, it's new. Otherwise, it's old.

    There is a *very* large caveat to this method. If the object you are updating has other properties that are *not* updated on this page, they will be set to whatever default value is appropriate for its datatype. And when it is then sent to the database for "updating", its corresponding field will be updated to this value right alongside every other field.

    In this case, you can add an intermediate step in the process somewhere. I.E. Somewhere along the line you'll need to do the following:

    1. Retrieve the object from the database (based on the ID)
    2. If an object is retrieved, update it with the values that were entered in the view.
    3. Save the object

    This way, you won't overwrite any properties with default values just because they aren't editted in this particular view of the object. In theory at least. I haven't actually tried it myself.

    Kyle the Purported

  • Hillbilly in da house!

    Probably should have been more on the ball with this but I'm in Calgary for the week. Anyone in the area looking to eat, drink, and be merry, come meet me on Thursday evening around 7-ish at Joey Tomato's Chinook Centre (unless someone has a better suggestion but it had better be as good as, or better than, Joey's Ahi Tuna Salad). Hopefully, we can hit the theatre for a movie while I'm up here in civilization, too.

    Comment here if you can make it.

    Kyle the Inviting

  • Automated deployments

    In my last post, I ran on about automated releases. So much so that adding automated deployments at the end seemed like cruel punishment. So instead, I decided to make it a separate post. Still a cruel punishment, but now it is unusually so.

    To recap, our automated release target executes on every check-in and creates a subfolder under the releases folder with the version number of the application. Then a releaseable "package" is placed in it. In this case, it's a web application that is to be deployed.

    The target you are about to see follows from this train of thought. And apologies if the formatting looks off. I've yet to figure out how to embed XML into a post in LiveWriter or Community Server's editor so that it uses the right HTML in the browser and the average RSS reader.

    <target name="deploy" depends="version">
         <deliisdir vdirname="${deploy.vdir}" failonerror="false" />
         <delete dir="${deploy.dir}" failonerror="false" />
         <mkdir dir="${deploy.dir}" />
         <copy todir="${deploy.dir}" >
             <fileset basedir="${latest.release.dir}">
                 <include name="**\*.*" />
             </fileset>
         </copy>
         <property name="database.name" value="${database.name.test}" />
         <call target="drop-database" />
         <call target="create-database" />
         <mkiisdir dirpath="${deploy.dir}" vdirname="${deploy.vdir}" defaultdoc="Default.aspx" />
         <loadtasks assembly="${tools.dir}\nant\Vitreo.Nant.dll" />
         <iisappmap vdirname="${deploy.vdir}" extension=".mvc"
                    executable="C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll"
                    verbs="GET,POST"
                    checkfileexists="false" /> 
    </target> 

    As before, a list of variables:

    deploy.vdir The name of the IIS virtual directory we are deploying to
    deploy.dir The folder that the virtual directory will point to
    latest.release.dir Path to the folder containing the latest release. e.g. C:\projects\LineageOrganizer\releases\Latest
    database.name.test The name of the database we are pointing to in the deployed environment (i.e. the Test environment)
    tools.dir Path to tools used during the build process. e.g. C:\projects\LineageOrganizer\tools

    And again, a brief synopsis of what the target does:

    1. Delete the existing IIS virtual directory if it exists. (The <deliisdir> and <mkiisdir> tasks are included in NAntContrib)
    2. Delete the deploy folder if it exists and re-create it.
    3. Copy the latest release to the deploy folder.
    4. Drop and re-create the test database (the drop-database and create-database targets use the <updateSqlDatabase> task from vincent-vega, which appears to be off-limits today but you can get a copy from CodeCampServer).
    5. Re-create the IIS virtual directory
    6. Create a mapping for the .mvc extension for ASP.NET MVC. (<iisappmap> task is courtesy of Brian Donahue, who I'd recommend adding to your blogroll).

    The last step is necessary only if you need to create an IIS mapping, which you do when deploying an ASP.NET MVC application into an IIS 6 environment. Otherwise, you can skip this step.

    In the post on releases, I snuck in a teaser on the folder where I store the latest version of the application. In that target, I created a folder with the same name as the application version and released to it. Then I copied the entire contents of this folder to another one called Latest. The reason for this will be clear shortly. First, I need to talk about when this target is executed.

    The deployments don't happen at the same interval as the build. That is, the application is not deployed on every build. This would be pretty chaotic if you re-deployed the application several times a day while the testers were trying to work.

    So they have to be on a different schedule or, as in my case, triggered manually. This means setting up a separate CruiseControl.NET project for them. So for our LineageOrganizer application, we would have two CC.NET projects: one for the main build and one to deploy the application. Both use the same build file and both have the same root folder. In fact, the CC.NET project for the deploy is relatively simple:

    <project name="LineageOrganizer.deploy">
      <webURL>http://ccnetservername/server/local/project/LineageOrganizer.deploy/ViewProjectReport.aspx</webURL> 
      <workingDirectory>C:\data\ccnet\Trilogy.Gunton</workingDirectory>
      <triggers />
      <tasks>
        <nant>
          <executable>c:\data\ccnet\Trilogy.Gunton\tools\nant\nant.exe</executable>
          <buildFile>Trilogy.Gunton.build</buildFile>
          <targetList>
            <target>deploy</target>
          </targetList>
        </nant>
      </tasks>
    </project>

    There's no <sourcecontrol> tag nor are there any merge files to take care of. Just execute the target and that's it.

    Here's where the concept of a Latest folder comes in. The releases are created based on the application's version number. And the version number is based partially on the CruiseControl version. But now we have two different CC.NET projects, each with their own version number in CruiseControl. So the latest version for the main build, which is the one that creates the releases, is different than the one that deploys the application. I.E. If we try to get the path to the latest release folder based on the version number within the deploy target, it would create a path based on the deployment CC.NET project, not the original one.

    So to get around this, after every release, we copy the release to a statically named folder called Latest. That way, both the build CC.NET project and the deploy CC.NET project know where it is.

    Another way to do this would be to write the current version number to a file. This was going to be my first course of action until Donald suggested the static Latest folder which I like better although only because it feels better.

    This process is still a work-in-progress for me but it's worked out reasonably well so far on my one-man team. Though I will admit that the deploy target does fail on occasion if someone is using the app when it is running. Seems to be locking files so that the deploy folder can't be deleted/re-created. When that becomes annoying enough to fix, you can be sure I'll pad my blog resume with the solution.

    Kyle the Deceptively Simpleww

  • Automated releases (with a touch of deployments)

    One of the things I espoused in my recent presentation on Brownfield applications is automating your releases and your deployments for continuous integration. And being a good-natured hillbilly who has been a little lax on technical content of late, I will now turn your attention to that very topic.

    Note: Even though I mention this in the context of a Brownfield application, you would do the same thing to set it up with your Greenfield application as well.

    First off, we should define a release and a deployment because they sound pretty similar.

    A release is a packaged version of the application. That is, it is some artifact or group of artifacts that *could* be deployed if so desired. A deployment is the actual act of deploying a release.

    A couple of notes on the definitions:

    • There doesn't need to be a one-to-one mapping 'twixt releases and deployments. You can create releases all the live long day and not deploy any of them.
    • The reverse isn't true. You can't create a deployment without a release. Because, there ain't nuthin' to deploy, Jed!

    There are a few schools of thought on when to create releases and what to do with them. Which is another way of saying don't take what I'm about to say as gospel. This is how I've implemented it on my current project which has a development team consisting of The Coding Hillbilly 'n his alter-egos, Code Coverage Nazi and Guerrilla Refactorer. It's not without its faults which I'll talk about in the next post on deployments.

    So with my requisite disclaimer out of the way, I create a release on every build. I.E. Every time I check in code, a new release is built. Wackiness? Maybe, but that's where the the Coding Hillbilly alias gives me a free pass. My advice is so easy to dismiss as the ramblings of a hick that when I eventually do say something useful, it's all the more meaningful.

    Why create a release on every check-in? Easy, because I can. It's easy to create them so why not do so every time you can. That way, if the client says, "show me what you have" you can give him the absolute latest and, one might assume, greatest. What's the downside to having a release on each check-in? Wasted disk space? Give me half a day off and go buy yourself something nice with the savings. (Yes, I'm ignoring a whole lot of internal politics. Such is the benefit of developing on your own build environment.)

    That's enough theory. Here's the NAnt task that creates my releases, with all subtasks merged into one. A summary of properties used follows:

         <target name="release">
            <property name="current.release.dir" value="${releases.dir}\${project.fullversion}" />
            <delete dir="${current.release.dir}" failonerror="false" />
            <mkdir dir="${current.release.dir}" />
            <copy todir="${current.release.dir}">
                <fileset basedir="${app.src.dir}\Trilogy.Gunton.Web">
                    <include name="Content\*.*" />
                    <include name="**\*.master" />
                    <include name="bin\*.*" />
                    <include name="**\*.aspx" />
                    <include name="**\*.asax" />
                    <include name="**\*.config" />
                </fileset>
            </copy>
            <copy file="${config.dir}\nhibernate-default.cfg.xml.template" tofile="${current.release.dir}\nhibernate-default.cfg.xml">
                <filterchain>
                    <replacetokens>
                        <token key="SERVER" value="${database.server}" />
                        <token key="DBNAME" value="${database.name.test}" />
                        <token key="SHOW_SQL" value="${debug.show_sql}" />
                        <token key="DATA_ACCESS_ASSEMBLY" value="${data.access.assembly}" />
                    </replacetokens>
                </filterchain>
            </copy>
            <delete dir="${latest.release.dir}" failonerror="false" />
            <mkdir dir="${latest.release.dir}" />
            <copy todir="${latest.release.dir}">
                <fileset basedir="${current.release.dir}">
                    <include name="**\*.*" />
                </fileset>
            </copy>
        </target>
    releases.dir Path to the releases folder. e.g. C:\projects\LineageOrganizer\releases
    project.fullversion Full version number of the project. This is generated partially using the CCNetLabel
    current.release.dir Combines releases.dir and project.fullversion. e.g. C:\projects\LineageOrganizer\releases\1.0.31
    app.src.dir Path to the source code for the application. e.g. C:\projects\LineageOrganizer\src\app
    config.dir Path to a folder that contains templates that vary from environment to environment. e.g. C:\projects\LineageOrganizer\config
    database.server Name of the database server you want to use for this environment
    database.name.test Name of the test database you are using for this environment.
    data.access.assembly Name of the data.access.assembly to be used for NHibernate
    latest.release.dir Path to the latest release folder. e.g. C:\projects\LineageOrganizer\releases\Latest

    Some these will become clear when I go through the build file. I won't add "I hope" because I GUARANTEE lucidity*.

    If you look closely at the tasks in this target, it does one of three things:

    • Deletes folders
    • Makes folders
    • Copies files

    That's all a release is in my case. Elsewhere in my build file, I'm already building the application for testing purposes and as it so happens, I'm building it in the same way I would release it. This is admittedly a little dicey here because you may not want to do that. For example, you may want to compile with a different configuration for your releases. But it's not a pain point for me yet so the decision can be put off.

    So, in order, here is what is happening (more explanation follows):

    1. Get the path to the new release we are building (based on the current version number). This uses the CCNetLabel property as described by Donald Belcham, whom I should have referenced long ago in this post because most of these ideas come from conversations with him.
    2. Delete the folder if it exists (it usually won't) and create/re-create it
    3. Copy the contents of the web application to the release folder
    4. Copy the NHibernate configuration template to the release folder and replace its tokens with values appropriate for the test environment.
    5. Delete the folder containing the latest release and re-create it. This is NOT the folder labeled with the previous release number. It is a subfolder in the releases folder that is physically named Latest. It will always exist already except for the first time you run this target (or if you physically delete it)
    6. Copy the contents of the current release folder to the latest release folder

    Note on #3. We copy only the files we need to run the application. No code files and no project files here, bub!

    Note on #4. The only reason I am creating a separate NHibernate file (rather than using the one already in the app folder) is because I want my releases to go against a different database than they do in the normal, everyday testing of the application. That database gets blown away and re-created every time I run my integration tests and I don't want that affecting any testing the QA department is doing on the latest deployment. So the database name is ${database.name.test} rather than ${database.name.dev}. Note that everything here is running on the same server so I don't need to differentiate the server names.

    Note on #5. Why have a separate folder for the latest release that is simply a copy of another folder? That's the subject of the next post where I go into the deployment target.

    Kyle the Anticipatory

    * I remind you that there are no refunds

     

More Posts