Help with Orchard ethos - am I on the right track re: Custom Tables created via my module.

Topics: General, Troubleshooting, Writing modules
Jun 25, 2014 at 5:14 AM
Edited Jun 25, 2014 at 6:27 AM
Hey guys, if I omit the following UpdateFrom1 from my Migrations, I get: System.NullReferenceException error when attempting to write an Address Record.

Migrations.cs
using EA.Profile.Models;
using Orchard;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
using Orchard.Users.Models;
using System;

namespace EA.Profile
{
    public class Migrations : DataMigrationImpl {

        private readonly IOrchardServices _orchardServices;

        public Migrations(IOrchardServices orchardServices)
        {
            _orchardServices = orchardServices;
        }
        public int Create() {

            //Profile
            SchemaBuilder.CreateTable("ProfilePartRecord",
                table => table
                    .ContentPartRecord()
                    //ProfilePartRecord_id [Auto Key]
                    .Column<string>("FirstName")
                    .Column<string>("LastName")
                    //System
                    .Column<DateTime>("CreatedAt")
                );

            //Address
            SchemaBuilder.CreateTable("AddressPartRecord",
                table => table
                    .ContentPartRecord()

                    .Column<int>("ProfilePartRecord_id") //FK
                    .Column<string>("Street1")
                    .Column<string>("Street2")
                    .Column<string>("City")
                    .Column<string>("State")
                    .Column<string>("PostCode")
                    .Column<string>("Country")
                );

            //Address Type
            SchemaBuilder.CreateTable("AddressTypePartRecord",
                table => table
                    .ContentPartRecord()

                    .Column<int>("AddressPartRecord_id") //FK
                    .Column<string>("Type")
                );

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

            ContentDefinitionManager.AlterTypeDefinition("Profile", t => t
                .WithPart(typeof(ProfilePart).Name)
                .WithPart("UserPart")
                );

            ContentDefinitionManager.AlterTypeDefinition("User", t => t
                .WithPart("ProfilePart")
                );

            var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
            registrationSettings.UsersCanRegister = true;

            return 1;
        }

        public int UpdateFrom1()
        {
            ContentDefinitionManager.AlterPartDefinition("AddressPart",
                builder => builder.Attachable());

            ContentDefinitionManager.AlterTypeDefinition("Address", t => t
                .WithPart(typeof(AddressPart).Name)
                .WithPart("ProfilePart")
                );

            ContentDefinitionManager.AlterTypeDefinition("Profile", t => t
                .WithPart("AddressPart")
                );

            return 2;
        }
    }
}
But attaching it to the ProfilePart means that when I create a new profile, orchard automatically writes an Address Record [containing Nulls except ID] to DB as well [which I dont want; well not currently, anyhow] and vice versa, creating an address record also creates a Null Profile Record.

AddressService.cs
using EA.Profile.Models;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Security;

namespace EA.Profile.Services
{
    public interface IAddressService : IDependency {
        AddressPart CreateAddress(Details addressdetails); 
    }

    public class AddressService : IAddressService {
        private readonly IOrchardServices _orchardServices;

        public AddressService(IOrchardServices orchardServices) {
            _orchardServices = orchardServices;
        }

        public AddressPart CreateAddress(Details addressdetails) {
            var address = _orchardServices.ContentManager.New("Address");
            //var profilePart = paddress.As<ProfilePart>();
            var addressPart = address.As<AddressPart>();

            IUser user = _orchardServices.WorkContext.CurrentUser;

            var profilePart = _orchardServices.ContentManager.Get(user.Id);

            addressPart.ProfilePartRecord_id = profilePart.Id;

            addressPart.Street1 = addressdetails.Street1;
            addressPart.Street2 = addressdetails.Street2;
            addressPart.City = addressdetails.City;
            addressPart.State = addressdetails.State;
            addressPart.PostCode = addressdetails.PostCode;
            addressPart.Country = addressdetails.Country;
            
            _orchardServices.ContentManager.Create(address);


            return addressPart;
        }
    }
}
This got me thinking, that i'm obviously not creating my module in the orchard way/recommended implementation style? Problem is, i'm not sure what the preferred implementation would be? Can anyone recommend, point me in the right direction of some materials relating to such.

I've been though:
http://docs.orchardproject.net/Documentation/Creating-1-n-and-n-n-relations
Hungary Demo
https://github.com/Jetski5822/NGM.Forum [looks good, but differs from the pattern i've seen in the docs]
And quite a few of the orchard modules, trying to better understand the orchard pattern.

Any advice will be greatly appreciated - thanks for your support, Ron
Developer
Jun 25, 2014 at 11:07 AM
To fix the issue above, Can you confirm...

var addressPart = address.As<AddressPart>();

Do you have a driver for this?

public class AddressPartDriver : ContentPartDriver<AddressPart>


And have you got a Handler with Storage?
[UsedImplicitly]
public class AddressPartHandler : ContentHandler {
    public AddressPartHandler(IRepository<AddressPartRecord> repository) {
        Filters.Add(StorageFilter.For(repository));
    }
}
Jun 25, 2014 at 12:30 PM
Driver:
using EA.Profile.Models;
using EA.Profile.ViewModels;
using Orchard;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Localization;

namespace EA.Profile.Drivers
{
    public class AddressPartDriver : ContentPartDriver<AddressPart> {
        public IOrchardServices Services { get; set; } 
        public AddressPartDriver(IOrchardServices services) {
            Services = services;
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        protected override string Prefix { get { return "AddressPart"; } }

        protected override DriverResult Editor(AddressPart part, dynamic shapeHelper) {
            var profilePart = part.As<ProfilePart>();

            return ContentShape(
                "Parts_AddressPart",
                () => shapeHelper.EditorTemplate(TemplateName: "Parts.AddressPart.Edit",
                    Model: new AddressViewModel
                    {
                        ProfilePartRecord_id = profilePart.ContentItem.Id,
                        Street1 = part.Street1,
                        Street2 = part.Street2,
                        City = part.City,
                        State = part.State,
                        PostCode = part.PostCode,
                        Country = part.Country,
                    },
                    Prefix: Prefix
                )
            );
        }

        protected override DriverResult Editor(AddressPart part, IUpdateModel updater, dynamic shapeHelper) {
            var viewModel = new AddressViewModel();
            if (updater.TryUpdateModel(viewModel, Prefix, null, null)) {
                var profilePart = part.As<ProfilePart>();
                part.ProfilePartRecord_id = profilePart.Id;
                part.Street1 = viewModel.Street1;
                part.Street2 = viewModel.Street2;
                part.City = viewModel.City;
                part.State = viewModel.State;
                part.PostCode = viewModel.PostCode;
                part.Country = viewModel.Country;
            }
            return Editor(part, shapeHelper);
        }
    } 
}
...and my handler was the same as yours, except I didnt have [UsedImplicitly] which I have since added.

Thanks for taking the time to review my code Jetski5822, very much appreciated - cheers
Developer
Jun 25, 2014 at 2:18 PM
The reason why you get a Null Reference exception is because UpdateFrom1 is where you create the content type "Address"
ContentDefinitionManager.AlterTypeDefinition("Address", t => t
   .WithPart(typeof(AddressPart).Name)
   .WithPart("ProfilePart")
);
so if you omit that, then doing this
var address = _orchardServices.ContentManager.New("Address");
will yield a null.

Why is an empty record a problem?
Jun 26, 2014 at 1:37 AM
Edited Jun 26, 2014 at 4:09 AM
Ok, so my Migrations is correct, and when creating a Profile (which has the content type Address) orchard will also automatically populate an address record of nulls -- and this is normal/accepted behaviour?
But what about when I add another address to a Profile, for I note in my Profile Table, another Null record gets created within erroneously?

Why I'm concerned with such, is that having many records with just an ID field populated, with all the other fields being Null's in a Table seems like a bad idea for the users that never add an address -- and the Null record being created in the Profile Table whilst adding another address is just wrong?

Also, when loading/viewing/editing just an address for instance, the associated Profile will be returned as well -- is this not bad for server resource, and if I add more tables associated to the Profile [which I intend to], will this not make it worse. Please forgive my ignorance, as orchard and programming in general is a new escapade for me, so the simple is still a little perplexing at the moment.
Thanks for your efforts Jetski5822 - cheers Ron

AddressController
        [HttpPost]
        public ActionResult Create(AddressPartRecord addressdetails) //Details
        {
            if (!ModelState.IsValid)
            {
                return new ShapeResult(this, _shapeFactory.Address_Create(AddressPartRecord: addressdetails)); //Details
            }

            var address = _addressService.CreateAddress(addressdetails);
            address.Street1 = addressdetails.Street1;
            address.Street2 = addressdetails.Street2;
            address.City = addressdetails.City;
            address.State = addressdetails.State;
            address.PostCode = addressdetails.PostCode;
            address.Country = addressdetails.Country;

            return RedirectToAction("index");
        }