Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

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.

like image 210
PeteM Avatar asked Jun 25 '13 09:06

PeteM


1 Answers

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.

like image 102
PeteM Avatar answered Oct 23 '22 17:10

PeteM