Widget to access non content data

Topics: Customizing Orchard, Writing modules
May 27, 2011 at 10:04 AM
I'm trying to create a module wich manages non content data through dashboard and then accessing the data using a widget at homepage. till now i have created a Model DxoMenuRecord.cs:
 
namespace DxOrchardMenu.Models
{
    public class DxoMenuRecord {
        public virtual int Id { get; set; }
        public virtual string ItemText { get; set; }
        public virtual string ItemLink { get; set; }
        public virtual string ItemPosition { get; set; }
    }
}

a Migrations.cs: 
 
using System.Data;
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;

namespace DxOrchardMenu {
    public class Migrations : DataMigrationImpl {

        public int Create() {
			// Creating table DxoMenuRecord
			SchemaBuilder.CreateTable("DxoMenuRecord", table => table
				.Column("Id", DbType.Int32, column => column.PrimaryKey().Identity())
				.Column("ItemText", DbType.String)
				.Column("ItemLink", DbType.String)
				.Column("ItemPosition", DbType.String)
			);



            return 1;
        }


        public int UpdateFrom1() {
            ContentDefinitionManager.AlterTypeDefinition("DevexpressMenuWidget",
            cfg => cfg
                .WithPart("WidgetPart")
                .WithPart("CommonPart")
                .WithSetting("Stereotype", "Widget"));

            return 2;
        }
    }
}
 
a ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DxOrchardMenu.Models;

namespace DxOrchardMenu.ViewModels {
    public class DxoMenuViewModel {
        public IEnumerable<DxoMenuRecord> DxoMenuItems { get; set; }
    }
}
 
a Handler:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard.ContentManagement.Handlers;
using Orchard;
using DxOrchardMenu.Services;
using DxOrchardMenu.ViewModels;

namespace DxOrchardMenu.Handlers {
    public class DxoMenuWidgetHandler : ContentHandler {
        private readonly IOrchardServices _orchard;
        private readonly IDxoMenuService _dxoMenuService;

        public DxoMenuWidgetHandler(IOrchardServices orchard, IDxoMenuService dxoMenuService) {
            _orchard = orchard;
            _dxoMenuService = dxoMenuService;
        }

        protected override void BuildDisplayShape(BuildDisplayContext context) {
            base.BuildDisplayShape(context);

            if (context.ContentItem.ContentType == "DevexpressMenuWidget") {
                context.Shape.Model = new DxoMenuViewModel {
                    DxoMenuItems = _dxoMenuService.Get().OrderBy(n => n.ItemPosition)
                };
            }
        }
    }
}
 
 and a View Widget-DevexpressMenuWidget.cshtml
@model DxOrchardMenu.ViewModels.DxoMenuViewModel
<div>Test</div>
<div class="menu">
	<ul>
        @foreach (var item in Model.DxoMenuItems) {
            <li><a href="@item.ItemLink" >@item.ItemText</a></li>
        }
    </ul>
</div>
Trying to access widget get an error:
The model item passed into the dictionary is of type 'IShapeProxy858618aba196481ea5db19bb93ce256e', but this dictionary requires a model item of type 'DxOrchardMenu.ViewModels.DxoMenuViewModel'.
May 27, 2011 at 12:04 PM
Edited May 27, 2011 at 12:04 PM

Basically it's all fine up until this bit:

 

context.Shape.Model = new DxoMenuViewModel {
                    DxoMenuItems = _dxoMenuService.Get().OrderBy(n => n.ItemPosition)
                };

 

The problem is that, most likely, other bits of the whole shape building pipeline are setting or changing the Model after you've changed it.

The most recommended way to add shapes to widgets is to write a Driver. By overriding the entire widget template in the above way (even if it worked) you'd prevent yourself from extending your widget later by adding extra parts.

When you write a driver, the shapes you add can then be controlled by Placement - otherwise it's all hardcoded. You could do context.Shape.Model.Content.Add(context.New.Some_Shape_Name(Model:new DxoMenuViewModel(...))) - which would add a template called Some.Shape.Name.cshtml into the Content zone of the widget, with your model. But drivers are there for a reason ;)

May 27, 2011 at 1:32 PM

But a driver needs a content part. Is it possible to create a Driver?

May 27, 2011 at 2:41 PM

You can create a ContentPartDriver<WidgetPart> if you really don't want to create your own part ... but it's much more useful to have your own part and they don't take long to build.