Tech Musings Just another tip's, tricks, and how-to's blog

12Sep/113

Shape Methods as an Alternative to Widgets

I've talked about shape methods in my previous post, and talked about using shape builders as alternatives to widgets in an earlier iteration of our designs, but when talking with someone in IRC I realized that going into detail about how we utilize shape methods to render different pieces of our website in different ways in different places might be helpful. So here goes:

Say for instance you had the need to display a Facebook like button at different places throughout the site, on different types of content, in different ways. You could create a widget to display on certain layers, but creating a widget just to display a Like button seems over the top. We have probably 100 different widget-like shapes that get displayed in various ways on our site. That many widgets would be a pain to deal with, and aren't nearly as flexible in how we can interact with them. My colleague, Jesse Wise, came up with a beautiful, simple, painless way to do without widgets: By using shape methods as sort of a partial controller/partial view.

Here is the code for our Like button:

[Shape]
        public IHtmlString ContentLike(dynamic Display, ContentItem item, int Width, int Height, string ShowFaces,string ButtonType)
        {
            string url;
            var shortUrl = item.As<ShortUrlPart>();
            if (shortUrl != null)
            {
                if (String.IsNullOrEmpty(shortUrl.ShortUrl))
                { //if the short url wasn't set at content creation time then we're creating it now. (helpful for pre-existing content)
                    shortUrl.ShortUrl = ShortUrlService.GetShortUrl(GetAbsoluteURL(item));
                }
                url = shortUrl.ShortUrl;
            }
            else
            {
                url = ShortUrlService.GetShortUrl(GetAbsoluteURL(item));
            }
            var title = GetTitle(item);
            return Display.PageLike(Display: Display, pageUrl: url, Width: Width, Height: Height, ShowFaces: ShowFaces,Title:title,ButtonType:ButtonType);
        }

[Shape]
        public IHtmlString PageLike(dynamic Display, string pageUrl, int Width, int Height, string ShowFaces)
        {
            return Display.LikeBlogView(ContentUrl: pageUrl, Width: Width, Height: Height, ShowFaces: ShowFaces);
        }

And then in LikeBlogView.cshtml:

@{

    int width = Model.Width;
    int height = Model.Height;
    string faces = Model.ShowFaces;
    string url = Model.ContentUrl;
}
@{
    <fb:like href="@url" send="false" width="@width" height="@height" show_faces="@faces" font=""></fb:like>
}

So as you can see, we have sort of a mini controller that handles the logic outside of the view, then passes the necessary data into the view for rendering. I'll touch on a couple of the finer points here:

public IHtmlString ContentLike(dynamic Display, ContentItem item, int Width, int Height, string ShowFaces,string ButtonType)

With a shape method, you can inject a ShapeHelper into the method by creating a dynamic parameter named Display. This is what allows the magic to happen down below where we pass off rendering to the Razor view.

ShortUrlService is actually a read-only property that resolves the IShortUrlService at call-time:

 private IShortUrlService ShortUrlService {
            get {
                return _workContextAccessor.GetContext(_httpContextAccessor.Current()).Resolve();
            }
        }

This may or may not be necessary, and I think it may be a hold-over from our original solution to the widget problem, as the Orchard docs show being able to inject services in the normal fashion.

return Display.LikeBlogView(ContentUrl: pageUrl, Width: Width, Height: Height, ShowFaces: ShowFaces);

Display.ShapeName will return an IHtmlString, allowing us to use the result of a Razor view as the return for our shape method. This is the part that really tied everything together, as it allows for a re-usable, lightweight means of separating our logic from our view.

To call ContentLike, you can simply place @Display.ContentLike(myContentItem, 100, 50, "true", "normal") in any view that you have a reference to the content item that you want to use with the Like button. Voila.

Now, I must add as a caveat that widgets have their place. If I was distributing a piece of UI as part of a module that I wanted a user to be able to place on any page without any effort, I would use a widget. If I'm writing my own code and I have the need to re-use specific functionalities/partial views throughout my site, a shape method is the obvious choice.

31May/115

Shape Method Caching

So we're preparing to launch our Orchard site, and one of the final steps is tuning the performance of the site. The Orchard team realizes that caching is a huge part of a CMS, but has left the framework open in such a way that a caching module should be relatively simple to write, so we did just that.

The way we use Orchard may be a little different than most, in that our site is almost entirely composed of little widgets of different pieces of content, but we don't use widgets at all because we found they did not suit our needs properly. Instead we use [Shape] methods to define a shape name, and then a corresponding view to go behind, as so:

        [Shape]
        public IHtmlString SidebarAd(dynamic Display) {
            var ads = ContentManager.Query<HouseAdPart>(VersionOptions.Latest).List().ToList();
            return Display.SidebarAdView(Ads: ads);

        }

This allows us to do Display.SidebarAd() from anywhere else on the site to plop this particular shape in wherever, and we maintain our separation of concerns by using a shape method as a sort of controller, with a view behind it called SidebarAdView (which is a razor view).

As I started investigating how we might go about caching, I decided that we would get about 90% cache coverage on our site by just being able to add an attribute to these shape methods designating that they should be cached. (The other 10% are just razor views that don't have a shape method in front of them, but will likely be refactored to have one soon).

To start out, I basically duplicated the structure of the Orchard team's ShapeAttribute, and called it CacheAttribute:

public class CacheAttribute : Attribute
    {
        public CacheAttribute() {
            Duration = 60 * 5;
        }
        public CacheAttribute(int duration)
        {
            Duration = duration;
        }

        public int Duration
        {
            get;
            private set;
        }
    }
public class CacheAttributeOccurence {

        public CacheAttributeOccurence(CacheAttribute cacheAttribute, MethodInfo methodInfo, IComponentRegistration registration) {
            CacheAttribute = cacheAttribute;
            MethodInfo = methodInfo;
            Registration = registration;
        }

        public CacheAttribute CacheAttribute { get; private set; }
        public MethodInfo MethodInfo { get; private set; }
        public IComponentRegistration Registration { get; private set; }

    }
public class CacheAttributeBindingModule : Module {
        readonly List<CacheAttributeOccurence> _occurences = new List<CacheAttributeOccurence>();

        protected override void Load(ContainerBuilder builder) {
            builder.RegisterInstance(_occurences).As<IEnumerable<CacheAttributeOccurence>>();
        }
        protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) {
            var occurences = registration.Activator.LimitType.GetMethods()
                .SelectMany(mi => mi.GetCustomAttributes(typeof (CacheAttribute), false).OfType<CacheAttribute>()
                    .Select(ca => new CacheAttributeOccurence(ca,mi,registration))
                ).ToArray();
            if (occurences.Any())
                _occurences.AddRange(occurences);
        }
    }

So now I can implement a binding strategy that enumerates each CacheAttributeOccurence and configures caching for each shape method. I started out by just configuring a standard IShapeTableProvider like so:

public class CacheAttributeBindingStrategy : IShapeTableProvider
    {
        private readonly IEnumerable<CacheAttributeOccurence> _occurrences;
        private readonly Cache<string, IHtmlString> _cache;

        private readonly IWorkContextAccessor _workContextAccessor;
        private readonly IHttpContextAccessor _httpContextAccessor;
        public CacheAttributeBindingStrategy(IEnumerable<CacheAttributeOccurence> occurences, ICacheManager cache, IWorkContextAccessor workContextAccessor, IHttpContextAccessor httpContextAccessor)
        {
            _workContextAccessor = workContextAccessor;
            _httpContextAccessor = httpContextAccessor;
            _occurrences = occurences;

            _cache = cache.GetCache<string, IHtmlString>() as Cache<string, IHtmlString>;
        }

        public void Discover(ShapeTableBuilder builder)
        {
            foreach (var occurence in _occurrences)
            {
                var shape = occurence.MethodInfo.GetCustomAttributes(typeof(ShapeAttribute), false).OfType<ShapeAttribute>().FirstOrDefault();
                if (shape != null)
                {
                    var shapeType = shape.ShapeType ?? occurence.MethodInfo.Name;
                    CacheAttributeOccurence occurence1 = occurence;
                    builder.Describe(shapeType)
                        .OnDisplaying(displaying =>
                        {

                        })
                        .OnDisplayed(displayed =>
                        {

                        });
                }
            }
        }

I had read a comment in the forums where Bertrand explained how caching could be just as simple as implementing OnDisplaying and OnDisplayed for a shape and populating the ChildContent if a shape exists in cache already.
The trouble was, how could I construct a key for my cache that can be autogenerated based on the parameter names and values for a shape method execution context. As it turns out, my answer again lay in the implementation of the ShapeAttribute. So I grabbed a few methods from their implementation, modified them a bit to fit my needs, and implemented them into my CacheAttributeBindingStrategy:

public static bool TypeImplementsInterface(Type type, Type interfaceType)
        {
            string interfaceFullName = interfaceType.FullName;
            return type.GetInterface(interfaceFullName) != null;
        }
        private static KeyValuePair<string, string> BindParameter(dynamic shape, ParameterInfo parameter)
        {
            if (parameter.Name == "Shape") return new KeyValuePair<string, string>(null, null);

            if (parameter.Name == "Display") return new KeyValuePair<string, string>(null, null);

            if (parameter.Name == "Output" && parameter.ParameterType == typeof(TextWriter))
                return new KeyValuePair<string, string>(null, null);

            // meh--
            if (parameter.Name == "Html")
            {
                return new KeyValuePair<string, string>(null, null);
            }

            var getter = _getters.GetOrAdd(parameter.Name, n =>
                CallSite<Func<CallSite, object, object>>.Create(
                Microsoft.CSharp.RuntimeBinder.Binder.GetMember(
                CSharpBinderFlags.None, n, null, new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) })));

            var result = getter.Target(getter, shape);

            if (result == null)
                return new KeyValuePair<string, string>(null, null);

            switch (Type.GetTypeCode(parameter.ParameterType))
            {
                case TypeCode.Boolean:
                case TypeCode.String:
                case TypeCode.Char:
                case TypeCode.DateTime:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                case TypeCode.SByte:
                case TypeCode.Byte:
                    var converter = _converters.GetOrAdd(parameter.ParameterType, CompileConverter);
                    var argument = converter.Invoke(result);
                    return new KeyValuePair<string, string>(parameter.Name, argument.ToString());
                    break;
                case TypeCode.Object:
                    if (TypeImplementsInterface(parameter.ParameterType, typeof(IContent)))
                    {
                        var converter2 = _converters.GetOrAdd(parameter.ParameterType, CompileConverter);
                        var argument2 = converter2.Invoke(result) as IContent;
                        if (argument2 != null)
                            return new KeyValuePair<string, string>(parameter.Name, argument2.Id.ToString());
                        else
                            return new KeyValuePair<string, string>(parameter.Name, "NULL");

                    }
                    return new KeyValuePair<string, string>(null, null);
                case TypeCode.DBNull:
                case TypeCode.Empty:
                default:
                    return new KeyValuePair<string, string>(null, null);
            }

            return new KeyValuePair<string, string>(null, null);

        }
        private static string ConstructCacheKey(string type, Shape shape, CacheContext cacheContext)
        {
            var arguments = cacheContext.MethodInfo.GetParameters()
                .Select(parameter => BindParameter(shape, parameter));

            var strings = new List<string>(arguments.Count() * 2);
            foreach (var p in arguments)
            {
                if (p.Key == null)
                    continue;

                strings.Add(p.Key);
                strings.Add(p.Value);
            }
            return type + string.Join("_", strings);
        }
        static readonly ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>> _getters =
            new ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>>();

        static readonly ConcurrentDictionary<Type, Func<object, object>> _converters =
            new ConcurrentDictionary<Type, Func<object, object>>();
        static Func<object, object> CompileConverter(Type targetType)
        {
            var valueParameter = Expression.Parameter(typeof(object), "value");

            return Expression.Lambda<Func<object, object>>(
                Expression.Convert(
                    Expression.Dynamic(
                        Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.ConvertExplicit, targetType, null),
                        targetType,
                        valueParameter),
                    typeof(object)),
                valueParameter).Compile();
        }

While I didn't have access to the context they used in the ShapeAttribute strategy, I found I could just as easily bind to the shape I was given to get the parameters for the shape method. Now to implement the actual caching code:

public void Discover(ShapeTableBuilder builder)
        {
            foreach (var occurence in _occurrences)
            {
                var shape = occurence.MethodInfo.GetCustomAttributes(typeof(ShapeAttribute), false).OfType<ShapeAttribute>().FirstOrDefault();
                if (shape != null)
                {
                    var shapeType = shape.ShapeType ?? occurence.MethodInfo.Name;
                    CacheAttributeOccurence occurence1 = occurence;
                    builder.Describe(shapeType)
                        .OnDisplaying(displaying =>
                        {
                            var cacheContext = new CacheContext { MethodInfo = occurence1.MethodInfo, CacheHit = false };
                            displaying.Shape._cacheContext = cacheContext;
                            string key = ConstructCacheKey(shapeType, displaying.Shape, cacheContext).ToString();
                            if (!string.IsNullOrWhiteSpace(key)) {
                                var result = _cache.TryGet(key);
                                if (result != null)
                                {
                                    displaying.ChildContent = result;
                                    cacheContext.CacheHit = true;
                                }
                            }
                        })
                        .OnDisplayed(displayed =>
                        {
                            var cacheContext = displayed.Shape._cacheContext as CacheContext;
                            if (cacheContext == null)
                                return;
                            if (cacheContext.CacheHit)
                            {
                                return;
                            }
                            string key = ConstructCacheKey(shapeType, displayed.Shape, cacheContext).ToString();
                            if (!string.IsNullOrWhiteSpace(key))
                            {

                                _cache.Get(key, acquire =>
                                {
                                    acquire.Monitor(Clock.When(TimeSpan.FromSeconds(occurence1.CacheAttribute.Duration)));
                                    return displayed.ChildContent;
                                });

                            }
                        });
                }
            }
        }

You'll notice in my code that I'm using a method that doesn't exist in the default cache implementation: .TryGet(). The reason being is that .Get() forces you to have a way to populate the cache at execution time if the item doesn't exist in cache, which I'm unable to do so because the shape hasn't been displayed yet. Instead I had to suppress the default Orchard CacheHolder and construct my own:

 [OrchardSuppressDependency("Orchard.Caching.DefaultCacheHolder")]
    public class CacheHolder : ICacheHolder
    {
        private readonly ConcurrentDictionary<CacheKey, object> _caches = new ConcurrentDictionary<CacheKey, object>();
        class CacheKey : Tuple<Type, Type, Type>
        {
            public CacheKey(Type component, Type key, Type result)
                : base(component, key, result)
            {
            }
        }
        public ICache<TKey, TResult> GetCache<TKey, TResult>(Type component) {
            var cacheKey = new CacheKey(component, typeof(TKey), typeof(TResult));
            var result = _caches.GetOrAdd(cacheKey, k => new ACLJ.Caching.Cache<TKey, TResult>());
            return (Cache<TKey, TResult>)result;
        }
    }
public class Cache<TKey, TResult> : ICache<TKey, TResult>
    {
        private readonly ConcurrentDictionary<TKey, CacheEntry> _entries;

        public Cache()
        {
            _entries = new ConcurrentDictionary<TKey, CacheEntry>();
        }
        public TResult TryGet(TKey key) {
            CacheEntry value = null;
            var entry = _entries.TryGetValue(key, out value);
            if (value == null)
                return default(TResult);

            return value.Result;
        }
        public TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = _entries.AddOrUpdate(key,
                // "Add" lambda
                k => CreateEntry(k, acquire),
                // "Update" lamdba
                (k, currentEntry) => (currentEntry.Tokens.All(t => t.IsCurrent) ? currentEntry : CreateEntry(k, acquire)));

            // Bubble up volatile tokens to parent context
            if (CacheAquireContext.ThreadInstance != null)
            {
                foreach (var token in entry.Tokens)
                    CacheAquireContext.ThreadInstance.Monitor(token);
            }

            return entry.Result;
        }

        private static CacheEntry CreateEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = new CacheEntry { Tokens = new List<IVolatileToken>() };
            var context = new AcquireContext<TKey>(k, volatileItem => entry.Tokens.Add(volatileItem));

            IAcquireContext parentContext = null;
            try
            {
                // Push context
                parentContext = CacheAquireContext.ThreadInstance;
                CacheAquireContext.ThreadInstance = context;

                entry.Result = acquire(context);
            }
            finally
            {
                // Pop context
                CacheAquireContext.ThreadInstance = parentContext;
            }
            return entry;
        }

        private class CacheEntry
        {
            public TResult Result { get; set; }
            public IList<IVolatileToken> Tokens { get; set; }
        }
    }

    /// <summary>
    /// Keep track of nested caches contexts on a given thread
    /// </summary>
    internal static class CacheAquireContext
    {
        [ThreadStatic]
        public static IAcquireContext ThreadInstance;
    }

Voila. Now I can just apply my CacheAttribute to any shape methods I want to cache like so:


        [Shape, Cache]
        public IHtmlString ContentImage(dynamic Display, ContentItem item, string suffix) {
            var image = item.As<ImageAttachmentPart>();

            if (image != null && image.Image != null)
            {
                var imageUrl = ImageService.GetUrl(image.Image.ContentItem.As<ImagePart>(), suffix);
                var dim = ImageService.GetDimensions().Where(d => d.Suffix == suffix).First();
                return Display.ImageView(ImageUrl: imageUrl, Width: dim.Width, Height: dim.Height);
            }

            return T("");
        }

And the result of this shape method will be stored in the cache based on the parameters specified to the shape method.

My next step is to make the parameter analysis when constructing the cache key a little bit more foolproof, as it won't handle any complex types. I'd also like to add some options to the CacheAttribute that allow it to specify which parameters should be used to construct the cache key.

I'm also trying to think of how to easily cache a shape that is defined only in a razor view, so if any one has any ideas on how to go about that, hit me up. If I can solve those problems, I'll release the module to the community.

21Feb/110

Draftable Content (or how to enable the publish button)

Today, for anyone new to Orchard, I thought I'd share how to go about dissecting the Orchard source in order to discover how to implement something in Orchard that doesn't have clear documentation yet:

One of the requirements for our YouTube feed is to allow editors to mark the video as published before it displays on our site to allow them to update any meta-data. To do so I import the video as a draft and everything works great. However, today I just discovered that in the editor for my Video type, I have no way to allow the editors to publish the video. This prompted me to go on a little scavenger hunt... ;)

I knew that Blog Posts have a Publish button, but couldn't figure out where that particular shape gets rendered from to add it onto my content type. I inspected the source of the blog post editor and found that the publish button lives in a fieldset with a class name of "publish-button". I did a Find In Files in the Orchard solution in Visual Studio and found a shape called Content.PublishButton.cshtml which contained it:

A search for PublishButton uncovered the ContentsDriver class which contains the following two lines of code:

if (part.TypeDefinition.Settings.GetModel().Draftable)
                results.Add(ContentShape("Content_PublishButton", publishButton => publishButton));

This adds the Content_PublishButton shape to any content item that has a Draftable setting. Bingo! Now all I have to do is add the Draftable setting to my migrations for my content type... But hmm, how the heck do I add the Draftable setting? Another quick search for Draftable uncovers the type definition for a BlogPost in the SetupService class (which is basically the initial migration for the Orchard site).

contentDefinitionManager.AlterTypeDefinition("BlogPost", cfg => cfg
                .WithPart("CommentsPart")
                .WithPart("TagsPart")
                .WithPart("LocalizationPart")
                .Draftable()
                .Indexed()
                );

There we go, I can just alter my Video type definition and add the .Draftable() method:

public int UpdateFrom3() {
            ContentDefinitionManager.AlterTypeDefinition("Video",
                                                         cfg => cfg
                                                                    .Draftable()
                );
            return 4;
        }

I hope this may help some as they are exploring Orchard and wondering, "how'd they do that?" Good luck!

Update:
I almost forgot. Now that the editor contains the publish now button, I have to make my controller aware of how to use it:

[HttpPost, ActionName("Edit")]
        [FormValueRequired("submit.Save")]
        public ActionResult EditPOST(int videoId)
        {

            var item = Services.ContentManager.Get(videoId, VersionOptions.Latest).As<VideoPart>();
            var model = Services.ContentManager.UpdateEditor(item.ContentItem, this);

            if (!ModelState.IsValid)
            {
                Services.TransactionManager.Cancel();
                return View(model);
            }

            return RedirectToAction("Index");
        }

        [HttpPost, ActionName("Edit")]
        [FormValueRequired("submit.Publish")]
        public ActionResult EditAndPublishPOST(int videoId)
        {
            var item = Services.ContentManager.Get(videoId, VersionOptions.DraftRequired).As<VideoPart>();
            var model = Services.ContentManager.UpdateEditor(item.ContentItem, this);

            if (!ModelState.IsValid)
            {
                Services.TransactionManager.Cancel();
                return View(model);
            }

            Services.ContentManager.Publish(item.ContentItem);

            return RedirectToAction("Index");
        }

The [FormValueRequired("submit.Publish")] attribute allows me to specify which controller action gets executed depending on which button was pressed (save or publish).

18Feb/115

Background Tasks in Orchard

As I was implementing a module that consumes a YouTube feed today, I came across a need to have a background process running to periodically check our YouTube feed to see if any new videos have been added and create content items for them for admin approval in the back-end. After a little research I discovered the IBackgroundTask interface that did just the trick:

public class YouTubeRefresher : IBackgroundTask {
        private readonly IVideoService _videoService;
        private readonly IClock _clock;
        public IOrchardServices Services { get; set; }
        public YouTubeRefresher(IOrchardServices services, IVideoService videoService, IClock clock) {
            Services = services;
            _videoService = videoService;
            _clock = clock;
        }
        public void Sweep() {
            Refresh();
        }

        private void Refresh() {

            var settings = Services.WorkContext.CurrentSite.As<MediaSettingsPart>();
            if (settings.Record.NextYouTubeRefreshUtc == null) {
                _videoService.InitialYouTubeLoad();
            } else {
                if (settings.Record.NextYouTubeRefreshUtc > _clock.UtcNow) {
                    _videoService.RefreshYouTubeFeed();
                }
            }
        }
    }

IBackgroundTask has one method: Sweep(). This is called every so often (not sure exactly, but seems to be about every 60 seconds) and executes whatever code is in the Sweep() implementation.

I don't need to refresh the YouTube feed once a minute, so I created a setting in my module called NextYouTubeRefreshUtc and set it to _clock.UtcNow.AddMinutes(20) to ensure at least a 20 minute delay between each YouTube refresh.

I'm sure you can see how this is an extremely useful part of the Orchard framework. Thanks Orchard team!

15Feb/116

Orchard Shape Wizardry

While working on my employer's new website, we discovered that we had a need that didn't seem to fit into the Widget system for Orchard. Several places throughout our site, we need to be able to trigger the creation of a shape from a template, but have that shape populated with data through a controller of sorts. After a lengthy discussion with Bertrand Le Roy (who is ever so helpful), I stumbled upon the solution that we were looking for: IShapeTableProvider.

Inside of my template somewhere, I can make a call like so:
WorkContext.Layout.BeforeHeader.Add(New.HeaderBar(), "1");
This creates a new shape called HeaderBar and adds it to the BeforeHeader zone I have configured in our theme. Elsewhere (we have it in a common module we use to contain widgets & shapes that may be site-wide but don't make sense to keep in the template), I can create a shape called HeaderBar.cshtml and the shape factory will automatically use that Razor template to display the shape. However, now we need to provide data to the shape.

To do so, I created a ShapeProviders namespace in our common module and added the following class:

public class HeaderBar : IShapeTableProvider {
        private readonly IWorkContextAccessor _workContextAccessor;
        private readonly IHttpContextAccessor _httpContextAccessor;

        // not injected the usual way because this component is a 'static' dependency and any services that consume data are per-request
        private ICaseService CaseService
        {
            get
            {
                return _workContextAccessor.GetContext(_httpContextAccessor.Current()).Resolve<ICaseService>();
            }
        }
        public HeaderBar(IWorkContextAccessor workContextAccessor, IHttpContextAccessor httpContextAccessor)
        {
            _workContextAccessor = workContextAccessor;
            _httpContextAccessor = httpContextAccessor;
        }
        public void Discover(ShapeTableBuilder builder)
        {
            builder.Describe("HeaderBar").OnCreated(created =>
            {
                created.Shape.Cases = CaseService.GetFeaturedCases().Slice(0,4).ToList();                

            });
        }
    }

So what this is doing is describing the HeaderBar shape that we're using, and hooking into the OnCreated event for this shape. This allows us to call into a service and grab the data we need to populate the shape and assign it to the shape.

One thing I need to note, and this took me an entire day to discover, is that you can't just inject your data service into your IShapeTableProvider implementation. If you do, it'll try to use a transaction out of scope and that will cause you all kinds of problems. After tearing my hair out for hours on end, I finally discovered what the Orchard team is doing inside of the CoreShapes class: The trick to resolve a service dependency within the function itself by using a property that loads the service per-request.

Once I did that, I was able to use the data within my Razor view easily:

@using Orchard.ContentManagement;
@using Orchard.Core.Routable.Models;
@{
    var cases = Model.Cases;

}
Current Cases:
    @foreach (var c in cases) { var item = c as ContentItem; var route = item.As();
  • @route.Title
  • |
  • }
  • More...
Get Email Updates | Already Registered? Login