November Happy Hour will be moved to Thursday December 5th.

MVC form block

Vote:
 

I have a block for contact form but I cannot get the post to work. Whenever I try to post I get a 404 not found.

My form view is quite simple as I have not yet added any fields.

@model ContactFormBlockViewModel

@using (Html.BeginForm("Send", "ContactFormBlock", FormMethod.Post))
{
    <input type="submit" value="Send" class="button" />
}

The controller:

public class ContactFormBlockController : BlockControllerBase<ContactFormBlock>
	{
		public override ActionResult Index(ContactFormBlock currentBlock)
		{
			var vm = currentBlock.CreateBlockViewModel<ContactFormBlockViewModel, ContactFormBlock>();
			return PartialView("Blocks/ContactFormBlock", vm);
		}

		[HttpPost]
		public ActionResult Send(ContactFormBlock currentBlock, ContactFormBlockViewModel vm)
		{
			var currentVm = currentBlock.CreateBlockViewModel<ContactFormBlockViewModel, ContactFormBlock>();

			return PartialView("Blocks/ContactFormBlock", currentVm);
		}
	}

The model: (the attached file property is not used, but will be in the future)

public class ContactFormBlockViewModel : BlockViewModel<ContactFormBlock>
	{
		public AttachedFileModel AttachedFile { get; set; }
	}

The block is placed on a page with the URL localdev.se/sv/mmx.

When I try to post the form it redirects to  localdev.se/ContactFormBlock/Send and gets a 404 not found.

What have I missed? Is there any custom routing to be done?

Thanks for helping.

#87861
Jun 24, 2014 16:06
Vote:
 

I had the same problem a few days ago. Seems putting the form element in the block makes the URL not point to an EPiServer page. I hade to put the @using (Html.BeginForm statement on the page, surronding the block view. I found a few blog posts out there concerning forms in blocks but none helped me much.

I guess you have to find some smart way to solve routing between the page controller and the block controller to get it working (if you can't live with putting the post logic in the page controller and the BeginForm on the page view).

#87875
Edited, Jun 24, 2014 18:25
Vote:
 

Try these
1. Check the HTML of rendered page and see what is action path of the page 
2. Move the View in Shared Folder
3. @using (Html.BeginForm("Send", "ContactFormBlock", new {language= ContentLanguage.PreferredCulture.Name}))

Regards
/K

#87876
Jun 24, 2014 18:32
Vote:
 

BeginForm doesnt know about EPiServer page language.

Try the HtmlHelper described here http://www.danielnordmark.se/language-specific-forms-episerver-7/

When the POST goes to "/ContactFormBlock/Send", EPiServer cannot know which "currentBlock" instance you were POST'ing from, so it cannot bind that parameter In your Send action.

#87878
Jun 24, 2014 18:41
Vote:
 

Thanks for the fast replies, it's much appreciated.

I tried adding the language branch but that didn't help.

I have now stumbled upon another, more grave bug. When I have multiple form block on the same page, the post for one form triggers them all.

How do solve this? Is there any way to pass along some unique ID for the post?

#87901
Jun 25, 2014 11:30
Vote:
 

you can create an extra Property in Block in itself and render in View

/K

#87921
Jun 25, 2014 16:02
Vote:
 

Yes, but I can't get my head around how I would route with page and block controllers.

#87974
Jun 26, 2014 16:39
Vote:
 

Me neither, that's why I gave up and put the form tag on the page view. If you have multiple form blocks, you need to surround each block with its own form tag in the page view to avoid posting all forms on the page. You also need to put a submit button for each block inside each form. 

I guess there mightbe a better way, where you can have the block handle each form itself, but in my case I don't have the time to figure it out. Please let me know if you find a way!

#87978
Jun 26, 2014 16:49
Vote:
 

Hannes, did you ever find a solution to this? Where the Block controller can handle the post?

#119163
Edited, Mar 23, 2015 13:33
Vote:
 

Hi, from MVC perspective any incoming form submit request safely mapps to controller and action that should be invoked.

If you already prelimenary know what kind of block instances will be used on the page, you could get an idea from my solution (we had defined list of the possible form blocks on the page - we knew already that the same form will be used 3 times for instance to submit different stuff to the server):

a) Add secret property in block and expose it in templates (like hidden field)

b) Have multiple actions to handle each action in controller

c) Add attribute to each action with required matched secret field (hidden field in template):

[HttpPost]
[ActionName("Index")]
[FormValueRequired("HiddenField1")]
public async Task<ActionResult> Action1(BlaBlaPage currentPage, 

[HttpPost]
[ActionName("Index")]
[FormValueRequired("HiddenField2")]
public async Task<ActionResult> Action2(BlaBlaPage currentPage, 

[HttpPost]
[ActionName("Index")]
[FormValueRequired("HiddenField3")]
public async Task<ActionResult> Action3(BlaBlaPage currentPage, 

Attribute itself:

public class FormValueRequiredAttribute : ActionMethodSelectorAttribute
{
    private readonly FormValueRequirement _requirement;
    private readonly string[] _submitButtonNames;

    public FormValueRequiredAttribute(params string[] submitButtonNames) : this(FormValueRequirement.Equal, submitButtonNames)
    {
    }

    public FormValueRequiredAttribute(FormValueRequirement requirement, params string[] submitButtonNames)
    {
        //at least one submit button should be found
        _submitButtonNames = submitButtonNames;
        _requirement = requirement;
    }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        foreach (var buttonName in _submitButtonNames)
        {
            try
            {
                var value = "";
                switch (_requirement)
                {
                    case FormValueRequirement.Equal:
                    {
                        //do not iterate because "Invalid request" exception can be thrown
                        value = controllerContext.HttpContext.Request.Form[buttonName];
                    }
                        break;
                    case FormValueRequirement.StartsWith:
                    {
                        foreach (var formValue in controllerContext.HttpContext.Request.Form.AllKeys)
                        {
                            if (formValue.StartsWith(buttonName, StringComparison.InvariantCultureIgnoreCase))
                            {
                                value = controllerContext.HttpContext.Request.Form[formValue];
                                break;
                            }
                        }
                    }
                        break;
                }
                if (!String.IsNullOrEmpty(value))
                {
                    return true;
                }
            }
            catch (Exception exc)
            {
                //try-catch to ensure that 
                Debug.WriteLine(exc.Message);
            }
        }
        return false;
    }
}
#119225
Mar 24, 2015 21:51
Vote:
 

I think this post could be good to read about this:

http://thisisnothing.nystrom.co.nz/2013/11/19/using-custom-forms-in-an-episerver-mvc-block-template/

#119238
Mar 25, 2015 9:45
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.