Sense/Net 6.0 Devblog
The development blog of Sense/Net 6.0
Back to Sense/Net

Custom view for the custom fields

January 4, 2010 14:24 by Miklós Tóth

Peter already wrote a few words about managing the custom fields of a Content List. The idea is that the collection of the custom fields can be presented as a list itself. This is possible because the topmost layer (e.g. a ListView) only needs a collection of Contents to work. The background of this - using Content fields with the standard Eval method and the way of presenting the custom fields as a list of Contents - will be a subject of another post (dynamic, in-memory content type creation, anybody?), right now I'm focusing on the presentation.

The solution is to create a custom view for the given ContentList. This view will show a list of the custom fields of the ContentList.

Creating a custom view

A custom view for a ContentList is a simple ASP.NET user control that contains the markup for the chosen data bound control. You can place this control (let's call it MyCustomView.ascx) to the Views folder of any ContentList, and you'll see it in the Views menu on the upper right corner. You can test the view by selecting it from the menu.

We placed two things into this user control:

  • ListGrid: this is a common ASP.NET ListView with a few shiny scripts added
  • SenseNetDataSource control

Using SenseNetDataSource with a ListGrid

Few weeks ago we introduced the SenseNetDataSource control, that makes building data-driven sites a lot easier. Like SQLDataSource, it gives all data bound controls access to pure data - in our case, contents in the Content Repository. One of its functions is to give access to a specific multireference member of a content (e.g.: a Product content may have a Suppliers member that refers to some Supplier contents).

This way we can get a collection of editable custom fields of a ContentList. In markup, it looks like:

<sn:SenseNetDataSource ID="ViewDatasource" MemberName="FieldSettingContents" runat="server"  />

The MemberName can refer to any reference field of a content. In this case we created a "virtual" field called FieldSettingContents for the ContentList class that creates the contents from the custom fields. This is a _very_ special use case, in most cases you will use a simple reference field defined in the content type definition.

We can present the results with any data bound control:

listgrid

Creating a new template for a built-in content list view

January 3, 2010 12:14 by Dániel József

One of the main features of the Xmas preview edition of Sense/Net 6.0 Beta 5 is the Content List View framework. A Content List View is basically an ASP.NET User Control that can be used to display Content items in various fashions. We have created the framework to allow for several levels of customization. Deployers and builders have the option to create entirely new User Controls either with a compiled classbehind dll placed in the binary folder of the Sense/Net installation, or using solely declarative components, such as the newly introduced SenseNetDataSouce. However, existing, built-in views can also be altered, and several variants of a single built-in view can be created in the system.

The template engine

The framework of built-in Content List Views is comprised by two levels - a template level and an instance level. The instance level is the actual User Controls that are loaded and executed by the Webforms engine to display the Content List. Every View configured by users on a single List is represented by an instance containing the configuration-, and metadata of the view, and also the actual User Control code that is auto-generated from said metadata. What facilitates this auto-generation is the template level of the infrastructure.

In creating this template engine, we considered several options to use as the templating language. In the end we have decided for XSLT, considering its wide penetration in the web industry, and also its relative simplicity and power. The code that does the actual work in creating the instance View from the template resides in the class Portal.UI.ContentListViews.Handlers.ViewBase. This is the Content Handler for the Content type ContentListView, the type of View instances. Upon the Save event of a ContentListView, the code contained in this class loads the document referred in the Template property as an XSLT template, and executes the transform on the XML document passed on by the virtual method GetSource(). By overriding this method in the Handlers of specific Content List View types - such as list views, calendar views, icon views, etc. -, the metadata necessary to generate the markup for the given view type can be passed to the template. In the ViewBase class, the data passed to the template is an empty XML document.

In the final product, the selection of this template file will be obscured from the user as an internal detail, but currently it needs to be selected by hand upon creating a View instance. The reason for this is flexibility for testing and prototyping, and also the fact that we were in a hurry, and had more important stuff to code. Wink For you, as a developer checking out the new interesting features of our product, this is most definitely a Good Thing.

Currently there is a single View type implemented in the system, the list view. The list view - not to be confused by the "Content List View" framework, which means the entire big thing - is the "one item per row" view all so know from user interfaces dating back more than twenty years, including those of Norton Commander, Windows Explorer and Microsoft SharePoint Server. The handler class for this View type is Portal.UI.ContentListViews.Handlers.ViewBase.ListView. The XML source used for the transform is the XML formatted list of columns to be displayed in the view.

Editing the template

Now, the User Controls used as the instances for List Views were designed to be user-modifiable. The ClassBehind implementing the logic of the View type contains references to well-known control IDs. Any User Control markup containing the needed controls of the right type with the right IDs will function in this system.

However, the markup of a View instance should - and in fact, can - only be modified through the XSLT template. Any changes to the instance markup itself would be overwritten during Save. To give you a taste of how this works, let's have a look at the built-in template for list views: /Root/System/SystemPlugins/ListView/Templates/ListView.xslt. (This path may change in later releases.)

This is the main template in the XSLT file, describing the outline of the markup. As you can see, we have decided to escape all ASP.NET markup in CDATA blocks. This was necessary to maintain readability and simple editing, as ASP.NET code is non-xml and contains lots of special characters, and so doesn't play nice with XSLT at all. Note how the ClassBehind SenseNet.Portal.UI.ContentListView.ListView is referred in the header, and also note the two known controls: the sn:ListGrid and the sn:SenseNetDataSource. (The ListGrid is a wrapper over the ASP.NET ListView webcontrol, adding JavaScript for visual and behavioral effects. The ClassBehind will accept standard asp:ListView controls without problems.)

To create a new look for your ListView, try creating a copy of this template as MyListview.xslt. At first, try changing the sn:ListGrid to an asp:ListView control without further modifications. Now, take one of the existing List View instances under the demo document library, and modify its Template to MyListView.xslt. Upon saving, the new template will be used to regenerate the markup. On viewing the document library with this View, you will see that the Javascript effects have disappeared.

Further things to play with:

  • Add additional HTML markup to the view.
  • Try to convert the list view template to use an Unordered List instead of a tabular layout.
  • Add an additional, declarative ASP.NET server control to the view.
  • Try databinding it to the SenseNetDataSource.

Sense/Net 6.0 Beta 5 preview is on codeplex.com

December 28, 2009 12:12 by Peter Zentai

The source code only version of the Sense/Net 6.0 Beta 5 has just made available on codeplex as an alpha release.

There are quite a number of things we need to polish before we can reach production quality. And we are hardly working hard on the evaluation materials too that describe the Beta 5 features in detail. So get this version to experiment only with the Sense/Net 6.0 Content Lists, the Templated Field Controls and the Lucene based Content Query.  Do not implement anything important with it just yet.

More...

Searching a tree structure with Lucene.Net

December 16, 2009 03:00 by Peter Zentai

 

   1: /*
   2: Newsflash!
   3: 
   4: It’s now just a couple of days and we release Beta5. 
   5: In that the Content Query infrastructure is moved to the Lucene platform. 
   6: Sense/Net 6.0 endorses the eat your own dogfood philosophy, 
   7: so the Content Query feature is massively used across the system 
   8: in the core functionality. Even the repository type system 
   9: relies on the Content Query layer. 
  10: We expect a nice boost in the global system performance 
  11: in every content releated feature. Read on to find out why.
  12: */

Lucene is amazing – it is really everything you want from a software component: it’s smart, does it’s job well - asking very little in response, greatly simplifies your (professional) life :), open source (so you can feel more confident: you will have a higher success rate in mitigating project risks coming from relying on a 3rd party tool),  extensible and proven.

On our way to implement the Sense/Net Content Repository Lucene Adapter we had some experiences that worth sharing on using Lucene with deeply structured content and path based queries.

As Lucene follows a flat model (one and only one document type, arbitrary fields, no structures), the most trivial approach would be to flatten the tree by storing the position of each node as an extra stored/indexed field added to the node’s own fields– let call it the Path field.

The PrefixQuery

Now, finding nodes in a specific sub-tree looks really simple as we have PrefixQuery at hand:

private IEnumerable<DocumentWrapper> Search()
{
var pathquery = new PrefixQuery(new Term("Path", "c:\\Development"));
var result = Searcher.Search(pathquery);
foreach (Document o in result.Iterator)
{
yield return (new DocumentWrapper(o));
}
}

In your first couple of test scenarios it works nice. So you increase the size of your prototype to let’s say handle quarter a million of items now. Just to run into the the dreaded “TooManyClauses was unhandled” exception

image

It turns out the the PrefixQuery compiles into a list of possible TermQueries connected with OR relation, so basically the query Path = “C:\Development*” transforms into this: Path = “C:\Development\Folder1” OR Path = “C:\Development\Folder2” OR … and so on. The list is capped at 1023.

This 1023 limit looks really bad, but googling around the topic quickly brings up a solution that appears to be working.

The PrefixFilter solution!

The PrefixFilter let’s you efficiently filter rows from the query result based on a prefix criteria.

var query = new MatchAllDocsQuery();
var filter = new PrefixFilter(new Term("Path", "c:\\Development"));
var result = Searcher.Search(query, filter);

The query completes nicely, but with a touch of sluggishness. It has two major drawbacks however

- This approach splits the original query into two parts – a query and a filter, and it would complicate things to a level that would likely to be error prone in complex query cases (where multiple references of filters would be needed)

- Performance drops to mediocre/bad this way: in my test environment (Processor: Core2Duo, 2Ghz, Lucene index: 219000 rows, my complete c:'\ drive effectively) searching for c:\Development* returned 34.000 hits in 500 milliseconds. What is even worse: the execution time does not drop significantly as the result count get smaller. You may say: hey, 500 milliseconds is fantastic for such a mighty query!! yes. for the desktop it is… For the “Server Side”– it isn’t. In our case millions of queries will run a day, most of them around content pathes, and if each would cost us 500ms then we are – let’s face it - mucked.

So what else do we have?

The ConstantScoreQuery Solution?

The ConstantScoreQuery wraps a filter – a PrefixFilter in our case – and turns it into a Query class, ready to be combinef with other query parts. It removes the complexity of the split query model at least.

private IEnumerable<DocumentWrapper> Search()
{
var prefixFilter = new PrefixFilter(new Term("Path", "c:\\Development"));
var query = new ConstantScoreQuery(prefixFilter);
var result = Searcher.Search(query);
....
}

But

- The performance is even worse, 800 milliseconds now

- Plus now we need to give up ranking and scoring or at least we are threatened by constant scores instead of the default relevant scores.

And this is the point where we can be relatively sure that the problem lies in our approach and not in the implementation. Lets think in TERMS.

The Ancestors array property way

Instead of querying the Path field for a prefix match let’s extend our Lucene storage model. On the top of the path field let’s store each and every ancestor’s path of the node in the Ancestor array property.

 

private static Field[] CreateAncestorFields(string path, string separator)
{
string[] fragments = path.Split(new string[] { separator }, StringSplitOptions.None);
List<Field> fields = new List<Field>();
for(int i = 0; i < fragments.Length; i++)
{
string value = string.Join(separator, fragments, 0, i+1);
fields.Add(new Field("Ancestor", value, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
}
return fields.ToArray();
}

so:

the document at c:\development\sensenet\luceneadapter\program.cs will have 4 Ancestor fields, these:

c:\
c:\development
c:\development\sensenet
c:\development\sensenet\luceneadapter

Lucene thinks in TERMS. Each of the above lines became a TERM of its own, and when we search for contents with a specific term as a value for one of the content’s fields, then we are back in Lucene’s domain. A simple TermQuery to the Ancestor field will produce the desired result: in no time.
private IEnumerable<DocumentWrapper> Search()
{
var query = new TermQuery(new Term("Ancestor", "c:\\Development"));
var result = Searcher.Search(query);
....
}

 
 Capture
  Times are milliseconds.

 

And what is the trade here? Index gets bigger of course. For ~219K items this meant about 30 extra MBs. Compared to the total index size which is 1.6GB this tradeoff is acceptable. The memory footprint is small however: searching the index gobbled up only an additional 4MB of memory.

Senset/Net 6.0 Content Repository RESTful API in ASP.NET MVC

December 10, 2009 20:06 by Peter Zentai

From Beta5 Sense/Net 6.0 will be packed together with the ASP.NET MVC assemblies as some of the core functionality is implemented using ASP.NET MVC.

As this point we use ASP.NET MVC in our revamped properietary Content Repository RESTful API. In this interpretation ASP.NET MVC provides us a superlightweight approach to expose RESTful functionality without the complexity and higher cost of ownership of Windows Communication Foundation.

ASP.NET MVC integration to the Sense/Net 6.0 Smart Application Model is in the plan. When finished you will be able to use ASP.NET MVC controllers as the other Sense/Net 6.0 context bindable application types (webform page, Ihttphandler) exploiting our taxonomically spiced declarative application composition concept.
For example:

/workspaces/mysales/documents?action=search can resolve to an MVC controller.action that gets the current context as an MVC model instance.

Tags:

Categories: Feature
Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Sense/Net 6.0 product checkins on Twitter

September 22, 2009 12:08 by Gergely Orosz

As you might have noticed in the recent months Sense/Net 6.0 was under constant development with releases coming out one after the other. The majority of this development at the same time is mostly happening behind the scenes. In order to give more insight to those interested in what is actually happening, we've developed a solution in-house that publishes major Sense/Net 6.0 product check-ins to the @SenseNetDev Twitter account and links them to the Sense/Net 6.0 check-ins collection page.

So if you'd like to see what updates the next release will be containing beforehand simply follow Sense/Net check-ins on Twitter. And of course for product news and announcements be sure to check on our @SenseNet Twitter account.

Screenshot of a working Sense/Net 6.0 Beta 4 Workspace

July 15, 2009 19:42 by Tamás Bíró

Here is the first working version of the Workspace in Sense/Net 6.0 Beta 4. Code release is planned for next Friday, 24th of July. Stay tuned. 

This is a real screenshot made from a working version, unlike the one created in Corel Draw in my previous post.

Sense/Net 6.0 Beta 3.1 with the Web Platform Installer is out!

July 10, 2009 01:11 by Orosz Gergely

We're happy to introduce that an enhanced version of the Sense/Net 6.0 Beta 3, Sense/Net 6.0 Beta 3.1 is out.

Go to http://www.sensenet.hu/download to download. 

Some features in this new release include:

  • New portlets: simple Event Calendar, Forum and Video portlet. These components are expected to undergo further development but we decided to include them in this release due to the positive feedback from our clients
  • Smoother frontend editing interface: instead of the small icons used to edit Portlets a menu dropdown menu has been implemented.
  • Content Picker integrated in the Rich Text editor
  • Sense/Net Application Model
  • Lots of bugfixes

And of all the improvements the greatest one is probably the Sense/Net Web Platform based Installer! Installing or re-installing Sense/Net 6.0 takes just as long as... about 3 minutes See the video for yoursef - which is also available in various formats on the videos page:

The video content presented here requires JavaScript to be enabled and the latest version of the Macromedia Flash Player. If you are you using a browser with JavaScript disabled please enable it now. Otherwise, please update your version of the free Flash Player by downloading here.

Note: before installing you might want to read the Web Platform Installer guide on prerequisites for the install. Also see the video on installing the Web Deployment tool which has to be installed for the Web Platform Installer to work.

Sense/Net 6.0 Installer

April 17, 2009 16:13 by Tamás Bíró

Our team has come up with a new installation method. Instead of restoring a database file, now we have an installation wizard which creates the default structure and contents in the Sense/Net Content Repository. This has many advantages, the best is that it gives us the opportunity to add more default structures and let the user decide which on to use, eg. the one with a sample portal or an empty one. The new installation tool will be released soon.

Sense/Net 6.0 Beta 3 released

March 25, 2009 16:41 by Peter Zentai

It's now only the matter of minutes and our newest release will be revealed on Codeplex. There is a lot work in this release and we do hope you will enjoy its results.

In the last minute we found an important scenario in the Source Code version that leeds to an unexpected behavior by pressing F5 in the VS solution. The Visual Studio  development experience is a key aim for Sense/Net 6.0, therefore the Pending release in Codeplex remains pending untill the solution for the issue is tested. It shouldn't take longer then a couple of hours :). 

I was happy about being rated to 1 by someone. This is a sign of ipatience for me, for what I really thank you guys.

(If you can't wait all you have to do is join to the SenseNet codeplex team to be able to see pending releases)

A week after I initially planned we released the Beta 3 finally. Check http://www.sensenet.hu/download for details.

Bookmark and Share