In Umbraco 6.1.1 MVC 4, how can I do a form post back to a surface controller using a model that inherits from Umbraco's RenderModel?
Asked Answered
K

1

10

The Scenario:

I'm building a site in Umbraco 6 with MVC - I'm fairly new to Umbraco but I've done everything so far by following tutorials etc, and for the most part everything works nicely.

So I have a "contact us" form built as a partial view, rendered with the following code:

@using (Html.BeginUmbracoForm("SendEmail", "ContactFormSurface"))
{

which posts back to my ContactFormSurfaceController:

public class ContactFormSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
    [HttpPost]
    public ActionResult SendEmail(ContactFormModel model)
    {

Now, my ContactFormModel inherits from the Umbraco RenderModel, and I am "hijacking" the route for my Contact Us view in a separate ContactFormController:

public class ContactFormController : RenderMvcController
{
    //
    // GET: /Contact-us/

    public override ActionResult Index(RenderModel model)
    {
        var contactFormModel = new ContactFormModel(model);
        return CurrentTemplate(contactFormModel);
    }

I want this so that I can have flexible headers and submit button text within the contact form based on Umbraco content. My ContactFormModel takes a RenderModel in it's constructor so that it has access to the underlying Umbraco content:

public class ContactFormModel : RenderModel
{
    #region Ctr

    public ContactFormModel(RenderModel model) : base(model.Content, model.CurrentCulture) {}

    #endregion


    #region Contact Form Fields

    [Display(Name = "Your Name")]
    [Required]
    public string Name { get; set; }

The Problem:

When the form posts back to my surface controller SendEmail method, it appears there is an attempt to instantiate a new ContactFormModel, and I get a YSOD with the following exception:

No parameterless constructor defined for this object.

My first thought was, ok, I'll supply a parameterless constructor, since I don't actually need access to the Umbraco content within the SendEmail surface controller method, I only want that when initially rendering the view. But that's not so easy, since the base RenderModel requires an IPublishedContent object passed to it's constructor. I tried just passing null, and also:

public ContactFormModel() :base(new DynamicPublishedContent(null)) {}

but that results in a "Value cannot be null" exception.

I then tried changing my Umbraco form declaration to:

@using (Html.BeginUmbracoForm("SendEmail", "ContactFormSurface", new {model = @Model}))

to ensure that the ContactFormModel being passed to the view is sent back to the surface controller. This gets past the YSOD, but within the surface controller SendEmail method, "model" is null.

So there's a couple of things I don't really understand:

  1. Why is there an attempt to call a parameterless constructor on my ContactFormModel in the first place? Why is my ContactFormModel from the view not just available in the surface controller method, since that is what I've specified?

  2. When I explicitly add the model to the route values for the form post, why does it come through as null?

It feels like there must be simple solution to this, and I'm maybe missing something fundamental. Searching the forums there are plenty of examples of hijacking routes and inheriting from RenderModel, and also using a custom model and surface controller to process a form post, but not the 2 things combined, when the custom model inherits from RenderModel.

If I can't find a solution to this, I'll have to resort to not inheriting from RenderModel and hence not allowing any editable content within the contact us form, which seems to defeat the object of Umbraco. Or, create another model purely for use with the surface controller that doesn't inherit from RenderModel but duplicates all the fields in my ContactFormModel, which would be plain crazy!

Thanks for any ideas or advice.

Kiddy answered 25/6, 2013 at 9:20 Comment(0)
K
16

Ok, I've had no responses to this question but am now in a position to answer it myself. Maybe it was a fundamental oversight, but not that obvious imho, and information on the Umbraco forum etc about inheriting from RenderModel is fairly limited.

Essentially the answer, as was my first instinct, is to solve the original exception "No parameterless constructor defined for this object" by providing a paramaterless constructor. The difficulty is working out what to put inside the parameterless constructor for my model, since it inherits from the Umbraco RenderModel which requires an IPublishedContent instance passed to it's constructor.

Luckily while browsing around I happened across this post on the Umbraco forum: http://our.umbraco.org/forum/developers/api-questions/40754-Getting-CurrentPage-from-SurfaceController.

The thing I had not fully understood was how the current page/view information is passed through Umbraco Context. So as per the above post, I created a couple of new constructors for my custom model:

    public ContactFormModel() : this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId)) {}
    public ContactFormModel(IPublishedContent content) : base(content) {}

With these in place, when I now post the form back to my Surface Controller method, as if by magic, my ContactFormModel is populated with all the details entered.

Kiddy answered 9/7, 2013 at 15:9 Comment(2)
Thanks for the information - the Umbraco documentation should really be better here, posting back with a custom model is the absolute basics.Poss
Sadly, this is because the whole of Umbraco is a work in progress. It's a fair exchange that almost all open-source project make. A kind of "we'll show you ours if you show us yours" trade where you get something for free but have to pay it with your own sweat. I know it can be maddening but this is the nature of anything cutting edge. If you don't want this kind of risk, then please consider using older versions since the MVC implementation is a work-in-progress.Giamo

© 2022 - 2024 — McMap. All rights reserved.