Creating an Umbraco 5 package
After a few weeks of tinkering, my first real package for Umbraco v5 is finally here! And… surprise, surprise, it's another incarnation of the Cultiv Contact Form package.
Disclaimer: This has been tested to work with changeset 5232 (d9cbdf1252fe) and will NOT work with beta 1. So, if you're confident, use the mentioned version, if not, wait until the next release before you can try these steps.
Also, this is not really all that hard right now, but will be simpler as the Umbraco team is nearing a final version.
So the approach I've taken for the previous Razor version for v4.7+ has been a bit hacky as I'm doing everything inside one big Razor file. That's not quite as Clean Code as I would like it to be (at all!). So when I started thinking about doing it for v5, I wanted to do it in pure MVC style, separating out my Model, View and Controller.
I'm still learning MVC and slowly but steadily making progress. The first thing I did was create what I wanted for my package as a really simple plain MVC3 site as a proof of concept. It contained one Controller (ContactFormController), one ViewModel (ContactViewModel) and one View (Index).

It ran, it did exactly what I wanted it to do, yay! Now then, let's get this into Jupiter… Or so I thought. I soon realized that you can't just render a controller from within Umbraco content. V5 URL's are effectively all routes generated by Jupiter that lead to Umbraco's "base" controller (I'm not sure what it is called) and there is no such thing as a "Partial" controller. In fact, never mind that, I also cannot "magically" merge two actions on one page!
Luckily, the Jupiter team thought of this and Matt Brailsford pointed out to me that there was something called a SurfaceController, that I just needed to inherit from. I had a look at the TestSurfaceController in Umbraco's source and noticed that I should create a Guid and stick that as an attribute on my class to help Umbraco recognize it as a new SurfaceController. I also had to send the requestContext to the base class.

On to the next step then, implementing my Index action, for that we don't need to do much, just annotate the DisplayForm method with [ChildActionOnly].
Next, we have to update the View, instead of using the usual Html.BeginForm (default MVC), we need to use Html.BeginSurfaceForm so that Umbraco knows it's dealing with a SurfaceController. In the background it will do some "magic" for you, namely: produce a form tag that in the target adds your SurfaceController name, Action, GUID and ActionResult. Just so that when you post the form, Umbraco knows what code to execute. We need to specificy the action we're going to execute in there, in this case it's "HandleFormSubmit", have a look at the source code for the exact call.
Alright, we are ready to post some form data! Once we do, the form's action will be called and our form data can be interpreted in our HandleFormSubmit action annotated with the [HttpPost] attribute. We can then execute any code we need to actually validate and send an e-mail.
Now, in order for Umbraco to even consider your DLL's as being a plugin, you absolutely HAVE to open up your Properties\AssemblyInfo.cs file in notepad and add a using statement (using Umbraco.Cms.Web;) and at the very bottom add an attribute: [assembly: AssemblyContainsPlugins]
I'm sure this is going to be the source of many questions on the forum: "My plugin isn't recognized, what gives?".. and as Matt has noted before, it'll be a great way to earn some easy karma. I don't want to be creating my package manually each time so I used Matt's excellent instructions on automating package building using MSBuild for v5. All I can say is: just do it! It's a one-time 30-minute investment of your time to create this setup and it will save you loads of time in the future.
This package is 100% open source, as usual and it includes the build script and the whole setup for you to inspect, go have a look.
So, you install the package simply through the installer in Umbraco's backoffice. A few things to note: The .nupkg file will be place in your local package repository (~/App_Data/Umbraco/LocalPackages) and, once installed, it is unpacked in ~App_Plugins/Packages/YourPackageName.Version.Number.
After installing the package, you can add the contact form as a macro in Umbraco. Create a new macro of the type ChildAction and give it a name (ContactForm would be logical). In the ChildAction dropdownlist, look for the CultivContactFormSurface.Index action and select it. Don't forget to save the macro.
One more thing, you need to have some valid SMTP settings (with a "from" address) in your web.config, in my case I just write mails into a folder (that needs to be writable for the application pool user):
<system.net>
<mailSettings>
<smtp
deliveryMethod="SpecifiedPickupDirectory" from="info@site.com">
<network
host="localhost" />
<specifiedPickupDirectory
pickupDirectoryLocation="C:\inetpub\mailroot" />
</smtp>
</mailSettings>
</system.net>
To wrap it all up, you are now ready to put the macro on a template, go into the Settings section and find the template on which you want to show the contact form. You can add it like so: @Html.UmbracoMacro("contactForm") (where "contactForm" is the alias of the macro). Save the template and go have a look, the form should be there and working!
A HUGE "High five, you rock!!" goes out to Shannon, whom I've been stalking over the past few weeks to get to where we are now. I kept wondering when he was going to tell me to just shut up and go find something better to do! ;-) But instead he was patient and awesome and I go to this point mostly because of him.
Just a final note, this is very much a proof of concept, so that people can get up and running, there is loads of improvement possible and I'll be adding to the source to make sure it's actually usable.
For the record I had a skype call with Shannon this morning where he gave *you* a high five for helping out making this happen.
So don't shut up, quite the contrary. You're helping *big time* making U5 what it is!
Thanks!
That's very good to hear, I just really want to praise Shannon for putting up with tons of my n00b questions and working incredibly hard to make this work as easy as it is at the moment! :-)
Nice work Sebastiaan!
Just wanted to ask why you need to use Notepad to add the AssemblyContainsPlugins attribute to your project?
You can edit your AssemblyInfo.cs file directly in your visual studio project, you can find the file listed under the 'Properties' node of your project. Also, there's nothing stopping you from just adding the
[assembly: AssemblyContainsPlugins]
to any c# file that you have in your proj
I don't know why I said to do it in notepad.. must've been thinking of the .csproj file! I'll update the post.
Cool that it can be in any C# file! I plan to let the packager (MSBuild) handle it if possible.
Thanks for the tutorial.
I ran package.cmd and created the output Build files. When I try and install via developer menu -> local packages I get the following error
The RouteData must contain an item named 'id' with a non-null value
Am I missing a step here to make it installable?
I tried 3 things:
1. Uploading CultivJupiterContactForm.dll
2. Uploading Package.nuspec
3. Zipping the entire build folder and uploading the .zip file
Regards,
Trevor
Sorry Trevor, this works with the version that was out in November, API's have changed a little since then and I haven't been able to update the source yet.
Feel free to give it a try yourself, and make sure to read Shannon's blog posts about Surface Controllers as well at http://shazwazza.com/
There an issue for this here which I'm about to debug: http://issues.umbraco.org/issue/U5-79?query=for%3A+me But I've just noticed that you're talking about .Zip files. I'm not sure how you are creating your package but these are not v4 packages, these are v5 = Nuget packages which are not .Zip files. I have a feeling this is where the issue is occuring. Please follow the issue I've posted to keep an eye on whats happening.
This is fantastic. I've managed to get it working and make a few personal adjustments.
One thing I'm desperate to figure out is how to set up and pass Macro Parameters through to the Surface Controller... any ideas?
Never mind... I figured it out. I just add parameters in the child action. Nice.
Well everyone. Wish me luck. I'm going to pour over your posts and see if I can't integrate our site tools into UmbracoV5...a complete MVC rewrite from ASP classic for me!
Hi Sebastian
I am working with Visual Web Developer 2010 express.I have created two class files,one for surfacecontroller named "ContactSurfaceController" and put in Controllers folder.The second class file for Contact Model and put it in Models folder.I have given namespace for controller as MyProject.Controller and for Model as MyProject.Model.The problem is I cannot access the MyProject.Model namespace in my controller or in my view.Can you suggest what the problem can be