Server error with XForms

Vote:
 

Hi All

I am having problems with a XForms as a property on a page within my MVC Site. When I submit the form I just get the following error:

The page cannot be displayed because an internal server error has occurred.

From my log files I have the following error:

2013-10-01 11:29:40,089 [124] ERROR EPiServer.Global: 1.2.5 Unhandled exception in ASP.NET
System.NullReferenceException: Object reference not set to an instance of an object.
at EPiServer.Web.Mvc.XForms.XFormValidator.<Validate>d__2.MoveNext()
at EPiServer.Web.Mvc.XForms.XFormPostModelBinder.Validate(ControllerContext controllerContext, ModelBindingContext bindingContext, XFormPostedData xFormPostedData)
at EPiServer.Web.Mvc.XForms.XFormPostModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at EPiServer.Web.Mvc.XForms.XFormUnknownActionHandlerBase.GetXFormPostedData(Controller controller, String actionName)
at EPiServer.Web.Mvc.XForms.XFormUnknownActionHandlerBase.HandleAction(Controller controller)
at EPiServer.Web.Mvc.ActionControllerBase.HandleUnknownAction(String actionName)
at System.Web.Mvc.Controller.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult)
at System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

I have looked at the following articles about XForm with MVC but they all seem related to XForm in Blocks rather than pages:

http://blog.nansen.com/2013/03/creating-xform-block-in-episerver-7-mvc.html

http://cjsharp.com/blog/2013/04/11/creating-a-xform-block-in-episerver-7-mvc-with-working-validation-updated/

Does anyone have any idea what may be causing this? 

Many many thanks
Dave

#75617
Oct 01, 2013 12:31
Vote:
 

Hi Dave, did you manage to solve this problem meanwhile?

#77512
Nov 20, 2013 14:06
Vote:
 

Hi Dave,

 

I have the same problem, but not found the solution until now. 

I use EPi 7.1 MVC.

Sometimes after an iisreset the problem seems solved, but after a publish returns.

Is there a solution for this?

 

#82166
Mar 06, 2014 10:05
Vote:
 

Hello Everyone,

I have the same problem here. It's not random though. It's just something that we had working before but now fails every time. I initially discovered this while using EPi 7.13 and MVC4 running under .NET Framework 4.0.x. I have no idea when our XForm stopped working but it's giving us the exact same error message. I have now gone completely wild and upgraded everything EPi 7.14 with MVC(5) and .NET Framework 4.5.1!!

It was actually less painful than I had thought and I haven't found anything broken yet. BUT this thing still is! (Thank god for unit tests! I wish I had time to cover more of the site though since this thing managed to slip through the cracks).

My form is trying to POST to this controller action in my PageControllerBase:

[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult XFormPost(XFormPostedData xFormpostedData, string contentId)
{
	this._contentId = contentId;
 
	return this._xformHandler.HandleAction(this);
}

but yeah, I get the exact same error message without even entering that function. If I comment away xFormpostedData like this:

[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult XFormPost(/*XFormPostedData xFormpostedData*/, string contentId)
{
	this._contentId = contentId;
 
	return this._xformHandler.HandleAction(this);
}

(I don't really know why it's there anyway, this code is really just from the second link mentioned in the OP with the guide to get working XForm validation).

Then I'm able to set a breakpoint inside the function and get to it. But I still get the exact same error when

return this._xformHandler.HandleAction(this);

Tries to execute.

#91387
Edited, Oct 03, 2014 15:32
Vote:
 

Hi ran into all kinds of strange things also, like formdata being null, when using an xform directly on a page. Was writing a blog about how to schedule mailings for XForm Data at the time. Look like you also ran into the formdata being null problem.

Maybe my solution for these problems might help you out in this case also.

#91395
Oct 03, 2014 16:03
Vote:
 

I think I'm about to figure this out. Something must've changed when we upgraded something because this was working perfectly before.

I have done some decompiling and debugging with DotPeek, and this is the line that is failing in the EPiServer dll::

if (!xFormPostedData.XForm.AllowMultiplePost && XFormActionHelper.HasAlreadyPosted((Guid) xFormPostedData.XForm.Id, xFormPostedData.SelectedSubmit.ChannelOptions, httpContextBase.Request.Cookies, httpContextBase.User))

And it is xFormPostedData.SelectedSubmit that is null. The reason for this has something to do with our custom rendering of the XForms which was working perfectly before but not anymore.

But I was able to get the form working again by removing the custom method:

@*Html.RenderCustomXForm(Model.FormBlockForm, Model.FormBlockHeading);
Replacing it with the standard one:*@
Html.RenderXForm(Model.FormBlockForm);

AND deleting every single custom fragment .cshtml file. But the biggest culprit seems to be the one that creates a prettier submit button. Just renaming/removing that file and not changing anything else also makes the form post without an error message but it does not validate or enter the database.

So I still have some investigating to do, because we do want to customize the look of our form...

The weekend is about to start, but I will keep updating this thread later once i manage to figure out completely what is going on here...

#91399
Edited, Oct 03, 2014 17:21
Vote:
 

I should probably also add that my XForm is NOT directly on a page. It's a shared block, I'm just getting exactly the same error.

It seems like it's some kind of EPiServer model binding/validation code that is causing a NullReferenceException. I'm hoping I'll be able to find out why it's not working on monday by comparing the POST requests that my browser client makes with the standard form vs our modified one.

I'll also add that I read your blog post Jeroen, but in my case I when I tried adding:

XFormActionHelper.BeforeSubmitPostedData += XFormActionHelper_BeforeSubmitPostedData;

to Global.asax.cs in Application_Start I was still not able to enter the XFormActionHelper_BeforeSubmitPostedData function. An exception happens in a EPiServer dll before that.

#91411
Oct 03, 2014 22:27
Vote:
 

Very interesting Alexander, good job! I'm suffering from the exact same problem with my customization of XForms (MVC). It previsouly worked just fine but is now broken. I'll keep you posted if I find anything more before you do! :)

#91426
Oct 04, 2014 12:51
Vote:
 

Hi again,

This might not solve the exact problems you are experiencing, but I've now got a working solution, "good enough" for our needs at the moment. The solution is based on the some previous articles on the subject:

  • http://www.eyecatch.no/blog/2013/01/using-xforms-and-mvc-in-an-episerver-7-block/
  • http://www.eyecatch.no/blog/2013/09/full-customization-of-xforms-with-episerver-7-and-mvc/
  • http://cjsharp.com/blog/2013/04/11/creating-a-xform-block-in-episerver-7-mvc-with-working-validation-updated/

Thought I share the code in case anyone is interested. Feedback is also appreciated! 

/Models/Blocks/XFormBlock.cs:

[SiteContentType(GUID = "FB326346-4D4C-4E82-AFE8-C36279006179")]
public class XFormBlock : SiteBlockData
{
    [Display(GroupName = SystemTabNames.Content)]
    [CultureSpecific]
    public virtual XForm Schema { get; set; }

    [Ignore]
    public string ActionUrl { get; set; }
}

 


/Controllers/PageControllerBase.cs
:

public abstract class PageControllerBase : PageController, IModifyLayout where T : PageData
{
    private string _contentId;
    private readonly string _viewDataKeyFormat = "ViewData_{0}";
    private readonly XFormPageUnknownActionHandler _xformHandler;

    private string ViewDataKey
    {
        get
        {
            return string.Format(_viewDataKeyFormat, _contentId);
        }
     }

    public PageControllerBase()
    {
        _xformHandler = new XFormPageUnknownActionHandler();
        _contentId = string.Empty;
    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!string.IsNullOrEmpty(_contentId))
        {
            if (TempData[ViewDataKey] != null)
            {
            ViewData = (ViewDataDictionary)TempData[ViewDataKey];
            }
        }

        base.OnActionExecuting(filterContext);
    }

    protected override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (!string.IsNullOrEmpty(_contentId))
        {
            TempData[ViewDataKey] = ViewData;
        }

        base.OnResultExecuting(filterContext);
    }

    [HttpPost]
    public virtual ActionResult Success(XFormPostedData xFormPostedData)
    {
        return RedirectToAction("Index", new { language = PageContext.LanguageID });
    }

    [HttpPost]
    public virtual ActionResult Failed(XFormPostedData xFormPostedData)
    {
        return RedirectToAction("Index", new { language = PageContext.LanguageID });
    }

    [HttpPost]
    public virtual ActionResult XFormPost(XFormPostedData xFormpostedData, string contentId)
    {
        _contentId = contentId;
        return _xformHandler.HandleAction(this);
    }
}

/Controllers/XFormBlockController.cs

public class XFormBlockController : BlockController
{
    private readonly UrlResolver urlResolver;

    public XFormBlockController(UrlResolver urlResolver)
    {
        this.urlResolver = urlResolver;
    }

    public override ActionResult Index(XFormBlock currentBlock)
    {
        var currentBlockID = (currentBlock as IContent).ContentLink.ID;
        var viewDataKey = string.Format("ViewData_{0}", currentBlockID);

        if (TempData[viewDataKey] != null)
        {
            ViewData = (ViewDataDictionary)TempData[viewDataKey];
        }
            
        var pageRouteHelper = ServiceLocator.Current.GetInstance();
        if (currentBlock.Schema != null && pageRouteHelper.Page != null)
        {
            var actionUrl = string.Format("{0}XFormPost/", urlResolver.GetUrl(pageRouteHelper.ContentLink));
            actionUrl = UriSupport.AddQueryString(actionUrl, "XFormId", currentBlock.Schema.Id.ToString());
            actionUrl = UriSupport.AddQueryString(actionUrl, "failedAction", "Failed");
            actionUrl = UriSupport.AddQueryString(actionUrl, "successAction", "Success");
            actionUrl = UriSupport.AddQueryString(actionUrl, "contentId", currentBlockID.ToString());

            currentBlock.ActionUrl = actionUrl;
        }
    
        return PartialView(currentBlock);
    }
}

 

/Helpers/HtmlHelpers.cs:

public static class HtmlHelpers
{
    public static void RenderCustomXForm(this HtmlHelper htmlHelper, XForm xform)
    {
        if (xform == null)
        {
            return;
        }

        htmlHelper.ViewContext.ViewData["XFormParameters"] = new XFormParameters();
        htmlHelper.RenderPartial(String.Format("DisplayTemplates/{0}", typeof(XForm).Name), xform);
    }

    public static MvcHtmlString RenderCustomHtmlFragment(this HtmlHelper htmlHelper, HtmlFragment fragment, bool renderFragmentsWithoutView = true)
    {
        var value = new StringBuilder();

        using (var writer = new StringWriter(value))
        {
            // try to find a matching partial view
            var viewEngineResult = ViewEngines.Engines.FindPartialView(htmlHelper.ViewContext, String.Format("DisplayTemplates/XForm/{0}", fragment.GetType().Name));

            if (viewEngineResult.View != null)
            {
                viewEngineResult.View.Render(viewEngineResult, htmlHelper.ViewContext, writer, fragment);
            }
            else if (renderFragmentsWithoutView)
            {
                // render fragment directly unless otherwise specified
                writer.Write(fragment.ToString());
            }
        }

        return value.Length <= 0 ? mvchtmlstring.empty : mvchtmlstring.create(value.tostring()); } private static void render(this iview view, viewengineresult viewengineresult, viewcontext context, textwriter writer, htmlfragment fragment) { var viewdatadictionaries="new" viewdatadictionary(fragment); foreach (var viewdatum in context.viewdata) { viewdatadictionaries[viewdatum.key]="viewDatum.Value;" } var viewcontext="new" viewcontext(context, viewengineresult.view, viewdatadictionaries, context.tempdata, writer); foreach (var modelstate in context.viewdata.modelstate) { viewcontext.viewdata.modelstate.add(modelstate.key, modelstate.value); } viewengineresult.view.render(viewcontext, writer); viewengineresult.viewengine.releaseview(context.controller.controllercontext, viewengineresult.view); } }>

/Views/XFormBlock/Index.cshtml

@model XFormBlock

x.Schema)> @{ var actionResult = ViewData["XFormActionResult"] as EPiServer.Web.Mvc.XForms.XFormActionBaseResult; var formMatch = actionResult != null && actionResult.XFormId.ToString() == Model.Schema.Id.ToString(); } @if (actionResult is EPiServer.Web.Mvc.XForms.XFormSuccessActionResult && formMatch) {
Form successfully posted. Thank you!
} else { if (formMatch) { @Html.ValidationSummary(false, "Failed to submit the form. Please correct the errors and try again.", new { @class = "alert alert-danger" }); } using (Html.BeginXForm(Model.Schema, htmlAttributes: new { Action = Model.ActionUrl, @class = "form xform form-horizontal" })) { Html.RenderCustomXForm(Model.Schema); } }

 

/Views/Shared/DisplayTemplates/XForm.cshtml

@using EPiServer.HtmlParsing
@model EPiServer.XForms.XForm
 
@if (Model != null)
{
    var actionResult = ViewData["XFormActionResult"] as EPiServer.Web.Mvc.XForms.XFormActionBaseResult;
    var formMatch = actionResult != null && actionResult.XFormId.ToString() == Model.Id.ToString();    
    var fragments = (formMatch) ?
        ((IEnumerable)ViewData["XFormFragments"] ?? Model.CreateHtmlFragments()) : 
        Model.CreateHtmlFragments();
            
    foreach (var fragment in fragments)
    {
        @Html.RenderCustomHtmlFragment(fragment)
    }
}

 

/Views/Shared/DisplayTemplates/XForm/InputFragment.cshtml

@model EPiServer.XForms.Parsing.InputFragment

@if (Model.Required) { @Html.TextBox(Model.Reference, Server.HtmlDecode(Model.Value) ?? string.Empty, new { @class = "form-control", size = Model.Size, placeholder = Model.Title, required = "required" }) } else { @Html.TextBox(Model.Reference, Server.HtmlDecode(Model.Value) ?? string.Empty, new { @class = "form-control", size = Model.Size, placeholder = Model.Title }) } @Html.ValidationMessage(Model.Reference) @Html.ValidationMessage(Model.ValidationReference) // NOTE: Using both Model.Reference and Model.ValidationReference here is my own lazy hack to get both // unobtrusive validation and rendering of "regular" validation working.
/Views

 

/Shared/DisplayTemplates/XForm/SubmitFragment.cshtml

@model EPiServer.XForms.Parsing.SubmitFragment
 
#91448
Oct 06, 2014 12:00
Vote:
 

I have finally solved this problem on our end as well.

It turned out that one of the biggest causes of problems was actually the caching code that we have started using:

The idea is basically from this post:

http://bergdaniel.se/asp-net-mvc-4-output-cache-with-episerver-7-preview

and I have implemented that Asp.net MVC Extensible donut caching on our site but with several modifications to it.

Trying to explain all of this would be way to much here but in the end I managed to build a workaround that turns off the caching completely automatically if there's an XForm block in any ContentArea on the current page.

I did this by adding this line of code the the Controller for the XForm block:

Response.CacheControl = "no-cache";

And then in the cache filter code I looked for that again and if it was set I would never add that item to the page output cache.

The simpler thing that had broken here though (in an earlier EPiServer upgrade perhaps?) was quite simple.

It is very important that these attributes from the submit button is POSTED to the server:

The problem is.. we were using styled button instead of input type="submit". The values from a button are appeareantly never POSTed to the server. So this did not work:

This on the other hand is working perfectly fine:


    

(Solved with an extra hidden input)

#91460
Edited, Oct 06, 2014 14:23
This thread is locked and should be used for reference only. Please use the Episerver CMS 7 and earlier versions forum to open new discussions.
* 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.