help with sfmskywalkers code

Topics: Writing modules
Mar 22, 2012 at 6:17 AM

Hi all, I had implemented some of sfmskywalker’s code - as follows - after reading his excellent tutorial:

        //SKYWALKER's code:
        //
        //http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-8
        //
        //
        public MemberPart CreateMember(string username, string email, string password)
        {
            var member = _orchardServices.ContentManager.New("Member");
            var userPart = member.As<UserPart>();
            var memberPart = member.As<MemberPart>();

            userPart.UserName = username;
            userPart.Email = email;
            userPart.NormalizedUserName = username.ToLowerInvariant();
            userPart.Record.HashAlgorithm = "SHA1";
            userPart.Record.RegistrationStatus = UserStatus.Pending;
            userPart.Record.EmailStatus = UserStatus.Approved;

            memberPart.CreatedAt = _dateTimeService.Now;

            _membershipService.SetPassword(userPart, password);
            _orchardServices.ContentManager.Create(member);

            return memberPart;
        }

With my memberpart, I also have a donation record attached by FK relationship as such:

SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("MemberPartRecord_id")
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("PaidDate")
                );
           
            SchemaBuilder.CreateTable("MemberPartRecord",
                table => table
                    .ContentPartRecord()
                    .Column<bool>("Lifetime")
                    .Column<string>("FirstName")
                    .Column<string>("LastName")
                    //Address
                    .Column<string>("Street1")
                    .Column<string>("Street2")
                    .Column<string>("City")
                    .Column<string>("State")
                    .Column<short>("PostCode")
                    //System
                    .Column<DateTime>("CreatedAt")
                );
What I can’t seem to fathom, is how to populate/create ‘one’ donation record during the CreateMember function – I mean, how do I reference such fields through memberpart; I’ve tried through debug to ascertain how, but have failed dismally. Any help is truly appreciated, thanks Dyr

Mar 22, 2012 at 6:35 PM

through the handler. 

http://docs.orchardproject.net/Documentation/Understanding-content-handlers

Mar 22, 2012 at 10:55 PM

Thanks for your offer of help ravetam -- forgive my ignorance, programming is certainly not my forte…this is my handler:

       public MemberPartHandler(IRepository<MemberPartRecord> repository)
       {
           Filters.Add(StorageFilter.For(repository));

           //SKYWALKER
           Filters.Add(new ActivatingFilter<UserPart>("Member"));
       }
 I thought that’s all that is required, as Donation is not a part (or do i have the concept wrong)?

Developer
Mar 23, 2012 at 1:03 AM

Hi Dyr,

If you don't want to create a ContentPart for the DonationRecord, then you can easily add a new record during the CreateMember function like so:

public MemberPart CreateMember(string username, string email, string password)
        {
            var member = _orchardServices.ContentManager.New("Member");
            var userPart = member.As<UserPart>();
            var memberPart = member.As<MemberPart>();

            userPart.UserName = username;
            userPart.Email = email;
            userPart.NormalizedUserName = username.ToLowerInvariant();
            userPart.Record.HashAlgorithm = "SHA1";
            userPart.Record.RegistrationStatus = UserStatus.Pending;
            userPart.Record.EmailStatus = UserStatus.Approved;

            memberPart.CreatedAt = _dateTimeService.Now;

            _membershipService.SetPassword(userPart, password);
            _orchardServices.ContentManager.Create(member);

       // Create and attach DonationRecord
       _donationRepository.Create(new DonationRecord { MembersPartRecord_Id = memberPart.Record.Id });

            return memberPart;
        }

Just remember to inject an IRepository<DonationRecord> via the constructor and assign it to the member field called _donationRepository.

Mar 23, 2012 at 2:23 AM
Edited Mar 23, 2012 at 3:31 AM

Hi smfskywalker, thanks ever so much for your advice [...intervention from the god himself, haha] - though I've hit a snag.

 

namespace TNS.Donations.Models {
    public class DonationRecord
    {
        public virtual int Id { get; set; }
        public virtual int MemberPartRecord_id { get; set; }
        public virtual byte Code { get; set; }
        public virtual int ProjectId { get; set; }
        public virtual string TransactionId { get; set; }
        public virtual decimal Amount { get; set; }
        public virtual DateTime? PaidDate { get; set; }
        public virtual MemberPartRecord MemberPartRecord { get; set; }
    }
}

 

basically, using the following as a test:

_donationRepository.Create(new DonationRecord {
                MemberPartRecord_id = memberPart.Record.Id,
                Amount = 100,
                Code = 2,
                PaidDate = Convert.ToDateTime("12/12/2012"),
                ProjectId = 22,
                TransactionId = "22"
            });


I get this error in the log:

2012-03-23 13:00:28,739 [47] Orchard.Exceptions.DefaultExceptionPolicy - Invalid index 6 for this SqlParameterCollection with Count=6.

Heres the exception:

    System.IndexOutOfRangeException was unhandled by user code
  Message=Invalid index 6 for this SqlParameterCollection with Count=6.
  Source=System.Data
  StackTrace:
       at System.Data.SqlClient.SqlParameterCollection.RangeCheck(Int32 index)
       at System.Data.SqlClient.SqlParameterCollection.GetParameter(Int32 index)
       at System.Data.Common.DbParameterCollection.System.Collections.IList.get_Item(Int32 index)
       at NHibernate.Type.NullableType.NullSafeSet(IDbCommand cmd, Object value, Int32 index)
       at NHibernate.Type.NullableType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session)
       at NHibernate.Type.ManyToOneType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session)
       at NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, Object[] fields, Object rowId, Boolean[] includeProperty, Boolean[][] includeColumns, Int32 table, IDbCommand statement, ISessionImplementor session, Int32 index)
       at NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, Object[] fields, Boolean[] includeProperty, Boolean[][] includeColumns, Int32 j, IDbCommand st, ISessionImplementor session)
       at NHibernate.Persister.Entity.AbstractEntityPersister.GeneratedIdentifierBinder.BindValues(IDbCommand ps)
       at NHibernate.Id.Insert.AbstractReturningDelegate.PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor session, IBinder binder)
       at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields, Boolean[] notNull, SqlCommandInfo sql, Object obj, ISessionImplementor session)
       at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields, Object obj, ISessionImplementor session)
       at NHibernate.Action.EntityIdentityInsertAction.Execute()
       at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
       at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
       at NHibernate.Event.Default.AbstractSaveEventListener.PerformSave(Object entity, Object id, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
       at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
       at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
       at NHibernate.Event.Default.DefaultSaveEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
       at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
       at NHibernate.Event.Default.DefaultSaveEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
       at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
       at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
       at NHibernate.Impl.SessionImpl.Save(Object obj)
       at Orchard.Data.Repository`1.Create(T entity) in C:\orc146\src\Orchard\Data\Repository.cs:line 96
       at Orchard.Data.Repository`1.Orchard.Data.IRepository<T>.Create(T entity) in C:\orc146\src\Orchard\Data\Repository.cs:line 36
       at TNS.Donations.Services.MemberService.CreateMember(String username, String email, String password)
       at TNS.Donations.Controllers.MembershipController.Register(SignupVM signup)
       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__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
       at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
  InnerException:

Mar 23, 2012 at 3:30 AM
Edited Mar 23, 2012 at 4:23 AM

I believe that above is to do with my mapped fk, and nhiberante trying to populate two columns 'MemberPartRecord_Id' of the same name.  I've seen a solution at:

http://devlicio.us/blogs/derik_whittaker/archive/2009/03/19/nhibernate-and-invalid-index-n-for-this-sqlparametercollection-with-count-n-error.aspx

that uses a pass through, something like:

       public virtual int MemberPartRecord_id
        {
            get { return MemberPartRecord.Id; }
            set { MemberPartRecord.Id = value; }
        }

but i'm not sure how to go about such without errors, and whether i'm implementing a remedy that has come about due to incorrect implemenation? Thanks for your thoughts, dyr

Mar 23, 2012 at 5:13 AM

I really dont get it, how is one meant to create such records [stipulating the FK] when using a migration that derives from the ContentRecord base class?  I note in smfskywalkers tutorial part 9, he creates two tables that do not derive from the base class, then manually creates the FK's; is this perhaps what I should have done so my models can have PK/FK Id's without nhibernate having a heart attack? Are there any pitfalls to doing so... Really confused, appreciate any suggestions - cheers dyr

Mar 23, 2012 at 6:23 AM
Edited Mar 23, 2012 at 6:24 AM

How about remove this from the class

namespace TNS.Donations.Models {
    public class DonationRecord
    {
        public virtual int Id { get; set; }
     => public virtual int MemberPartRecord_id { get; set; }
        public virtual byte Code { get; set; }
        public virtual int ProjectId { get; set; }
        public virtual string TransactionId { get; set; }
        public virtual decimal Amount { get; set; }
        public virtual DateTime? PaidDate { get; set; }
        public virtual MemberPartRecord MemberPartRecord { get; set; }
    }
}

and try this

_donationRepository.Create(new DonationRecord {
                MemberPartRecord = memberPart.Record,
                Amount = 100,
                Code = 2,
                PaidDate = Convert.ToDateTime("12/12/2012"),
                ProjectId = 22,
                TransactionId = "22"
            });

Mar 23, 2012 at 6:47 AM

THANK YOU!!!!!!!!!!!! x 1,000,000,000.  ravetam you are a legend, i've been going crazy over this all day.  I know fellow forum readers will probably think 'duh', but when you have very weak programming skills, a little help like this *especially with orchard*, goes a very, very, long way! Thanks to you both for taking the time to care, cheers Dyr.

Mar 23, 2012 at 9:55 AM

You’re going to hate me, but here goes…now that testing is over, I see I have one of three ways to populate the donation record.

        [HttpPost]
        public ActionResult Register (SignupVM signup)
        {
            // ensure users can register
            var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
            if ( !registrationSettings.UsersCanRegister ) {
                return HttpNotFound();
            }

            ViewData["PasswordLength"] = MinPasswordLength;

            if (ValidateRegistration(signup.UserName, signup.Email, signup.Password, signup.ConfirmPassword)) //First Username
            {
                // Attempt to register the user
                // No need to report this to IUserEventHandler because _membershipService does that for us
                if (!ModelState.IsValid)
                {
                    return new ShapeResult(this, _shapeFactory.Membership_Register(Signup: signup));
                }

                var member = _memberService.CreateMember(signup.UserName, signup.Email, signup.Password);
                    member.FirstName = signup.FirstName;
                    member.LastName = signup.LastName;
                    //Address
                    member.Street1 = signup.Street1;
                    member.Street2 = signup.Street2;
                    member.City = signup.City;
                    member.State = signup.State;
                    member.PostCode = signup.PostCode;
                    member.Country = signup.Country;

                    //MEMBERSHIP -Donation
                    //--> TODO: Populate donation here/or in service?

                //redirect to PayPal
                TempData["Model"] = signup;

                return RedirectToAction("Checkout", "PayPal", new { area = "TNS.PayPal" });
            }

            // If we got this far, something failed, redisplay form
            var shape = _shapeFactory.Membership_Register();
            return new ShapeResult(this, shape);
        }


•    I could pass the signup model [containing donation data] to the CreateMember service.
•    I could move _donationRepository.Create out to the register action.
•    Or three, the correct method – ask you guys what the best approach is, and why?

Thanks in anticipation *again*, hopefully this is the last question I'll have on this topic - sheesh, cheers Dyr

Developer
Mar 23, 2012 at 10:27 AM
Edited Sep 29, 2012 at 7:49 PM

Personally I would not recommend passing in view models to your service, as that would break the seperation of concerns (view models are a concern of the controller and view layers, not of the domain itself).

So, in your case, perhaps it would be perfectly fine to create a new DonationRecord inside of your Register action and assign it to the membership. After all, you're not implementing business logic here; it's just mapping view model data to domain model data, which is perfectly OK, as far as I know.

What I sometimes do when I see that the action contains a lot of mapping code, I move those lines into a private method called Map. That method will then take both the source view model and the destination domain model, and copy over each property as you are doing right now.

If I have a lot of mapping code and the properties of both view models and domain models are equally named, I might even resort to AutoMapper, although that might be a bit of overkill in this case. 

Another approach might be to create a new class in your domain layer called CreateMembershipArguments.
This class will be like a view model in the sense that it contains all of the required info for the Membership service to create both a membership as well as a donation, but it's just a domain specific class extending the API of your service.

This will enable you to simply map your incoming view model to a new instance of CreateMembershipArguments, and then pass it into some CreateMembership method on your service class.

There are probably more ways to go about this. As a general guideline, try to keep it as simple as possible while adhering to the recommended patterns & practices (such as seperation of concerns and SRP).

Please note that I am no expert whatsoever on the matter, and others might have other opinions who are more qualified than me. I'm still learning like you.

Mar 23, 2012 at 10:03 PM

Brilliantly concise and informative response; ‘much food for thought to chew on’ ;)  I believe you’re right though, that simply implementing the code block in the register action itself would be the easiest approach and shouldn’t go against the MVC ethos. I think I’m on the precipice of having clouded, scattered fragments of assumption mixed with minute parts of ‘know how’, finally culminating to some actual factual knowledge *yikes*; but seriously, things are starting [slowly] to make sense.  Thanks for being a 'big' catalyst in that realization/learning process, cheers Dyr