This project is read-only.

Problems with saving and events, what am I doing wrong?

Topics: User Forum
Mar 10, 2009 at 1:41 PM
Hi!

I have created a template and a page.

The page shows up when I create a new item under the root-node. But I can't save it, the error message "says object references an unsaved transient instance - save the transient instance before flushing: N2.Details.ContentDetail"

I think the problem is that none of my events are fired, how do I make them do it? The only method that is executed is the constructor. If I override the AddTo-method, not even that method is executed.

Any help will be appreciated, I'm really stuck with this thing.


Regards,
Per

The code

[N2.

Definition("NewsPage")]

 

public

class NewsTest : N2.ContentItem, IPersister,IAutoStart,IStartable

 

 

{

 

IPersister persister;

 

 

 

public NewsTest()

 

{

 

persister = N2.

Context.Current.Persister;

 

 

 

}

[N2.Details.

EditableFreeTextArea("Text", 2)]

 

 

 

 

 

public string Text

 

{

 

 

get { return (string)GetDetail("Text"); }

 

 

 

set

 

 

{SetDetail(

"Text", value);

 

 

 

}

}

[N2.Details.

EditableFreeTextArea("Introduction", 1)]

 

 

 

 

public string Introduction

 {

 

get

 

 

{

 

 

return (string)GetDetail("Introduction");

 

 }

 

set

 

 

{

 

SetDetail(

"Introduction", value);

 }

}

[N2.Details.

EditableTextBox("Title", 0)]

 

 

public new string Title

{

 

 

get

 

 

{

 

 

return (string)GetDetail("Title");

 }

 

set

 

 

{

 

SetDetail(

"Ttile", value);

 

 }

}

 

#region

IPersister Members

 

public

 

ContentItem Copy(ContentItem source, ContentItem destination, bool includeChildren)

 

{

 

 

throw new NotImplementedException();

 

}

 

public

ContentItem Copy(ContentItem source, ContentItem destination)

 

{

 

 

throw new NotImplementedException();

 

}

 

public

void Delete(ContentItem itemNoMore)

 

{

 

 

throw new NotImplementedException();

 

}

 

public

void Flush()

 

{

 

 

throw new NotImplementedException();

 

}

 

public

T Get<T>(int id) where T : ContentItem

 

 

 

{

 

throw new NotImplementedException();

 

}

 

public

ContentItem Get(int id)

 

{

 

 

throw new NotImplementedException();

 

}

 

public

event EventHandler<DestinationEventArgs> ItemCopied;

 

 

 

public

event EventHandler<CancellableDestinationEventArgs> ItemCopying;

 

 

 

public

event EventHandler<ItemEventArgs> ItemDeleted;

 

 

 

public

event EventHandler<CancellableItemEventArgs> ItemDeleting;

 

 

public

event EventHandler<ItemEventArgs> ItemLoaded;

 

 

 

public

event EventHandler<DestinationEventArgs> ItemMoved;

 

 

 

public

event EventHandler<CancellableDestinationEventArgs> ItemMoving;

 

 

 

public

event EventHandler<ItemEventArgs> ItemSaved;

 

 

 

public

event EventHandler<CancellableItemEventArgs> ItemSaving;

 

 

 

public

void Move(ContentItem source, ContentItem destination)

 

 

{

throw new NotImplementedException();

 

 

}

 

public

IRepository<int,ContentItem> Repository

 

 

{

 

 

get { throw new NotImplementedException(); }

 

}

 

public v

oid Save(ContentItem unsavedItem)

 

 

{

 

 

throw new NotImplementedException();

 

}

 

#endregion

#region

IDisposable Members

 

 

public

void Dispose()

 

{

 

 

throw new NotImplementedException();

 

}

 

#endregion

#region

IAutoStart Members

 

 

public

void Start()

 

{

persister.ItemSaving +=

 

new EventHandler<CancellableItemEventArgs>(persister_ItemSaving);

 

}

 

void

persister_ItemSaving(object sender, CancellableItemEventArgs e)

 

{

 

 

throw new NotImplementedException();

 

}

 

#endregion

#region

IStartable Members

 

 

 

public

void Stop()

 

 

{

 

 

throw new NotImplementedException();

 

}

 

#endregion
}

--------------------------------------------

And so the aspx-page:



 

using

System.Web.Security;

 

 

 

 

using

System.Web.UI;

 

 

 

 

using

System.Web.UI.HtmlControls;

 

 

 

 

using

System.Web.UI.WebControls;

 

 

 

 

using

System.Web.UI.WebControls.WebParts;

 

 

 

 

using

N2;

 

 

 

 

using

N2.Templates.Items;

 

 

 

 

using

N2.Web.UI;

 

 

 

 

using

N2.Persistence.NH;

 

 

 

 

using

N2.Engine;

 

 

 

 

using

N2.Persistence;

 

 

 

 

using

N2.Templates.News.UI;

 

 

 

 

public

partial class NewsForMe : N2.Web.UI.ContentPage<NewsTest>

 

{

 

 

public NewsForMe()

 

{

 

}

 

protected void Page_Load(object sender, EventArgs e)

 

{

 

}

 

}

Mar 10, 2009 at 9:05 PM
Hi, your code is very hard to read but from I can figure I can see that you're using IPersister,IAutoStart,IStartable on a content item class which wasn't the intention.

If you want to subscribe to the events you should use N2.Context.Current.Persister.ItemSaving += ...

Mar 11, 2009 at 8:16 AM
Hi!

The event is fired, but where do I place the IAutoStart and IStartable methods? Do I need them?

Best regards,

Per
Mar 11, 2009 at 8:49 AM
Hi!

Do I place the Persister_ItemSaving event in the content class or in the aspx-page?

When I place it in the aspx page and use the CurrentItem property, it is null.

Regards,

Per Ljung
Mar 11, 2009 at 4:26 PM
@p_ljung: I guess you could wire-up your event handler something like this, in the separate class inside your app:

[AutoInitialize]
public class MyEventInitializer: IPluginInitializer
{
    public void Initialize(IEngine engine)
    {
        engine.Persister.ItemSaving += (_sender, _e) => {
            //whatever ...
        };
    }
}

If you're under c#2, then replace
(_sender, _e) =>
by
delegate(object _sender, CancellableItemEventArgs _e)
Mar 11, 2009 at 10:32 PM
Hi and thanks for the answers!

The event is fired and everthing is good so far, but then the object CurrentItem is null, but my aspx-page derives from class that in turn derives from ContentItem.

I try to add all children to the CurrentItem object before saving, the that object is null so I can't.

Can someone show an example of saving an item using the Item_Saving method/event?

Best regards,

Per Ljung

Mar 11, 2009 at 10:43 PM
If you download the source code there are a few examples:

\src\N2.Tests\Persistence\NH\PersisterTests.cs \src\wwwroot\Edit\LinkTracker\Tracker.cs
\src\N2\Integrity\IntegrityEnforcer.cs
\src\N2\Security\SecurityEnforcer.cs

I don't really understand the part about aspx page deriving from content item. The typical pattern is something like this:

public class MyCodebehindPageClass : N2.Web.UI.ContentPage<MyContentItem>

where MyCodebehindPageClass would represent the interface type and MyContentItem the data bearer type.



C:\oss\n2cms\trunk\src\N2\Integrity\IntegrityEnforcer.cs
Mar 12, 2009 at 10:08 AM
Hi!

But how do I connect MyCodeBehindClass and MyContentItem? How do MyContentItem know about MyCodeBehindClass?

And how does this relate to the aspx-page? The aspx-page is what the visitor sees and what the administrator sees in preview mode. I want to right-click on the root node and create a new item of type MyContentItem, but the code in the MyCodeBehindClass doesn't run.

Best regards,

Per
Mar 12, 2009 at 5:53 PM
Edited Mar 12, 2009 at 6:50 PM
Per,

"how do I connect MyCodeBehindClass and MyContentItem" ?

You *may* derive your code-behind class from the ContentPage<ItemType> and magically get an access to your item via this.CurrentItem -- it will already be of type ItemType

"How do MyContentItem know about MyCodeBehindClass?"

Your item shouldn't care about code-behind. Otherwise, it would violate a separation of concerns at the very least.

"And how does this relate to the aspx-page?"

This question is rather general to ASP.Net. The role of code-behind should be very small. There you usually want to do some minor tweaks to a presentation, nothing more. Probably, events wire-up, or so.. You can safely go without code-behind, by removing all reference to it from .aspx . Another relatively important role of code-behind is to specify a base class for your page. This too can be safely done directly in .aspx: <%@ Page Inherits="N2.Web.UI.ContentPage`1[[MyItemType, MyAssembly]], N2" %> instead of how you usually do the same in code-behind " class MyPage: ContentPage<MyItemType> { ... ". Clunky? Sure! If your problem is that your .aspx cannot "see" your code-behind, than ensure the following: if you created your project from a "ASP.Net Web Site" template, then your aspx should start somehow like this <%@Page CodeFile="MyPage.aspx.cs" ... %>. Otherwise, if your web project type is "ASP.Net Web Application" then pay attention that instead of CodeFile attribute you have a CodeBehind one. Messing with these two is a common pitfall.

"The aspx-page is what the visitor sees and what the administrator sees in preview mode"

Exactly! It's the same page.

"the code in the MyCodeBehindClass doesn't run."

That's right, your .aspx page (called "Template") has nothing to the page you see when you create (OR edit) a new item in the Edit interface. The latter page is generated automatically from your content item alone by examining all [Editable*] attributes you've specified over some of your properties. These [Editable*] attributes are the primary mean to control the representation of the Edit page for any given Content Item type. If you really want to run some code when a new item is created, then perhaps the easiest trick would be do it in the content item default constructor. There you can set default properties values, perhaps something else, but not much...
Mar 12, 2009 at 9:36 PM
Edited Mar 12, 2009 at 9:37 PM

Hi and thanks for the answers!

But I wonder some more things. When does the code in MyCodeBehindClass run when I connect it to the MyItemClass trough this "public class MyCodebehindPageClass : N2.Web.UI.ContentPage<MyContentItem>"

I'm not talking about the aspx-page now, think you misunderstood me a little there.

So I should place N2.Context.Current.Persister.ItemSaving += ... in the class MyContentItem?

I have a class used for News, with the properties Introduction, Text and Title. When I create a new item of that type via root-node->right-click-New and type in information the the corresponding textboxes and try to save it, the error message says "error saving transient instance...before flushing content.detail". Sorry didn't remember the whole error message, but I'm sure you recognize it.

I have read that this error can occur if you don't add all attributes to the contentItem before saving. Can I use

News news = N2.Details.CreateInstance<News>

news.Introduction = "bla";
news.Text = "bla";
news.Title = "bla";

and then

this.Children.add(news);

By the way, I know a lot about asp.net and C#, so I'm not a beginner. I hold a Bachelors degree in Informatics. But I think n2cms is a little hard because there isn't so much documentation about it and some documentation is outdated.

Best regards,

Per

Mar 12, 2009 at 11:12 PM
"error saving transient instance" is a bit another story.. if you ever see it -- it's NHibernate, not N2 to blame :-)
the problem is that NHibernate, as it is used by N2, doesn't support cascade saving of items, so if, for instance, you're trying to save an item which somehow depends on another item and that item is not yet persisted, than you'll got this nasty exception. Here's a snippet:

var _myParent = Context.Current.Definitions.CreateInstance<TypeOfParent>(StartPage);
var _myChild = Context.Current.Definitions.CreateInstance<TypeOfChild>(_myParent );
...
Context.Current.Persister.Save(_myChild); //"transient state" exception guaranteed, you must save _myParent first
other scenario:
var _myItem = Context.Current.Definitions.CreateInstance<TypeOfItem>(StartPage);
var _anotherExistingItem = Context.Current.UrlParser.CurrentPage;
_anotherExistingItem["MyPropertyOfTypeOfItem"] = _myItem;
...
Context.Current.Persister.Save(_anotherExistingItem ); //the same "transient state" exception because _myItem wasn't persisted first
In general, you can detect unsaved items by examining their ID -- it will be "0", which isn't possible if an item was ever saved.

I think, to gain some experience in N2, it makes a perfect sense to explore N2.Templates sources -- there are plenty of examples of how a master-detail relationships are carried out. Overall, N2 grounding principles are so simple, that you shouldn't waste time in a seek for a formal docs. It is rather code, that speaks for itself.

Okay, let's way for Cristian's explanations :-)
Mar 13, 2009 at 9:44 PM
Edited Mar 13, 2009 at 9:46 PM
I think esteewhy gave you a very good answer. It's the one about [AutoInitialize] ...MyEventInitializer.

So, you need to intercept savings of certain items and add a child to them?

You can't do this from the content item instances themselves since these are re-created on each request and you would have a memory leak on your hand. You could subscribe to these events Application_Start or using the pugin initializer concept esteewhy mentioned.

Example
global.asax.cs:
...Application_Start(...)
{
    N2.Context.Current.Persister.ItemSaving += persister_ItemSaving;
}
void persister_ItemSaving(object sender, ItemEventArgs args)
{
    if(args.AffectedItem is News)
        CreateNewItem().AddTo(args.AffectedItem);
}

The MyCodeBehindClass I was referring to is an aspx code-behind class deriving from System.Web.UI.Page. ASP.NET creates it and calls methods within it (OnInit, OnPageLoad, etc). This could be a regular ASP page, but dericing from the generic base class gives you the convenience of a CurrentPage property loaded with the associated page. Anyway I'm repeating what's already been said.

In defence of NHibernate. That exception is actually useful in many situations. It tells you that there was an attempt to save an entity from the "wrong" direction. Not having it could lead to entities beeing saved unintentionally. N2 tries to hide away these details, but it's still possible to get it wrong.