Views: 15595
Number of votes: 5
Average rating:

Creating a Custom EPiServer Community Module - Part Three

This article is number three in a series of articles on how to work with and customize the modules in the EPiServer Community framework. In the list below you can also access the rest of the articles, as well as the source code for the objects described.

Other articles in this series

» Creating a Custom EPiSever Community Module - Part One

» Creating a Custom EPiSever Community Module - Part Two

» Creating a Custom EPiSever Community Module - Part Four

Get the Source Code

» Download the full source code for this article

Creating the Entity Provider

Our entity provider, MovieEntity provider will be responsible for providing, you guessed it, Movie instances.

using System;
using System.Data.Common;
using EPiServer.Common.Data;

namespace ExampleProjects.Movie.Data
{
    public class MovieEntityProvider
    {
    }
}

After having created the class the first thing we must do is make it implement the IEntityProvider interface:

public class MovieEntityProvider : IEntityProvider

To satisfy the IEntityProvider interface we must implement two overloads of a method named GetEntityInstance. One that accepts a type and a integer ID as a parameter and one that accepts a DbDataReader instead of the ID. We must also create a method named GetProviderInstance that returns a singleton instance of the entity provider. Let’s begin with that:

private static readonly MovieEntityProvider _instance =
    new MovieEntityProvider();

public static IEntityProvider GetProviderInstance()
{
    return _instance;
}
   

The GetEntityInstance(Type type, int id) Method

With the GetProviderInstance method set up let’s move on to the first overload of the GetEntityInstance method which returns objects requested by ID:

public object GetEntityInstance(Type type, int id)
{
    ValidateIsSupportedType(type);

    return MovieHandler.GetMovie(id);
}

This method validates that the specified type is a type that the entity provider supports and throws an exception otherwise. If no exception is thrown it asks the MovieHandler for the Movie object with the specified ID. The validation of the type is performed by the helper method ValidateIsSupportedType which we should implement like this:

private void ValidateIsSupportedType(Type type)
{
    if (type == typeof(Movie))
        return;

    throw new NotSupportedException(
        string.Format(
            "Unsupported type. Cannot create instance of {0}.",
            type.Name));
}
   

The GetEntityInstance (Type type, DbDataReader reader) Method

This overload also validates that the entity provider supports the requested type. It then invokes the ConstructMovie helper method with the reader to construct a Movie object from the reader.

public object GetEntityInstance(
    Type type, DbDataReader reader)
{
    ValidateIsSupportedType(type);

    return ConstructMovie(reader);
}

The ConstructMovie method is simply a helper method that loads all of the necessary data from the reader and instantiates a new Movie object:

public Movie ConstructMovie(DbDataReader reader)
{
    int id = reader.GetInt32(0);
    string title = reader.GetString(1);
    int? runtime = null;
    if(!reader.IsDBNull(2))
        runtime = reader.GetInt32(2);
    int authorID = reader.GetInt32(3);

    return new Movie(id, title, runtime, authorID);
}
   

Adding Mappings to web.config

With the entity provider created we must let the framework know that it exists and that it provides Movie objects. This is done in the configuration/episerver.common/entity node in web.config in your web project:

<episerver.common>
  <entity>
    <providers>
      <add name="MovieEntityProvider"
        type="ExampleProjects.Movie.Data.MovieEntityProvider,
          ExampleProjects.Movie" />
    </providers>
    <supportedTypes>
      <add type="ExampleProjects.Movie.Movie, ExampleProjects.Movie"
        provider="MovieEntityProvider" />
    </supportedTypes>
  </entity>
</episerver.common>

Creating the Handler

The handler is basically the public interface of the modules persistence layer. It handles caching and fires events when entities are added, updated or removed. We begin by creating a class named MovieHandler:

using EPiServer.Common;
using EPiServer.Common.Cache;
using EPiServer.Common.Security;

namespace ExampleProjects.Movie
{
    public static class MovieHandler
    {

    }
}
   

Creating and Firing the Events

Our handler should have events for when a Movie object is added, updated or removed:

public static event EPiServerCommonEventHandler MovieAdded;
public static event EPiServerCommonEventHandler MovieUpdated;
public static event EPiServerCommonEventHandler MovieRemoved;

While it’s the handler that exposes these events to other modules, it’s actually the factory that knows when they should be fired. Therefore we should create methods in the handler that the factory can invoke when the events should be fired. These three methods, OnMovieAdded, OnMovieUpdated and OnMovieRemoved all look the same. They first check if the event has any listeners. If it does it creates a new EPiServerCommonEventArgs object and finally it fires the event:

internal static void OnMovieAdded(Movie movie)
{
    if (MovieAdded == null)
        return;

    EPiServerCommonEventArgs eventArgs =
        CreateEventArgs(movie, "movie_added");
    MovieAdded(MovieModule.Instance.UniqueName, eventArgs);
}

private static EPiServerCommonEventArgs
    CreateEventArgs(Movie movie, string eventName)
{
    return new EPiServerCommonEventArgs(
        movie.ID.ToString(), eventName,
        SiteHandler.CurrentSite, movie);
}

internal static void OnMovieUpdated(Movie movie)
{
    if(MovieUpdated == null)
        return;

    EPiServerCommonEventArgs eventArgs =
        CreateEventArgs(movie, "movie_updated");
    MovieUpdated(MovieModule.Instance.UniqueName, eventArgs);
}

internal static void OnMovieRemoved(Movie movie)
{
    if (MovieRemoved == null)
        return;

    EPiServerCommonEventArgs eventArgs =
        CreateEventArgs(movie, "movie_removed");
    MovieRemoved(MovieModule.Instance.UniqueName, eventArgs);
}

Finally, we need to modify the methods in the MovieFactory class to tell the handler to fire the events. In the case of the AddMovie and UpdateMovie methods the events should be fired after the movie has been added/updated:

internal static Movie AddMovie(Movie movie)
{
    . . .  
        UpdateEntity(movie, newId);

        Movie addedMovie = MovieHandler.GetMovie(newId);
        MovieHandler.OnMovieAdded(addedMovie);

        if (!wasInTransaction)
            DatabaseHandler.Commit();
    . . .
}
internal static void UpdateMovie(Movie movie)
{
    . . .
        UpdateEntity(movie);
       
        MovieHandler.OnMovieUpdated(movie);

        if (!wasInTransaction)
            DatabaseHandler.Commit();
    . . .
}

Note that the modification we made to the AddMovie method above uses a method named GetMovie in the MovieHandler which doesn’t exist. We’ll create that method in just a bit.

In the case of the RemoveMovie method the event should be fired before removing the movie:

internal static void RemoveMovie(Movie movie)
{
    . . .

    try
    {
        MovieHandler.OnMovieRemoved(movie);

        DatabaseHandler.ExecuteNonQuery("spRemoveMovie", movie.ID);
       
     . . .
}
   

The GetMovie Method – Or “Dude, What Are You Doing with the Cache!?”

The GetMovie method in the handler class should accept an integer ID of a movie, check if a movie with that ID exists in the cache and if it doesn’t it should invoke the GetMovie method in the MovieFactory class, put the result in the cache and then return it.

In order to create the method we’ll actually begin by creating a delegate:

internal delegate Movie GetMovieDelegate(int id);

We then create a static member variable of the delegate type with the GetMovie method in the MovieFactory as value:

internal static GetMovieDelegate
    _getMovieDelegate = Data.MovieFactory.GetMovie;

We finally implement the GetMovie method like this:

public static Movie GetMovie(int id)
{
    if (!EntityValidator.IsValidId(id))
        return null;

    object[] getterArgs = new object[] { id };
    string[] cacheKey = Movie.ConstructCacheKey(id);
    return (Movie)CacheHandler.GetCachedObject(
        _getMovieDelegate, getterArgs, cacheKey);
}

So, what did we just do? Well, first of all the GetMovie method checks if the specified ID is a valid ID. It then creates an object array which will be used as the parameter(s) for the GetMovieDelegate (MovieFactory.GetMovie). Then it proceeds to creating a string array which is the cache key for the movie. Finally it invokes and returns the result of the CacheHandlers GetCachedObject method with the GetMovieDelegate, the arguments for it and the cachekey as arguments.

To explain why we are using a delegate like this, let’s take a look at what the GetCachedObject method does:

  1. It checks if there is an object in the cache with the specified cache key. If so, it returns that object.
  2. If no object existed in the cache it will check if the specified delegate is already being invoked with the specified parameters. If it is not, it will invoke the delegate (invoke the MovieFactory.GetMovie with the ID parameter in our specific case), put the result in the cache with the specified key and finally return the result.
  3. If the delegate is currently being invoked with the specified parameters the GetCachedObject method will wait until it no longer is being invoked and then again check if the object with the specified key exists in the cache. If it doesn’t, the delegate will be invoked, and the result will be cached and returned.

The interesting part here is #2 and #3 above. See, if our factory method is currently executing with the exact same set of parameter there is no point in executing it again which would only cause extra database calls. So, therefore the GetCachedObject method ensures that only one invokation with the same parameters will happen at a time. 

The AddMovie, UpdateMovie and RemoveMovie Methods

With the events and the GetMovie methods done we are almost finished with the handler as the AddMovie, UpdateMovie and RemoveMovie methods are very simple.

The AddMovie method validates that the Movie object that we are trying to add is not null, invokes the AddMovie method in the MovieFactory class and returns the result:

public static Movie AddMovie(Movie movie)
{
    EntityValidator.ValidateIsNotNull(movie, "movie");

    Movie addedMovie = Data.MovieFactory.AddMovie(movie);

    return addedMovie;
}

The UpdateMovie and RemoveMethods are similar. We first validate that the movie that we are going to update or remove is a persisted movie (it has a ID greater than or equal to zero) and then update or remove it by invoking the UpdateMovie or RemoveMovie methods in the MovieFactory:

public static void UpdateMovie(Movie movie)
{
    EntityValidator.ValidateIsCommittedEntity(movie, "movie");
   
    Data.MovieFactory.UpdateMovie(movie);
}

public static void RemoveMovie(Movie movie)
{
    EntityValidator.ValidateIsCommittedEntity(movie, "movie");
   
    Data.MovieFactory.RemoveMovie(movie);
}
   
More will follow in part four of this article series.

Comments

Brian Burge
Brian Burge Dec 17, 2013 07:52 PM





type="ExampleProjects.Movie.Data.MovieEntityProvider,
ExampleProjects.Movie" />



provider="MovieEntityProvider" />


This throws a 500 error for me. can you please post your full web.config so we can see how we are suspose to configure it?

Please login to comment.