Skip to main content

A DD4T.net Implementation - Pagination (Models)

This post adds pagination capabilities to an AJAX Dynamic Component Presentation (DCP) presented in an earlier post Rendering Only a Partial for AJAX.

The use case is the following: we have a controller that displays a list of DCPs in a paginated way. Due to the dynamic nature of this list, the list of DCPs should be rendered as a partial using AJAX. This implies that an actual AJAX request is made to the controller, which triggers only the list of DCPs be recalculated and served back. The paginated AJAX URL produces HTML directly, therefore the Javascript logic can simply replace the container div 'innerHTML' with the markup received from pagination. A whole page reload is thus not necessary. Additionally the list of DCPs can be sorted before being paginated, but about that in a later post.

This post descries the server-side model builder that generated the paginated list of models. In a subsequent post I will present the server-side views that render the same and the client-side Javascript performing the AJAX handling.

In RouteConfig.cs, I modified the partial AJAX route, by adding the lines highlighted below. They basically accept a new URL path parameter 'page' of type integer:

    routes.MapRoute(
        name: "DCP",
        url: "{controller}/{action}/{publication}/{component}/{view}/{partial}/{page}.ajax",
        defaults: new
        {
            partial = UrlParameter.Optional,
            page = UrlParameter.Optional
        },
        constraints: new
        {
            controller = @"\w{1,50}",
            action = @"\w{1,50}",
            publication = @"\d{1,3}",
            component = @"\d{1,6}",
            view = @"\w{1,50}",
            partial = @"_[\w\.]{1,50}",
            page = @"\d{0,3}"
        }
    );

An example URL for calling this partial paginated URL would be:

http://my.server.com/Device/Index/123/456/FullDetail/_PartialDocument/2.ajax

The URL above calls controller Device, action method Index on Component with id tcm:123-456 and uses view FullDetail to retrieve the DD4T strong model. The controller dispatches to view _PartialDocument.cshtml and displays page 2 of the list of documents that the controller built.

The models that make all this possible and their builders are as follows. We start with a very simple BaseBuilder class that holds a generic type 'ModelType' representing the actual model we build from. The class is abstract because it simply defines the signature of a Build() method to be implemented in one of the specific subclasses of it:

public abstract class BuilderBase<ModelType>
{
    public ModelType Model { get; protected set; }

    public BuilderBase(ModelType model)
    {
        Model = model;
    }

    public abstract void Build();
}

Next, we define base class AjaxBase, another generic that provides some common properties for AJAX calls, such as controller name, view, partialView, partialUrl:

public abstract class AjaxBase<ModelType>
    : BuilderBase<ModelType> where ModelType : ModelBase
{
    public string Controller { get; private set; }
    public bool IsPostback { get; protected set; }
    public string View { get; private set; }
    public string PartialView { get; private set; }
    public string PartialUrl
    {
        get
        {
            Uri uri = HttpContext.Current.Request.Url;
            TcmUri modelId = new TcmUri(Model.Id);

            return string.Format("http://{0}{1}/{2}/Index/{3}/{4}/{5}/{6}",
                uri.Host,
                uri.Port == 80 ? string.Empty : string.Format(":{0}", uri.Port),
                Controller,
                modelId.PublicationId,
                modelId.ItemId,
                View,
                PartialView);
        }
    }

    public AjaxBase(ModelType model, string controller,
        string view, string partialView)
        : base(model)
    {
        Controller = controller;
        View = view;
        PartialView = partialView;
    }
}

Next, we define the PaginationBase class. In order not to reinvent the wheel, this class makes use of the package PagedList, which "makes it easier for .Net developers to write paging code", and "allows you to take any IEnumerable(T) and by specifying the page size and desired page index, select only a subset of that list". The class defines properties such as PageNumber and PageSize that will be used later on to determine which sub-set of the paged list to display. The class also helps us construct the paged partial URL that we call from client-side Javascript.

public abstract class PaginationBase<ModelType, PagedType> : AjaxBase<ModelType>
    where ModelType : ModelBase
    where PagedType : ModelBase
{
    public IPagedList<PagedType> PagedItems { get; protected set; }
    public int PageNumber { get; set; }
    public int PageSize { get { return 10; } } // just an example

    public PaginationBase(ModelType model, string controller, string view,
        string partialView, int pageNumber)
        : base(model, controller, view, partialView)
    {
        PageNumber = Math.Max(1, pageNumber);
    }

    public string GetPagerUrl(int pageNumber)
    {
        return string.Format("{0}/{1}.ajax", PartialUrl, pageNumber);
    }

    protected void ToPagedList(IEnumerable<PagedType> items)
    {
        PagedItems = items.ToPagedList(PageNumber, PageSize);
    }
}

Next, the actual implementation of the paginated model is class DocumentPartial. This class defines the Build() method that creates the list of Document object and then paginates it. Notice the use of IsPostback property -- this implies the method Build is only invoked during a postback from AJAX, otherwise this model partial does not actually build the paginated list.

public class DocumentPartial : PaginationBase<Device, Document>
{
    public DocumentPartial(Device device, int pageNumber)
        : base(device, "Device", "Device", "_PartialDocument", pageNumber)
    { }

    public override void Build()
    {
        IsPostback = true;
        IEnumerable<Document> documents = // fetch the documents...
        ToPagedList(documents);
    }
}

Finally, the calling code sits inside the controller. In our case, the Device controller, action Index has the following simplified logic. The DocumentPartial class is used to build the list of Document only if the partial variable is actually specified and has value '_PartialDocument'; otherwise, the DocumentPartial is created, but not built. The controller either dispatches directly to the _PartialDocument partial view with the corresponding paginated list of Document, or it attaches the empty DocumentPartial to the ViewBag for later processing during the Device view.

public class DeviceController
{
    public ActionResult Index(int? publication, int? component,
            string view, string partial, int? page)
    {
        Device device;
        string viewName;
        ResolveModelAndView<Device>(publication, component, view, out device,
                out viewName);

        if (partial == null || partial == "_PartialDocument")
        {
            var documentPartial = new DocumentPartial(device, page ?? 1);
            if (partial == null)
            {
                ViewBag.DocumentPartial = documentPartial;
            }
            else
            {
                documentPartial.Build();
                return View(partial, documentPartial);
            }
        }

        return View(viewName, device);
    }
}

The mechanism above is quite flexible and can be used in many situations time and time again, by simply providing implementation classes for the abstract PaginationBase and wiring them up in the Component Controller.

The pagination, ajax, or base models are quite versatile and can be used together or individually depending on the requirement to display simple embedded lists of DCPs, or AJAX lists, or paginated AJAX lists, or any combination thereof.

In a follow-up post Pagination (Views), I tackled the server-side view logic.


Comments

Popular posts from this blog

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as

Content Delivery Monitoring in AWS with CloudWatch

This post describes a way of monitoring a Tridion 9 combined Deployer by sending the health checks into a custom metric in CloudWatch in AWS. The same approach can also be used for other Content Delivery services. Once the metric is available in CloudWatch, we can create alarms in case the service errors out or becomes unresponsive. The overall architecture is as follows: Content Delivery service sends heartbeat (or exposes HTTP endpoint) for monitoring Monitoring Agent checks heartbeat (or HTTP health check) regularly and stores health state AWS lambda function: runs regularly reads the health state from Monitoring Agent pushes custom metrics into CloudWatch I am running the Deployer ( installation docs ) and Monitoring Agent ( installation docs ) on a t2.medium EC2 instance running CentOS on which I also installed the Systems Manager Agent (SSM Agent) ( installation docs ). In my case I have a combined Deployer that I want to monitor. This consists of an Endpoint and a

Debugging a Tridion 2011 Event System

OK, so you wrote your Tridion Event System. Now it's time to debug it. I know this is a hypothetical situtation -- your code never needs any kind of debugging ;) but indulge me... Recently, Alvin Reyes ( @nivlong ) blogged about being difficult to know how exactly to debug a Tridion Event System. More exactly, the question was " What process do I attach to for debugging even system code? ". Unfortunately, there is no simple or generic answer for it. Different events are fired by different Tridion CM modules. These modules run as different programs (or services) or run inside other programs (e.g. IIS). This means that you will need to monitor (or debug) different processes, based on which events your code handles. So the usual suspects are: dllhost.exe (or dllhost3g.exe ) - running as the MTSUser is the SDL Tridion Content Manager COM+ application and it fires events on generic TOM objects (e.g. events based on Tridion.ContentManager.Extensibility.Events.CrudEven