Creating simple contact form module

Topics: Customizing Orchard, Writing modules
Dec 25, 2012 at 3:07 PM
Edited Dec 25, 2012 at 3:08 PM

Hi i am trying to create a simple form module that submits the information from the front end 

For now i am having 2 issues 

issue 1 : i am having an error when submitting the form it`s null object exception 


 

here is the code .

i will show you the code 

Models

 

 public class FormPart:ContentPart<FormPartRecord>
    {
        public string Name
        {
            get { return Record.Name; }
            set { Record.Name = value; }
        }
        public string Email
        {
            get { return Record.Email; }
            set { Record.Email = value; }
        }
        public string Phone
         {
             get { return Record.Phone; }
             set { Record.Phone = value;}
        }
        
        public string Subject
          {
              get { return Record.Subject; }
              set { Record.Subject = value; }
        }
       public string Message
        {
            get { return Record.Message; }
            set { Record.Message = value; }
        }
  //      [DefaultValue(DateTime.Now)]
       public DateTime DateCreated
       {
           get { return DateTime.Now; }
           set { Record.DateCreated = value; }
       }
 public class FormPartRecord : ContentPartRecord
    {
    
        public virtual string Name { get; set; }
        public virtual string Email { get; set; }
        public virtual string Phone { get; set; }
        public virtual string Subject { get; set; }
        public virtual string Message { get; set; }
        public virtual DateTime DateCreated { get; set; }
    }

The Driver 

 

    public class FormPartDriver : ContentPartDriver<FormPart>
    {
        protected override string Prefix
        {
            get
            {
                return "Form";
            }
        }
      
        protected override DriverResult Display(FormPart part, string displayType, dynamic shapeHelper)
        {
            return ContentShape("Parts_Form", () => shapeHelper.Parts_Form(FormPart:part));
        }


    }

The Handler

 public class FormPartHandler : ContentHandler
    {
        public FormPartHandler(IRepository<FormPartRecord> repository)
        {
            Filters.Add(StorageFilter.For(repository));
            
            OnInitializing<FormPart>((context, part) =>
            {
                part.DateCreated = DateTime.Now;
            });
        }
    }

 

The Migration 

 

 

 public class Migrations : DataMigrationImpl {

        public int Create() {
            SchemaBuilder.CreateTable("FormPartRecord", table => table
                .ContentPartRecord()
                .Column<string>("Name", column => column.WithLength(80))
                .Column<string>("Phone", column => column.WithLength(15))
                .Column<string>("Email", column => column.WithLength(80))
                .Column<string>("Subject", column => column.WithLength(200))
                .Column<string>("Message", column => column.WithLength(500))
                .Column<DateTime>("DateCreated", column => column.WithDefault(DateTime.Now))
                );
            ContentDefinitionManager.AlterPartDefinition("FormPart", part => part
                .Attachable());

            return 1;
        }
        public int UpdateFrom1()
        { 
        
            ContentDefinitionManager.AlterTypeDefinition("FormPart",part => part
                .WithPart("CommonPart"));

            return 2;
        }

 

The view

 

 

@model dynamic
@{
    
      var myModel = (XXX.Forms.Models.FormPart)Model.FormPart;
    }
  
  @using (Html.BeginForm("Contact", "Form", new { area = "XXX.Forms" }, FormMethod.Post))
  {
    <article class="form">
        <fieldset>
            
class="editor-label">@Html.LabelFor(m=>myModel.Name)
class="editor-field">@Html.EditorFor(m => myModel.Name)
class="editor-label">@Html.LabelFor(m => myModel.Phone)
class="editor-field">@Html.EditorFor(m => myModel.Phone)
class="editor-label">@Html.LabelFor(m => myModel.Email)
class="editor-field">@Html.EditorFor(m => myModel.Email)
class="editor-label">@Html.LabelFor(m => myModel.Subject)
class="editor-field">@Html.EditorFor(m => myModel.Subject)
class="editor-label">@Html.LabelFor(m => myModel.Message)
class="editor-field">@Html.EditorFor(m => myModel.Message)
</fieldset> <button type="submit">Soumettre</button> </article> }

 

And finally the Controller

 

 [HttpPost]
        public ActionResult Contact(FormPart entry)
        {
            var newContact = _contentManager.New<FormPart>("Form");
            newContact.Name = entry.Name;
            newContact.Email = entry.Email;
            newContact.Phone = entry.Phone;
            newContact.Subject = entry.Subject;
            newContact.Message = entry.Message;
            newContact.DateCreated = DateTime.Now;

            _contentManager.Create(newContact);
            return RedirectToAction("Index");
        }

 

So basically i think iàm missing something so when i submit my form i got the exception below  .

And my second problem is the CreationDate i was having an exception because it`s not defined i tried to assign a default value using the constructor and it didn`t work i tried with the handler with the OnInitialazing but it didn`t i think my two problems are linked

Thanks for the help

 

 OnInitializing<FormPart>((context, part) =>
            {
                part.DateCreated = DateTime.Now;
            });

 

Server Error in '/OrchardLocal' Application.

Object reference not set to an instance of an object.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error: 


Line 160:            public void EndProcessRequest(IAsyncResult result) {
Line 161:                try {
Line 162:                    _httpAsyncHandler.EndProcessRequest(result);
Line 163:                }
Line 164:                finally {

Source File: E:\Project\orchardDev\src\Orchard\Mvc\Routes\ShellRoute.cs    Line: 162 

Stack Trace: 


[NullReferenceException: Object reference not set to an instance of an object.]
   Creawebmarket.Forms.Models.FormPart.get_DateCreated() +68

[TargetInvocationException: Property accessor 'DateCreated' on object 'Creawebmarket.Forms.Models.FormPart' threw the following exception:'Object reference not set to an instance of an object.']
   System.ComponentModel.ReflectPropertyDescriptor.GetValue(Object component) +400
   System.Web.Mvc.<>c__DisplayClassb.b__a() +42
   System.Web.Mvc.ModelMetadata.get_Model() +51
   System.Web.Mvc.d__15.MoveNext() +266
   System.Web.Mvc.d__1.MoveNext() +798
   System.Web.Mvc.DefaultModelBinder.OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) +413
   System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model) +115
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +2542
   System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +606
   System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) +476
   System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) +181
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +830
   System.Web.Mvc.<>c__DisplayClass1d.b__19() +63
   System.Web.Mvc.Async.<>c__DisplayClass1.b__0() +44
   System.Web.Mvc.Async.<>c__DisplayClass8`1.b__7(IAsyncResult _) +42
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +140
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +54
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +47
   System.Web.Mvc.Async.<>c__DisplayClass4.b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +140
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +54
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.<>c__DisplayClass8.b__3(IAsyncResult asyncResult) +48
   System.Web.Mvc.Async.<>c__DisplayClass4.b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +140
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +54
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +40
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +38
   Orchard.Mvc.Routes.HttpAsyncHandler.EndProcessRequest(IAsyncResult result) in E:\Project\orchardDev\src\Orchard\Mvc\Routes\ShellRoute.cs:162
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8963149
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
Developer
Dec 25, 2012 at 5:24 PM
Edited Dec 25, 2012 at 5:24 PM

The problem occurs as soon as the ModelBinder tries to set any of your FormPart instance's properties. Each property delegates it's getter and setter to it's Record property, which is null if you (or in this case, the ModelBinder) instantiates a FormPart (when you load or new up a content item with the FormPart, a content handler or filter will new up the Record as well, but since you're accepting a FormPart as an action parameter, it will be instantiated by the ModelBinder, which will not instantiate the Record property).

A solution would be to new up a FormPart using the content manager as you are doing in the first line of your action, and then do an explicit model update with the TryUpdateModel method.

Alternatively, use a view model class.

Dec 26, 2012 at 12:32 AM

Hi 

First i want to thank you for your quick response. 

Actually i tried both of the solutions you suggested .

First i tried to do an explicit TryUpdateModel but my controllers was not even hitted when submitting i was getting the exception before the controller get called.

So basically  what i did i changed my FormPart in the view and the controller with the FormPartRecord and then i was not getting the exceptions. But when in the controller my Model was empty all the fields where null I was able to access the fields using Request.Form["myModel.Name"] . and then i was getting an exception on the line 

var newContact = _contentManager.New<FormPart>("MyForm"); 

   at Orchard.ContentManagement.ContentCreateExtensions.New[T](IContentManager manager, String contentType) in E:\Project\orchardDev\src\Orchard\ContentManagement\ContentExtensions.cs:line 19
   at Creawebmarket.Forms.Controllers.FormController.Contact(FormViewModel myForm)
   at lambda_method(Closure , ControllerBase , Object[] )
   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
   at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass13.<InvokeActionMethodWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)

So I was not able to save the Part .

Then i used the viewModel so here is the ViewModel Class

 

 public class FormViewModel 
    {
        [StringLength(50),Required,Display(Name="Name")]
        public  string Name { get; set; }
        [StringLength(50), Required,DataType(DataType.EmailAddress), Display(Name = "Email")]
        public  string Email { get; set; }
        public  string Phone { get; set; }
        public  string Subject { get; set; }
        public  string Message { get; set; }
    }

And here is my view now 

 

@{
    
    var myModel = new FormViewModel();
     // myModel.DateCreated = DateTime.Now;
    }
  
  @using (Html.BeginForm("Contact", "Form", new { area = "Creawebmarket.Forms" }, FormMethod.Post))
  {
    <article class="form">
        <fieldset>
            
class="editor-label">@Html.LabelFor(m=>myModel.Name)
class="editor-field">@Html.EditorFor(m => myModel.Name)
class="editor-label">@Html.LabelFor(m => myModel.Phone)
class="editor-field">@Html.EditorFor(m => myModel.Phone)
class="editor-label">@Html.LabelFor(m => myModel.Email)
class="editor-field">@Html.EditorFor(m => myModel.Email)
class="editor-label">@Html.LabelFor(m => myModel.Subject)
class="editor-field">@Html.EditorFor(m => myModel.Subject)
class="editor-label">@Html.LabelFor(m => myModel.Message)
class="editor-field">@Html.EditorFor(m => myModel.Message)
</fieldset> <button type="submit">Soumettre</button> </article> }

But When i submit my controller my object again is not binded all the fields are null and i'am still getting the problem to save the Part . 

I'am little confused so this problem seems to be simple enough but i think i am missing some part . Is there a simple exemple to follow i tried to find something  similar on the forum but without success i have seen your complexe shopping cart exemple but i was not able to find the information i am missing here .

 

Thanks for the help

Developer
Dec 26, 2012 at 10:00 AM

When using the view model approach, did you name the argument of your action "myModel" as well? If not, then that may be the reason the model binder failed to bind the form values.
It's curious why you are instantiating a view model inside of your view, although I don't think that is the cause of the issues.

You mention you get an exception when invoking the New<TContent> method, but you did not show us what the exception was exactly, just the stack trace.

Dec 26, 2012 at 2:45 PM

Hi thanks again for your quick and prompt reply when i changed the argument name to myModel the binding is working fine.

So also for the view i don`t have to instantiate a viewModel inside my view but i was unable to cast My Model to my view model.

And the exception i am getting when invoking the new< TContent>

var newContact = _contentManager.New<FormPart>("MyForm"); 

 

is invalid cast exception which is thrown from this function 

 public static T New<T>(this IContentManager manager, string contentType) where T : class, IContent {
            var contentItem = manager.New(contentType);
            if (contentItem == null)
                return null;

            var part = contentItem.Get<T>();
            if (part == null)
                throw new InvalidCastException();

            return part;
        }
Thanks for the help

Developer
Jan 1, 2013 at 5:21 PM

If you are unable to cast your model to your view model, be sure that you actually sent an instance of your viewmodel class.

As for the InvalidCastException, make sure that:

  1. The "MyForm" content type has the "FormPart" part attached.
  2. That the "FormPart" has a driver (and that the feature it's part of is enabled)

You can use the debugger to inspect the parts that are actually attached to the content item as soon as the InvalidCastException is thrown. Look for the name of the PartDefinition. If you do find a "FormPart" part, but it's typed "ContentPart", then your part was not welded on to your type (see 2). If your part is not part of the instance at all, then make sure the part is attached to your type at all, using the dashboard (see 1).

I noticed that you used the content type "Form" in one post and "MyForm" in your last post. Perhaps a mistake is made there?