Error trying to create a N-N Relations with 2 Content Parts

Topics: Writing modules
May 30, 2011 at 12:57 PM

Hi,

Hope someone can point me in the write direction / give me the answer!!  I'm still trying to learn the ropes (or vines!) of Orchard.  I work for a college in the UK and am evaluating using the framework to build an e-commerce site for browsing and purchasing online professional courses.

Ok so I am trying to build a N-N relationship with entities Course and Course Category.  I have followed this walk through http://www.orchardproject.net/docs/Creating-1-n-and-n-n-relations.ashx and got it working with a Course Part and just a course category record.  However I would like to 'upgrade' the course category to a Part.  I have attempted to do this, using the Blog module with its BlogPost part as a further guide.  However this seems to break everything!  Now when I try to create a Couse I gett the following error:

Specified cast is not valid.
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.InvalidCastException: Specified cast is not valid.

Source Error:

[No relevant source lines]


Source File: ContentExtensions.cs    Line: 18

Stack Trace:

[InvalidCastException: Specified cast is not valid.]
   Orchard.ContentManagement.ContentCreateExtensions.New(IContentManager manager, String contentType) in ContentExtensions.cs:18
   Orchard.Courses.Controllers.CourseAdminController.Create() +89
   lambda_method(Closure , ControllerBase , Object[] ) +40
   System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +17
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +188
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
   System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +56
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +267
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +20
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +267
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +20
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +267
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +20
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +190
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +329
   System.Web.Mvc.Controller.ExecuteCore() +115
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +94
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
   System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +55
   System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +31
   System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
   System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +23
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +59
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   Orchard.Mvc.Routes.HttpAsyncHandler.EndProcessRequest(IAsyncResult result) in ShellRoute.cs:148
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8841105
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184

I can't seem to solve it, it must be something silly that i'm overlooking.  You can download the module from here http://dl.dropbox.com/u/11941453/Orchard.Courses.zip

Here's my Migrations file in case someone can spot it from here!

        public int Create() {
			// Creating table CoursePartRecord
			SchemaBuilder.CreateTable("CoursePartRecord", table => table
				.ContentPartRecord()
				.Column("ShortDescription", DbType.String, column => column.WithLength(400))
			);

            ContentDefinitionManager.AlterTypeDefinition("CoursePart",
                cfg => cfg
                    .WithPart("CoursePart")
                    .WithPart("CommonPart")
                    .WithPart("PublishLaterPart")
                    .WithPart("RoutePart")
                    .WithPart("BodyPart")
                    .WithPart("MenuPart")
                    .WithPart("AdminMenuPart", p => p.WithSetting("AdminMenuPartTypeSettings.DefaultPosition", "3"))
                    .Indexed()
                );

            return 2;
        }
        
        public int UpdateFrom1() {
            ContentDefinitionManager.AlterTypeDefinition("CoursePart", 
                cfg => cfg
                    .WithPart("MenuPart")
                    .WithPart("AdminMenuPart", p => p.WithSetting("AdminMenuPartTypeSettings.DefaultPosition", "3"))
                );
            return 2;
        }

        public int UpdateFrom2()
        {
            SchemaBuilder.CreateTable("CourseCategoryPartRecord", table => table
                    .ContentPartRecord()
                    .Column("Name", DbType.String, column => column.WithLength(40))
                    .Column("ShortDescription", DbType.String, column => column.WithLength(400))
                );

            ContentDefinitionManager.AlterTypeDefinition("CourseCategoryPart",
                cfg => cfg
                    .WithPart("AdminMenuPart", p => p.WithSetting("AdminMenuPartTypeSettings.DefaultPosition", "4"))
                    .Indexed()
                );

            SchemaBuilder.CreateTable("ContentCourseCategoriesRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("CoursePartRecord_Id")
                    .Column<int>("CourseCategoryPartRecord_Id")
                );

            ContentDefinitionManager.AlterPartDefinition(
                "CoursePart",
                builder => builder.Attachable());

            return 3;
        }

If I have missed any vital bits of the story, just let me know and I will add it in.

Thanks,

Jeff

Coordinator
May 31, 2011 at 8:02 PM

Why is your content type called "CoursePart" instead of just "Course"?

Jun 7, 2011 at 11:48 AM

Thanks for the pointer, I'm a bit confused as the .WithPart method takes the full name of a part with the word "Part" in it!  Anyway I am now using  typeof(CoursePart).Name to get the name and this seems to work.  I also noticed that I was not attaching the CourseCategoryPart to itself in the config.  Here is my updated migrations.cs

public class Migrations : DataMigrationImpl {

        public int Create() {
			// Creating table CoursePartRecord
			SchemaBuilder.CreateTable("CoursePartRecord", table => table
				.ContentPartRecord()
				.Column("ShortDescription", DbType.String, column => column.WithLength(400))
			);

            ContentDefinitionManager.AlterTypeDefinition(typeof(CoursePart).Name,
                cfg => cfg
                    .WithPart("CoursePart")
                    .WithPart("CommonPart")
                    .WithPart("PublishLaterPart")
                    .WithPart("RoutePart")
                    .WithPart("BodyPart")
                    .WithPart("MenuPart")
                    .WithPart("AdminMenuPart", p => p.WithSetting("AdminMenuPartTypeSettings.DefaultPosition", "3"))
                    .Indexed()
                );

            return 2;
        }
        
        public int UpdateFrom1() {
            ContentDefinitionManager.AlterTypeDefinition(typeof(CoursePart).Name, 
                cfg => cfg
                    .WithPart("MenuPart")
                    .WithPart("AdminMenuPart", p => p.WithSetting("AdminMenuPartTypeSettings.DefaultPosition", "3"))
                );
            return 2;
        }

        public int UpdateFrom2()
        {
            SchemaBuilder.CreateTable("CourseCategoryPartRecord", table => table
                    .ContentPartRecord()
                    .Column("Name", DbType.String, column => column.WithLength(40))
                    .Column("ShortDescription", DbType.String, column => column.WithLength(400))
                );

            ContentDefinitionManager.AlterTypeDefinition(typeof(CourseCategoryPart).Name,
                cfg => cfg
                    .WithPart("CourseCategoryPart")
                    .WithPart("AdminMenuPart", p => p.WithSetting("AdminMenuPartTypeSettings.DefaultPosition", "4"))
                    .Indexed()
                );

            SchemaBuilder.CreateTable("ContentCourseCategoriesRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("CoursePartRecord_Id")
                    .Column<int>("CourseCategoryPartRecord_Id")
                );

            ContentDefinitionManager.AlterPartDefinition(typeof(CoursePart).Name,
                builder => builder.Attachable());

            return 3;
        }
    }

This seems to work fine, and renders the admin screens to allow me to create a new Course and Course Category.  However when I try to create a course and select a category, the selection is not persisted.  So I am unable to form a relation between the Course and Course Category.  I have debugged this and I get a NullReferenceException - Object reference not set to an instance of an object.

   at Orchard.ContentManagement.ContentPart.get_Id() in C:\Development\cpd\src\Orchard\ContentManagement\ContentPart.cs:line 45

It appears that the ContentItem is null for the Course Category when the TryUpdateModel is called with the ViewModel used to capture the checkbox selections.

I have updated the module source code at http://dl.dropbox.com/u/11941453/Orchard.Courses.zip if you need to take a look.

Any Ideas?

Thanks

Jeff

Jun 7, 2011 at 12:41 PM
j3ffb wrote:

Thanks for the pointer, I'm a bit confused as the .WithPart method takes the full name of a part with the word "Part" in it!  Anyway I am now using  typeof(CoursePart).Name to get the name and this seems to work.  I also noticed that I was not attaching the CourseCategoryPart to itself in the config.  Here is my updated migrations.cs

To avoid confusion, you don't want "Part" in your content type names. You can name them anything you want.

By convention, all parts end in "Part" (at least when referencing them in code; for UI display the "Part" is usually removed). So by also naming your content types "CoursePart" and "CourseCategoryPart" the same as your parts, you'll just end up confusing yourself :)

As to your error, if you post the code here it might be more obvious. But if you're trying to join to a ContentItem it won't work as you expect - you can join to ContentPartRecord but you'll still need to call ContentManager.Get(..) to get the full ContentItem.

"CoursePart" and "CourseCategoryPart"