The issue is that you are not specifying a ViewName
in the return of the SaveValue()
action, therefore it is trying to locate a View with the name of the action, i.e. myFeature/SaveValue.cshtml. The error is telling you it could not find this file.
You have two options. Either specify the specific view to render for the action:
[HttpPost]
public ActionResult SaveValue(myViewModel viewModel)
{
_myRepository.SaveData(viewModel.Value1);
return View("~/Views/myFeature/Index.cshtml", viewModel);
}
Or change the Action Name to match the view (i.e. Index). Since the method signatures are different this will compile and the Accept Verbs Attribute will ensure the correct action is called depending on whether a Get or Post is made:
[HttpPost]
public ActionResult Index(myViewModel viewModel)
{
_myRepository.SaveData(viewModel.Value1);
return View(viewModel);
}
Don't forget to change the helper in the form as well:
@Html.Sitecore().FormHandler("myFeature", "Index")
The above is best used with AJAX posts, since you have the issue of only the specific view being returned. You could redirect in your [HttpPost]
action to the thank you page (using return this.Redirect("/url/to/redirect-page")
) but this causes issues with server side form validation.
using return View will interrupt Sitecore’s page load and return that view only.
...
A controller rendering executes much later in page life cycle than a true ‘controller’. By the time your [HttpPost] action executes, the page is already partially loaded.
I would recommend dealing with this issue in a similar manner to how it is solved in Sitecore Habitat demo.
Create a new attribute that will be used to determine the selection of an action method on form submission:
public class ValidateRenderingIdAttribute : ActionMethodSelectorAttribute
{
internal const string FormUniqueid = "uid";
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var ignoreCase = StringComparison.InvariantCultureIgnoreCase;
var httpRequest = controllerContext.HttpContext.Request;
var isWebFormsForMarketersRequest = httpRequest.Form.AllKeys
.Any(key => key.StartsWith("wffm", ignoreCase) && key.EndsWith("Id", ignoreCase));
if (isWebFormsForMarketersRequest)
{
return false;
}
string renderingId;
if (!httpRequest.GetHttpMethodOverride().Equals(HttpVerbs.Post.ToString(), ignoreCase) || string.IsNullOrEmpty(renderingId = httpRequest.Form[FormUniqueid]))
{
return true;
}
var renderingContext = RenderingContext.CurrentOrNull;
if (renderingContext == null)
{
return false;
}
Guid id;
return Guid.TryParse(renderingId, out id) && id.Equals(renderingContext.Rendering.UniqueId);
}
}
Update your controller action to include this new attribute:
[HttpPost]
[ValidateRenderingId]
public ActionResult Index(myViewModel viewModel)
{
if (ModelState.IsValid())
{
_myRepository.SaveData(viewModel.Value1);
return View("~/Views/Comments/Thanks.cshtml", viewModel);
}
return View(viewModel);
}
Note the above relies on ModelState for validation and checks the data passes validation which may have been set on the model using attributes, and if so returns a different different "Thank you" view (since you probably do not want to show the form again). The model/form data is passed if you want to show the data in the confirmation.
Create a new HTML Helper which returns a unique rendering id hidden field:
public static class HtmlHelperExtensions
{
public static MvcHtmlString AddUniqueFormId(this HtmlHelper htmlHelper)
{
var uid = htmlHelper.Sitecore().CurrentRendering?.UniqueId;
return !uid.HasValue ? null : htmlHelper.Hidden(ValidateRenderingIdAttribute.FormUniqueid, uid.Value);
}
}
Finally, change the view to just use a regular MVC BeginRouteForm
and add the unique form input helper:
@using (Html.BeginRouteForm(MvcSettings.SitecoreRouteName, FormMethod.Post))
{
@Html.AddUniqueFormId()
@Html.TextBoxFor(x => x.Value1)
<input type="submit" value="Save" />
}
By following this approach you are using an approach which is much closer to standard ASP.Net MVC so hopefully it is easier to follow/workaround the in-built Sitecore problems. It also has the benefit that should you require multiple forms on the same page this this code can handle that too.
You can find examples from the Sitecore Habitat repo:
- AddUniqueFormId
- ValidateRenderingIdAttribute
- Example usage in Rendering View and Controller.
Further Reading
I would recommend reading these posts for further background information: