Wednesday 16 December 2009

ASP.NET MVC Supporting Multiple Actions Through a Single Form

One of the things that always plagues me in ASP.NET MVC is supporting multiple actions through a single form.  Sometimes this is not a good idea, but often we need to do it to support multiple functions on the same data.  I’ve tried using javascript to override the form action but it takes a lot of complicated code and is much harder when using ajax calls. I’ve tried calling a single action and then splitting out the data to call the sub methods but unless you use redirections it makes your methods much harder to test and you lose your form if you redirect, plus the code is messy.

Finally I can across this post by MAARTEN BALLIAUW.  It specifies that you can use a custom attribute to determine which action is valid depending on other parameters.  It’s very smart and I highly suggest you read it, in fact I’m going to assume at this point that you have.

I took it a little bit further and have some hints for you.  You’ll likely get an error first time you try it saying that there is ambiguity between the actions, be sure that your form is calling an action that does not exist.  This code is going to decide if an action is valid, if the Index action is valid and the form post variable also says your post action is valid then it’s going to throw this exception.  An action that doesn’t exist can’t be valid, so only the one related to your form post variable will be valid.

Also, I don’t like to use the value of the submit button.  The value is also what the user sees and is likely to be changed by the business.  For this reason all I want to do is verify that the form variable was submitted.

I’ve changed the code in the attribute to read:

   1: public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
   2: {
   3:    //return controllerContext.HttpContext.Request[MatchFormKey] != null &&
   4:    //    controllerContext.HttpContext.Request[MatchFormKey] == MatchFormValue;
   5:    // Commented out because we don't want to match the value, just the key.
   6:  
   7:    if (MatchFormValue == null)
   8:    {
   9:        return controllerContext.HttpContext.Request[MatchFormKey] != null;
  10:    }
  11:    else
  12:    {
  13:        return controllerContext.HttpContext.Request[MatchFormKey] != null &&
  14:            controllerContext.HttpContext.Request[MatchFormKey] == MatchFormValue;
  15:    }
  16: }

It’s a fantastic solution so far, my thanks to Maarten.

Thursday 10 December 2009

ASP.NET MVC 2 Areas in a Converted ASP.NET Application

I recently converted our ASP.NET Application to ASP.NET MVC 2.  This allows me to run ASP.NET pages along side ASP.NET MVC pages without any trouble.  Well we have a big new functionality project coming up and it would be a shame not to take advantage of the ASP.NET MVC 2 areas.  The areas  tutorial on MSDN is actually extremely good showing all you need to know on how to setup areas, but it won’t show you how to set it up in a converted ASP.NET web app. 

It’s not too much different really, just a small change.  In the step “Enabling the Custom Build Step for MVC Areas Projects” you’re asked to un-commend some information from the csproj files.  The ASP.NET application does not have this  section.  Fortunately you can jsut copy and paste it in after the already commented out “AfterBuild” section.  It’s pasted below, ready for the master web app.

   1: <!-- To enable MVC area subproject support, uncomment the following two lines: -->
   2:   <UsingTask TaskName="Microsoft.Web.Mvc.Build.CreateAreaManifest" AssemblyName="Microsoft.Web.Mvc.Build, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
   3:   <UsingTask TaskName="Microsoft.Web.Mvc.Build.CopyAreaManifests" AssemblyName="Microsoft.Web.Mvc.Build, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
   4:  
   5:   <Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
   6:     <PropertyGroup>
   7:       <AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
   8:     </PropertyGroup>
   9:     <!-- If this is an area child project, uncomment the following line:
  10:     <CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
  11:     -->
  12:     <!-- If this is an area parent project, uncomment the following lines: -->
  13:     <CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
  14:     <CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
  15:  
  16:   </Target>
  17:   <Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
  18:     <AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
  19:   </Target>

Take all of it, you’ll need it.  Once you’ve done that and reloaded the site (be sure to set your startup project again) you’re good to go.  Also bear in mind that your project should have the same name as the area path, if you want http://localhost/MyNewArea/Controller/Action/id then you should call your project MyNewArea not MyNamespace.MyNewArea.  When you build the views are placed into the Areas folder of the parent project, they are placed under the folder which is the namespace of the project.  There could be a way around this, let me know if you have one, but it’s pretty easy to keep the namespace simple.

Tuesday 8 December 2009

Continuous Integration – Why use MSBuild?

I’ve always loved continuous integration.  Usually I use CruiseControl.net for my .NET applications, and really it doesn’t matter which environment you use as long as it supports MSBuild.  But why should you use MSBuild?  There are many alternatives out there, NAnt being the most popular, but they don’t get a lot of support, certainly not as much as Microsoft puts behind MSBuild.  Microsoft is not known for inventing the wheel, they quite publicly take good ideas and do their best to make them better. 

The style of MSBuild is very similar to NAnt, an XML based build script.  Project files in visual studio are now written in MSBuild script.  You can edit them by hand, add pre and post build events if you want to.  The script is very straight forward, if you’ve used Ant before then you’ll be familiar with it.  Create targets, dependencies, run executables and build project files.  Support for building projects and solutions is built in, file system interface is powerful and easy to use.

Item Groups are great, if you want to process a bunch of files but disregard certain file types you can setup an item group that defines the rule.

   1: <ItemGroup>
   2:     <!--This group of files will be published as part of the website.-->
   3:     <MySourceFiles Include="..\WebProject\**\*" 
   4:                    Exclude="..\WebProject\**\.svn\**;..\HotelsCombined.WebUI\**\*.pdb"/>
   5:   </ItemGroup>
The above example shows how to define an item group for a web project that takes all files from the project except the subversion source and the pdb files.  Useful if you want to deploy a site.
   1: <Target Name="PublishDevelopment" DependsOnTargets="Compile">
   2:   <Delete Files="Publish\**\*"/>
   3:   <Copy SourceFiles="@(MySourceFiles)" 
   4:         SkipUnchangedFiles="true"
   5:         DestinationFiles="@(MySourceFiles->'Publish\%(RecursiveDir)%(Filename)%(Extension)')"/>
   6:   <Copy SourceFiles="development.web.config" DestinationFiles="Publish\web.config"/>
   7: </Target>
The above example will take the MySourceFiles item group and copy it to my output directory, then take my development.web.config and replace the web.config.

Properties passed in get pushed up the tree, so if you pass /p:Configuration=Release into the script it will push it up to any build files that you call.

Why use it over another tool such as NAnt?  Well NAnt can do most things that MSBuild can do really and if you do use NAnt you’ll get a lot of community support.  Both are supported by the popular continuous integration environments.

The Clincher

For me it boils down to two things, Microsoft is heavily supporting MSBuild and support is really important to me.  New features will be added and Microsoft will continue to advance the product as they are very heavily invested in it. NAnt has been in beta since 2007.  If I want to be sure that .NET 4.0 will be supported I’ll go with MSBuild.  The other feature is IDE integration.  MSBuild is supported in Visual Studio out of the box, no extensions necessary.  It’s not as important as the support issue, but it’s nice to have.