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

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