My Blog

Not a fan of writing but why not give it a try

Orchard View Counter for a Content Item

In this post I’ll create a very simple view counter which can be attached to any content item and it’ll increment everytime someone visits the content item’s page. Here is what we need

  • ContentPart: a content part which is the model
  • ContentPartRecord: since we are saving data to the database
  • Handler: to save the count to the database
  • Driver: this is where the counter will be incremented
  • Migration: to create a database table
  • Shape: this is optional. You don’t have to view the count this way but you can

ContentPart and ContentPartRecord


Let’s dive into the code and add a file ViewCounterPart.cs to Models directory with following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ViewCounterPart : ContentPart<ViewCounterPartRecord>
{
    [Required]
    public int Views
    {
        get { return Record.Views; }
        set { Record.Views = value; }
    }
}

public class ViewCounterPartRecord : ContentPartRecord
{
    public virtual int Views { get; set; }
}

Handler


This part is saving data to the database so we must create a handler for it. Create a file named ViewCounterPartHandler.cs to Handlers directory with following content:

1
2
3
4
5
6
7
public class ViewCounterPartHandler : ContentHandler
{
    public ViewCounterPartHandler(IRepository<ViewCounterPartRecord> repository)
    {
        Filters.Add(StorageFilter.For(repository));
    }
}

Driver


This class is used everytime Orchard tries to render a content item with ViewCounterPart attached. Therefore I’ll place the incrementation logic here. NOTE there is no ip filtering to make sure views are added for unique users only. Every page refresh will increment the view by 1 (unless page is cached). You can add ip filtering yourself if you wish

Add the following content to ViewCounterPartDriver.cs in Drivers directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ViewCounterPartDriver : ContentPartDriver<ViewCounterPart>
{
    protected override string Prefix
    {
        get { return "ViewCounter"; }
    }

    private readonly IWorkContextAccessor _workContext;
    public ViewCounterPartDriver(IWorkContextAccessor workContext)
    {
        _workContext = workContext;
    }

    protected override DriverResult Display(ViewCounterPart part, string displayType, dynamic shapeHelper)
    {
        // Do not increment if in admin menu
        if (AdminFilter.IsApplied(_workContext.GetContext().HttpContext.Request.RequestContext)) {
            return null;
        }

        // Increment the part.Views, it'll be saved to the database automatically
        part.Views++;

        return null; // If you wish to display a shape you must replace this line
    }
}

if you decided to add a shape replace the last return null with the following

1
2
3
return ContentShape("Parts_ViewCounter", () => shapeHelper.Parts_ViewCounter(
    Views: part.Views
));

Migration


We’ll need to create a database table and a content part (you can also attack it to a content item in the migraion). Add following to Migrations.cs

1
2
3
4
5
6
7
8
9
10
SchemaBuilder.CreateTable(typeof(ViewCounterPartRecord).Name,
    table => table
    .ContentPartRecord()
    .Column<int>("Views", column => column.NotNull())
);

ContentDefinitionManager.AlterPartDefinition(typeof(ViewCounterPart).Name,
    part => part
        .Attachable()
);

Shape (optional)


If you decided to render a shape you can add the file Views/parts/ViewCounter.cshtml with following content:

1
@(Model.Views ? Model.Views : 0)

That snippet will only render a number (if exists). Feel free to add better markup, container and styles to it.