4.11 Using SurfaceController Child Action with Post
Hi,
I need some help with using Surface Controller's as Child action. The child action renders just fine, but when doing a post it breaks after returning the CurrentUmbracoPage() as ActionResul.
The exception that is returned
The particular return just before the exception is thrown.
In the umbraco source I can find this particular code:
//validate that the current page execution is not being handled by the normal umbraco routing system if (!context.RouteData.DataTokens.ContainsKey("umbraco-route-def")) { throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); }
In the "Standard_Website_MVC_1.1" project I noticed that in the Contact form example it is jusing a Partial View action instead, but I think it's nicer to use child actions for this.
So, this brings me to the next question. Is it possible to use a SurfaceController with a child action that needs to handle post form validation and return the current page ?
Please let me know if you need more information about my setup.
I think you're missing Surface in your controller name.
ContactFormController should be ContactFormSurfaceController, or it won't be detected as a SurfaceController and throw the exception you mention.
But that won't get you were you (and me) want to be either. You'll just get another exception: The model item passed into the dictionary is of type 'Umbraco.Web.Models.RenderModel', but this dictionary requires a model item of type 'YourModel'.
I agree it would be a lot cleaner to be able to post from a (child)action, but that doesn't seem possible atm. Which kind of defeats the purpose of using MVC - the form is in a shared partial view - 'triggered' from the template's view - posting to an unrelated (surface) controller.
The error is occurring because you are calling ContactForm yet there are 2 ambiguous signatures that support calling those methods with no parameter, try specifying unique action names or specifying an ActionName attribute on one of them.
My view has this model: @inherits Umbraco.Web.Mvc.UmbracoViewPage<Site.Core.Models.ContactFormModel>. Also at this moment the controller is called ContactFormSurfaceController, so thats not the case any longer.
I know they are unique signatures but I think they might have to be differently named, do you get this when you initially load the page or after posting?
I don't get this issue, I have a validating form and it works fine, providing Ihave unique action names, what is the exact exception you are getting after using unique actions?
[ChildActionOnly]
public ActionResult Contact()
{
var contactModel = new ContactModel();
return PartialView(contactModel);
}
public ActionResult ContactPost(ContactModel model)
{
if (!ModelState.IsValid)
{
return CurrentUmbracoPage();
}
Exception
The model item passed into the dictionary is of type 'Umbraco.Web.Models.RenderModel', but this dictionary requires a model item of type 'ContactModel'.
I've emailed you, but here is what I think the issue is:
I think I’ve found a caveat here, basically your POST action should be the same name as your template, I can only guess this is because when it returns the current page it passes the model through your template (I am guessing here though), I don’t know whether this is intentional or not but it’s the only way I can get it to work, and it’s how my implementation works, here’s the new sigantures in the controller:
[ChildActionOnly]
[ActionName("ContactForm")]
public ActionResult Contact()
{
return PartialView("ContactForm", new ContactModel());
}
[HttpPost]
public ActionResult Contact(ContactModel model)
{
if (!ModelState.IsValid)
{
return CurrentUmbracoPage();
}
Then rename your Contact.cshtml partial to ContactForm.cshtml. I have got this working successfully here using the code you sent me.
Thanks for looking at the code - I really appreciate it. Your solution does seem to work!
I still think we're onto a bug here. It doesn't make sense (to me at least) that the action invoked on a SurfaceController influences the rendering, and only in this specific case.
I have to admit I'm not familiar with the new routing and rendering code yet, but this might be a good opportunity to get up to speed on it. Maybe it will make more sense then - in that case this behavoir should be documented and explained.
I have replaced all the form's with the solution, but now I ran in another quirk. It seems that the ViewData is no longer passed to the model in this setup.
When you execute a @Html.Action command, MVC does not pass/merge up the current view's ViewData dictionary to the ChildAction, though it does merge up the ModelState dictionary which is why validation works (though I cannot seem to find out where this occurs in the MVC source). If you want to access the ViewData that you've set on the master ViewContext's on a ChildAction being rendered from the master's ViewContext then you need to use:
The ParentActionViewContext in this example is the ViewContext that is rendering the Umbraco template, not the ChildAction. That is because when you POST (whether inside of Umbraco or normal MVC), you are posting to a new Action and the rendering process starts from scratch, when you validate your model, update the ViewData, etc... this all happens on what will become the 'master' ViewContext when the view renders. This view then will render your ChildAction.
@Jon: Not sure I am understanding your question correctly. This will return the value for an item called "Prompt" that was set in the "parent" view context:
because 'Watermark' is not a property of ModelMetadata (System.Web.Mvc.ModelMetadata). If you had set an item called "Watermark" in the parent view context you could retreive it by this:
However I'm having issues getting the values set in the back office from the Surface Controller into the form model anyway so until I can figure this out the placeholder is moot no matter which way I go about it!
I am getting this error.
"The model item passed into the dictionary is of type 'Umbraco.Web.Models.RenderModel', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[abo.Models.Phone]'."
4.11 Using SurfaceController Child Action with Post
Hi,
I need some help with using Surface Controller's as Child action.
The child action renders just fine, but when doing a post it breaks after returning the CurrentUmbracoPage() as ActionResul.
The exception that is returned
The particular return just before the exception is thrown.
In the umbraco source I can find this particular code:
//validate that the current page execution is not being handled by the normal umbraco routing system
if (!context.RouteData.DataTokens.ContainsKey("umbraco-route-def"))
{
throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form");
}
In the "Standard_Website_MVC_1.1" project I noticed that in the Contact form example it is jusing a Partial View action instead, but I think it's nicer to use child actions for this.
So, this brings me to the next question. Is it possible to use a SurfaceController with a child action that needs to handle post form validation and return the current page ?
Please let me know if you need more information about my setup.
Kind regards
I think you're missing Surface in your controller name.
ContactFormController should be ContactFormSurfaceController, or it won't be detected as a SurfaceController and throw the exception you mention.
But that won't get you were you (and me) want to be either. You'll just get another exception:
The model item passed into the dictionary is of type 'Umbraco.Web.Models.RenderModel', but this dictionary requires a model item of type 'YourModel'.
I agree it would be a lot cleaner to be able to post from a (child)action, but that doesn't seem possible atm.
Which kind of defeats the purpose of using MVC - the form is in a shared partial view - 'triggered' from the template's view - posting to an unrelated (surface) controller.
The error is occurring because you are calling ContactForm yet there are 2 ambiguous signatures that support calling those methods with no parameter, try specifying unique action names or specifying an ActionName attribute on one of them.
@ Timothy
My view has this model: @inherits Umbraco.Web.Mvc.UmbracoViewPage<Site.Core.Models.ContactFormModel>.
Also at this moment the controller is called ContactFormSurfaceController, so thats not the case any longer.
The view contains this
@using(Html.BeginUmbracoForm<Site.Core.SurfaceControllers.ContactFormSurfaceController>("ContactForm"))
I also tried the regular
@using(Html.BeginForm())
so the form is going to be submitted to the page's url (/contact)
@Kevin
I do have different signatures. The post action has the model passed as parameter
At this point I'm using the partial as solution but I think it's not nice at all..
ignore this post
Hi Stephen
I know they are unique signatures but I think they might have to be differently named, do you get this when you initially load the page or after posting?
Hi Kevin,
Initially it works fine. This exception occures when you post the form.
Hi Stephen
Did you trying using unique action names?
Kev
Hi Kevin,
I tried unique action names in my initial quest but it didn't matter - as expected.
Using a Surface controller will get you past the error from the first post.
But you'll hit the one I mentioned earlier.
Seems like were stuck with using partial views for forms atm.
It does look like using a different action name for post does work
In the template I do this
In the ContactForm view I do this and this does work.
Thanks!
Glad it works :-)
It won't work for me if I do something like this - you'll get the same exception again.
if (!ModelState.IsValid) { return CurrentUmbracoPage(); }
I don't get this issue, I have a validating form and it works fine, providing Ihave unique action names, what is the exact exception you are getting after using unique actions?
Exception
The model item passed into the dictionary is of type 'Umbraco.Web.Models.RenderModel', but this dictionary requires a model item of type 'ContactModel'.
Template View
ContactSurface\Contact.cshtml
@Timothy
You don't have a [HttpPost] attribute above your ContactPost action.
Kev
Hi Kev,
I actually do - was just struggling with the wysiwyg editor & must have deleted it by accident.
It shouldn't make at difference either I think?
/Edit: Also, the view looks like this (hoping it displays correctly this time around)
It will make a difference, but if you already have it then that's fine.
The type in your Contact view should be:
Jep, it's there.
It actually executes the correct action (ContactPost) as well, but throws somewhere after it returns CurrentUmbracoPage().
Hmm, this is a bit baffling then, that all seems legit, feel free to email me some of the code if you want me to investigate further.
Sounds great - where can I contact you?
/edit: email removed
I've emailed you, but here is what I think the issue is:
[ChildActionOnly]
[ActionName("ContactForm")]
public ActionResult Contact()
{
return PartialView("ContactForm", new ContactModel());
}
[HttpPost]
public ActionResult Contact(ContactModel model)
{
if (!ModelState.IsValid)
{
return CurrentUmbracoPage();
}
Hi Kev,
Thanks for looking at the code - I really appreciate it.
Your solution does seem to work!
I still think we're onto a bug here.
It doesn't make sense (to me at least) that the action invoked on a SurfaceController influences the rendering, and only in this specific case.
I have to admit I'm not familiar with the new routing and rendering code yet, but this might be a good opportunity to get up to speed on it.
Maybe it will make more sense then - in that case this behavoir should be documented and explained.
Thanks again!
Hi Timothy
Agreed, I think this is a bug, it should make no difference what the action is called, might be worth one of us logging this behaviour here:
http://issues.umbraco.org/dashboard/#tab=Hints
Kev
Hi All,
I have replaced all the form's with the solution, but now I ran in another quirk.
It seems that the ViewData is no longer passed to the model in this setup.
So when I do for example:
It ends up empty.
I will create an issue for it on the bug tracker.
When you execute a @Html.Action command, MVC does not pass/merge up the current view's ViewData dictionary to the ChildAction, though it does merge up the ModelState dictionary which is why validation works (though I cannot seem to find out where this occurs in the MVC source). If you want to access the ViewData that you've set on the master ViewContext's on a ChildAction being rendered from the master's ViewContext then you need to use:
@ViewContext.ParentActionViewContext.ViewData["ErrorMessage"]
The ParentActionViewContext in this example is the ViewContext that is rendering the Umbraco template, not the ChildAction. That is because when you POST (whether inside of Umbraco or normal MVC), you are posting to a new Action and the rendering process starts from scratch, when you validate your model, update the ViewData, etc... this all happens on what will become the 'master' ViewContext when the view renders. This view then will render your ChildAction.
Hope that helps.
@Shannon, et al,
Sorry to hijack but out of curiosity, why wouldn't this work for either
"ViewContext.ParentActionViewContext.ViewData["Prompt"]"
or
"ViewContext.ParentActionViewContext.ViewData.ModelMetadata.Watermark"
in order to get the value of the placeholder for a form field of a child action?
@Jon: Not sure I am understanding your question correctly. This will return the value for an item called "Prompt" that was set in the "parent" view context:
ViewContext.ParentActionViewContext.ViewData["Prompt"]
this will never work however:
ViewContext.ParentActionViewContext.ViewData.ModelMetadata.Watermark
because 'Watermark' is not a property of ModelMetadata (System.Web.Mvc.ModelMetadata). If you had set an item called "Watermark" in the parent view context you could retreive it by this:
ViewContext.ParentActionViewContext.ViewData["Watermark"]
Or by this:
ViewContext.ParentActionViewContext.ViewBag.Watermark
The latter is using the dynamic ViewBag property which is just a dynamic wrapper on the ViewData.
Shannon,
I was trying to figure out how to use the Prompt property of the System.ComponentModel.DataAnnotations.DisplayAttribute so I could pass in placeholder text from the backoffice based on the SO conversation: http://stackoverflow.com/questions/5824124/html5-placeholders-with-net-mvc-3-razor-editorfor-extension
However I'm having issues getting the values set in the back office from the Surface Controller into the form model anyway so until I can figure this out the placeholder is moot no matter which way I go about it!
Thanks for your help!
Jon
Is there a way to avoid postback here and display message to user? On success or error?
I am getting this error. "The model item passed into the dictionary is of type 'Umbraco.Web.Models.RenderModel', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[abo.Models.Phone]'."
For more details click here Click Here
Thanks.
is working on a reply...