Views: | 15595 |
Number of votes: | 5 |
Average rating: |
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.
» Creating a Custom EPiSever Community Module - Part One
» Creating a Custom EPiSever Community Module - Part Two
» Creating a Custom EPiSever Community Module - Part Four
» Download the full source code for this article
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;
}
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));
}
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);
}
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>
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
{
}
}
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 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:
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.
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);
}
type="ExampleProjects.Movie.Data.MovieEntityProvider,
ExampleProjects.Movie" />
provider="MovieEntityProvider" />