//
you're reading...

Uncategorized

Sitecore MVC – Controller Renderings

In this post I will look at how you can use the new Controller Renderings in Sitecore 6.6 MVC to create a commenting component.

To make working with MVC  easier I will be using Glass.Sitecore.Mapper to create the model.

Firstly the requirements:

  • It should be possible to add a comment component to any item
  • Comments will be stored beneath a comments folder that is a child of the page being commented on
  • Comments must be moderated first, i.e. saved to the master database where a content editor will approve and publish them
  • The content editor should have to create the comments folder.
  • Comments will capture the name, email and comment from the user

This means that if we dropped a comments component onto the Home item we would want to end up with the following structure in the master DB:

I am going to use the date and time the comment was submitted to save it to Sitecore.

First I need to model this structure, lets start with the Comment item, it has the following template in Sitecore:

The class to represent this template looks like this:

    /// <summary>
    /// Template: /sitecore/templates/Components/Comment/Comment
    /// </summary>
    [SitecoreClass(TemplateId = "{5281CBCF-1A2D-413A-B182-2854FC6B9176}")]
    public class Comment :AbstractBase
    {

        [SitecoreField]
        [Required(ErrorMessage = "You must enter your comment")]
        public virtual string Content { get; set; }

        [SitecoreField]
        [Required(ErrorMessage = "You must enter your email")]
        [RegularExpression(".+@.+", ErrorMessage="Your email was incorrect")]
        public virtual string Email { get; set; }

        [SitecoreField]
        [Required(ErrorMessage="You must enter your full name")]
        public virtual string FullName { get; set; } 

    }

Firstly it inherits from a abstract base class that looks like this:

    [SitecoreClass]
    public abstract class AbstractBase
    {
        [SitecoreId]
        public virtual Guid Id { get; set; }

        [SitecoreInfo(SitecoreInfoType.Name)]
        public virtual string Name { get; set; }
    }

Nothing to exciting there but this class is used several times later on.

Notice that the properties on the class not only use the Glass.Sitecore.Mapper attributes but I have also added the Data Annotation attributes that MVC uses for model validation. If not familiar with Data Annotations the following for example will ensure that the property has a value:

        [SitecoreField]
        [Required(ErrorMessage="You must enter your full name")]
        public virtual string FullName { get; set; }

Next I need to move up the item hierarchy and create a class to represent the Comments folder:

    /// <summary>
    ///  Template: /sitecore/templates/Common/Folder
    /// </summary>
    [SitecoreClass(TemplateId="{A87A00B1-E6DB-45AB-8B54-636FEC3B5523}")]
    public class CommentFolder : AbstractBase
    {
        [SitecoreChildren]
        public virtual IEnumerable<Comment> Comments { get; set; }
    }

This is quite a straight forward class, it simply loads the child comments. I am using the standard Folder template for the comments folder.

Finally we need to represent the page that the comments will be associated to:

    [SitecoreClass]
    public class CommentPage : AbstractBase
    {
        [SitecoreQuery("./*[@@name='Comments']", IsRelative = true)]
        public virtual CommentFolder CommentFolder { get; set; }

        /// <summary>
        /// Indicates that a comment was successfully added.
        /// </summary>
        public virtual bool CommentAdded { get; set; }
    }

Again this is a simple class that just looks for a Comments folder.

Ok so far we haven’t really touched the MVC part so lets start by setting up the home page to use MVC. I am assuming that the solution has already been setup for MVC (see John West’s blog). Also ensure that the Layout for the home item is a cshtml page.

Ok lets create the Controller Rendering and set the Controller and Action to call:

And assign it as a control to the home item:

Next we need to write the Controller. MVC uses convention over configuration so you need to ensure that the controller is created beneath the Controllers directory and the name ends with Controller:

Notice that the value entered for the controller in the Controller Rendering did not end with “Controller” but the name of your class must.

Your controller needs to inherit from the SitecoreController class:

    public class CommentController : SitecoreController
    {
        ISitecoreContext _context;
        ISitecoreService _master;

        public CommentController()
            : this(
            new SitecoreContext(),
            new SitecoreService("master"))
        {

        }
        /// <summary>
        /// This constructor can be used with dependency injection or unit testing
        /// </summary>
        public CommentController(ISitecoreContext context, ISitecoreService master)
        {
            _context = context;
            _master = master;
        }
    }

I have given the controller two constructors, one that can be used with IOC or unit testing and another that passes concrete classes to the first. The controller requires two services, the first gets data from the current Sitecore context and the second to write to the Sitecore master database.

The name of the Action we want to call is Index so need to implement a method called Index:

        [HttpGet]
        public override ActionResult Index()
        {
            var model = _context.GetCurrentItem<CommentPage>();
            return View(model);
        }

This method handles the get request, it simply loads the comments that already exist in Sitecore database. I have added the HttpGet attribute to the method to ensure that it is only called on a get request.

The more interesting method is the method that handles the form post:

        [HttpPost]
        public ActionResult Index(Comment comment)
        {
            var webModel = _context.GetCurrentItem<CommentPage<();

            if (ModelState.IsValid)
            {
                var masterModel = _master.GetItem<CommentPage<(webModel.Id);

                if (masterModel.CommentFolder == null)
                {
                    CommentFolder folder = new CommentFolder();
                    folder.Name = "Comments";

                    using (new SecurityDisabler())
                    {
                        _context.Create(masterModel, folder);
                    }
                    masterModel.CommentFolder = folder;
                }

                using (new SecurityDisabler())
                {
                    comment.Name = DateTime.Now.ToString("yyyyMMddhhmmss");

                    //create the comment in the master database
                    _master.Create(masterModel.CommentFolder, comment);
                    webModel.CommentAdded = true;
                }
            }

            return View(webModel);
        }

Lets break this method down a bit more. This method takes our Comment class we wrote earlier, MVC will automatically map the properties from our form onto this object.

We have to check that the item in the master database already contains a Comment folder beneath itself, if it doesn’t we create one:

                if (masterModel.CommentFolder == null)
                {
                    CommentFolder folder = new CommentFolder();
                    folder.Name = "Comments";

                    using (new SecurityDisabler())
                    {
                        _context.Create(masterModel, folder);
                    }
                    masterModel.CommentFolder = folder;
                }

After doing this check we can then add the comment to Sitecore:

                using (new SecurityDisabler())
                {
                    comment.Name = DateTime.Now.ToString("yyyyMMddhhmmss");

                    //create the comment in the master database
                    _master.Create(masterModel.CommentFolder, comment);
                    webModel.CommentAdded = true;
                }

Notice that we don’t have to pass any values to our Comment object except the name of the item, all the other values are automatically mapped by MVC. Finally we notify the page that the comment has been added. Overall not very much code to add two different items into Sitecore that includes validation.

Finally we need to create the view that renders form, again this needs to be in the correct location in you project with correct name, beneath Views/Comment with the name of the action (Index.cshtml):

The cshtml looks like this:

@inherits System.Web.Mvc.WebViewPage<Symposium.Models.CommentPage>
@using System.Web.Mvc.Html
<div>
    <h3>Comments</h3>
    @if (Model.CommentAdded)
    {
        <div>
            Your comment has been submitted and is awaiting approval
        </div>
    }

    <div>
        @Html.ValidationSummary("Comment Errors")
        <h4>Add Comment</h4>
        @using (Html.BeginForm())
        {
          <p>
            @Html.ValidationMessage("FullName")
            @Html.Label("FullName", "Your Name")
            @Html.TextBox("FullName")
          </p>
          <p>
            @Html.ValidationMessage("Email")
            @Html.Label("Email", "Email")
            @Html.TextBox("Email")
          </p>
          <p>
            @Html.ValidationMessage("Content")
            @Html.Label("Comment", "Your Comment")
            @Html.TextArea("Content")
          </p>

            <input type="submit" value="Add" name="addComment" />
        }
    </div>
    <div>
        @if (Model.CommentFolder != null && Model.CommentFolder.Comments.Any())
        {
            <ul>
                @foreach (var comment in Model.CommentFolder.Comments)
                {
                    <li>
                        From: <span class="Author">@comment.FullName</span>
                        <p>
                            @comment.Content
                        </p>
                    </li>
                }
            </ul>
        }
        else
        {
            <p>Be the first to comment</p>
        }

    </div>
</div>

With the view complete we just need to test the solution.

The basic comment form looks like this:

If we leave the field empty we should get form error messages:

If we fill out the form we should get a comment added to Sitecore in the master database:

If we reload the page with the comment component we should see it now appear in the list of comments:

That ends the first in a series of post about Sitecore MVC. In the next post I will look at getting Sitecore working with the Castle Windsor IOC container.

Discussion

No comments yet.

My Tweets

Stack Overflow

GitHub Projects