Refactoring ModelDriver to favor composition over inheritance. Introduces idea of IModelFilter.

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4041298
This commit is contained in:
loudej
2009-11-18 22:19:54 +00:00
parent d78f9a9b6f
commit dc9e302bae
26 changed files with 293 additions and 124 deletions

View File

@@ -0,0 +1,48 @@
using NUnit.Framework;
using Orchard.Models;
using Orchard.Models.Driver;
namespace Orchard.Tests.Models.Drivers {
[TestFixture]
public class ModelDriverTests {
[Test]
public void ModelDriverShouldUsePersistenceFilterToDelegateCreateAndLoad() {
var modelDriver = new TestModelDriver();
var part = new TestModelPart();
((IModelDriver)modelDriver).Creating(new CreateModelContext { Instance = part });
Assert.That(part.CreatingCalled, Is.True);
}
[Test]
public void PartShouldBeAddedBasedOnSimplePredicate() {
var modelDriver = new TestModelDriver();
var builder = new ModelBuilder("testing");
((IModelDriver)modelDriver).Activating(new ActivatingModelContext { Builder = builder, ModelType = "testing" });
var model = builder.Build();
Assert.That(model.Is<TestModelPart>(), Is.True);
Assert.That(model.As<TestModelPart>(), Is.Not.Null);
}
public class TestModelPart : ModelPart {
public bool CreatingCalled { get; set; }
}
public class TestModelDriver : ModelDriver {
public TestModelDriver() {
Filters.Add(new ActivatingFilter<TestModelPart>(x => x == "testing"));
Filters.Add(new TestModelStorageFilter());
}
}
public class TestModelStorageFilter : StorageFilterBase<TestModelPart> {
protected override void Creating(CreateModelContext context, TestModelPart instance) {
instance.CreatingCalled = true;
}
}
}
}

View File

@@ -2,7 +2,7 @@
namespace Orchard.Tests.Models.Stubs {
public class AlphaDriver : ModelDriver {
protected override void New(NewModelContext context) {
protected override void Activating(ActivatingModelContext context) {
if (context.ModelType == "alpha") {
context.Builder.Weld<Alpha>();
}

View File

@@ -6,7 +6,7 @@ using Orchard.Models.Driver;
namespace Orchard.Tests.Models.Stubs {
public class BetaDriver : ModelDriver {
protected override void New(NewModelContext context) {
protected override void Activating(ActivatingModelContext context) {
if (context.ModelType == "beta") {
context.Builder.Weld<Beta>();
}

View File

@@ -2,7 +2,7 @@
namespace Orchard.Tests.Models.Stubs {
public class FlavoredDriver : ModelDriver {
protected override void New(NewModelContext context) {
protected override void Activating(ActivatingModelContext context) {
if (context.ModelType == "beta" || context.ModelType == "alpha") {
context.Builder.Weld<Flavored>();
}

View File

@@ -1,7 +1,4 @@
using System;
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
using Orchard.Data;
using Orchard.Data;
using Orchard.Models;
using Orchard.Models.Driver;
using Orchard.Models.Records;
@@ -15,15 +12,10 @@ namespace Orchard.Tests.Models.Stubs {
}
public class GammaDriver : ModelDriverWithRecord<GammaRecord> {
public GammaDriver(IRepository<GammaRecord> repository)
: base(repository) {
}
protected override void New(NewModelContext context) {
if (context.ModelType == "gamma") {
context.Builder.Weld<Gamma>();
}
public class GammaDriver : ModelDriver {
public GammaDriver(IRepository<GammaRecord> repository){
Filters.Add(new ActivatingFilter<Gamma>(x => x == "gamma"));
Filters.Add(new StorageFilterForRecord<GammaRecord>(repository));
}
}
}

View File

@@ -2,7 +2,7 @@
namespace Orchard.Tests.Models.Stubs {
public class StyledDriver : ModelDriver {
protected override void New(NewModelContext context) {
protected override void Activating(ActivatingModelContext context) {
if (context.ModelType == "alpha") {
context.Builder.Weld<Styled>();
}

View File

@@ -107,6 +107,7 @@
<Compile Include="Logging\LoggingModuleTests.cs" />
<Compile Include="Models\DefaultModelManagerTests.cs" />
<Compile Include="Models\Drivers\ModelBuilderTests.cs" />
<Compile Include="Models\Drivers\ModelDriverTests.cs" />
<Compile Include="Models\Stubs\Alpha.cs" />
<Compile Include="Models\Stubs\AlphaDriver.cs" />
<Compile Include="Models\Stubs\Beta.cs" />

View File

@@ -6,7 +6,7 @@ using Orchard.Security;
using Orchard.Services;
namespace Orchard.Core.Common.Models {
public class CommonDriver : ModelDriverWithRecord<CommonRecord> {
public class CommonDriver : ModelDriver {
private readonly IClock _clock;
private readonly IAuthenticationService _authenticationService;
private readonly IModelManager _modelManager;
@@ -15,40 +15,35 @@ namespace Orchard.Core.Common.Models {
IRepository<CommonRecord> repository,
IClock clock,
IAuthenticationService authenticationService,
IModelManager modelManager)
: base(repository) {
IModelManager modelManager) {
_clock = clock;
_authenticationService = authenticationService;
_modelManager = modelManager;
AddOnCreating<CommonModel>(SetCreateTimesAndAuthor);
Filters.Add(new StorageFilterForRecord<CommonRecord>(repository));
AddOnLoaded<CommonModel>(LoadOwnerModel);
}
protected override void Create(CreateModelContext context) {
var instance = context.Instance.As<CommonModel>();
if (instance != null && instance.Record != null) {
if (instance.Record.CreatedUtc == null) {
instance.Record.CreatedUtc = _clock.UtcNow;
}
if (instance.Record.ModifiedUtc == null) {
instance.Record.ModifiedUtc = _clock.UtcNow;
}
if (instance.Record.OwnerId == 0) {
instance.Owner = _authenticationService.GetAuthenticatedUser();
if (instance.Owner != null)
instance.Record.OwnerId = instance.Owner.Id;
}
void SetCreateTimesAndAuthor(CreateModelContext context, CommonModel instance) {
if (instance.Record.CreatedUtc == null) {
instance.Record.CreatedUtc = _clock.UtcNow;
}
if (instance.Record.ModifiedUtc == null) {
instance.Record.ModifiedUtc = _clock.UtcNow;
}
if (instance.Record.OwnerId == 0) {
instance.Owner = _authenticationService.GetAuthenticatedUser();
if (instance.Owner != null)
instance.Record.OwnerId = instance.Owner.Id;
}
base.Create(context);
}
protected override void Loaded(LoadModelContext context) {
var instance = context.Instance.As<CommonModel>();
if (instance != null && instance.Record != null) {
if (instance.Record.OwnerId != 0) {
instance.Owner = _modelManager.Get(instance.Record.OwnerId).As<IUser>();
}
void LoadOwnerModel(LoadModelContext context, CommonModel instance) {
if (instance.Record.OwnerId != 0) {
instance.Owner = _modelManager.Get(instance.Record.OwnerId).As<IUser>();
}
base.Loaded(context);
}
}
}

View File

@@ -3,8 +3,9 @@ using Orchard.Data;
using Orchard.Models.Driver;
namespace Orchard.Core.Common.Models {
public class RoutableDriver : ModelDriverWithRecord<RoutableRecord> {
public RoutableDriver(IRepository<RoutableRecord> repository) : base(repository) {
public class RoutableDriver : ModelDriver {
public RoutableDriver(IRepository<RoutableRecord> repository) {
Filters.Add(new StorageFilterForRecord<RoutableRecord>(repository));
}
}
}

View File

@@ -3,15 +3,10 @@ using Orchard.Data;
using Orchard.Models.Driver;
namespace Orchard.Core.Settings.Models {
public class SiteDriver : ModelDriverWithRecord<SiteSettingsRecord> {
public SiteDriver(IRepository<SiteSettingsRecord> repository)
: base(repository) {
}
protected override void New(NewModelContext context) {
if (context.ModelType == "site") {
context.Builder.Weld<SiteModel>();
}
public class SiteDriver : ModelDriver {
public SiteDriver(IRepository<SiteSettingsRecord> repository){
Filters.Add(new ActivatingFilter<SiteModel>("site"));
Filters.Add(new StorageFilterForRecord<SiteSettingsRecord>(repository));
}
}
}

View File

@@ -21,19 +21,19 @@ namespace Orchard.Roles.Models {
_notifier = notifier;
}
protected override void New(NewModelContext context) {
protected override void Activating(ActivatingModelContext context) {
if (context.ModelType == "user") {
context.Builder.Weld<UserRolesModel>();
}
}
protected override void Create(CreateModelContext context) {
protected override void Creating(CreateModelContext context) {
var userRoles = context.Instance.As<UserRolesModel>();
if (userRoles != null) {
}
}
protected override void Load(LoadModelContext context) {
protected override void Loading(LoadModelContext context) {
var userRoles = context.Instance.As<UserRolesModel>();
if (userRoles != null) {
userRoles.Roles = _userRolesRepository.Fetch(x => x.UserId == userRoles.Id)

View File

@@ -2,15 +2,10 @@
using Orchard.Models.Driver;
namespace Orchard.Users.Models {
public class UserDriver : ModelDriverWithRecord<UserRecord> {
public UserDriver(IRepository<UserRecord> repository)
: base(repository) {
}
protected override void New(NewModelContext context) {
if (context.ModelType == "user") {
context.Builder.Weld<UserModel>();
}
public class UserDriver : ModelDriver {
public UserDriver(IRepository<UserRecord> repository) {
Filters.Add(new ActivatingFilter<UserModel>("user"));
Filters.Add(new StorageFilterForRecord<UserRecord>(repository));
}
}
}

View File

@@ -7,7 +7,7 @@ using Orchard.Models.Driver;
namespace Orchard.Wikis.Models {
public class WikiPageDriver : ModelDriver {
protected override void New(NewModelContext context) {
protected override void Activating(ActivatingModelContext context) {
if (context.ModelType == "wikipage") {
context.Builder
.Weld<CommonModel>()

View File

@@ -34,21 +34,21 @@ namespace Orchard.Models {
public virtual IModel New(string modelType) {
// create a new kernel for the model instance
var context = new NewModelContext {
var context = new ActivatingModelContext {
ModelType = modelType,
Builder = new ModelBuilder(modelType)
};
// invoke drivers to weld aspects onto kernel
foreach (var driver in Drivers) {
driver.New(context);
driver.Activating(context);
}
var context2 = new NewedModelContext {
var context2 = new ActivatedModelContext {
ModelType = modelType,
Instance = context.Builder.Build()
};
foreach (var driver in Drivers) {
driver.Newed(context2);
driver.Activated(context2);
}
// composite result is returned
@@ -72,7 +72,7 @@ namespace Orchard.Models {
// invoke drivers to acquire state, or at least establish lazy loading callbacks
foreach (var driver in Drivers) {
driver.Load(context);
driver.Loading(context);
}
foreach (var driver in Drivers) {
driver.Loaded(context);
@@ -100,7 +100,7 @@ namespace Orchard.Models {
// invoke drivers to add information to persistent stores
foreach (var driver in Drivers) {
driver.Create(context);
driver.Creating(context);
}
foreach (var driver in Drivers) {
driver.Created(context);

View File

@@ -0,0 +1,6 @@
namespace Orchard.Models.Driver {
public class ActivatedModelContext {
public string ModelType { get; set; }
public IModel Instance { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Linq;
namespace Orchard.Models.Driver {
public class ActivatingFilter<TPart> : IModelActivatingFilter where TPart : class, IModel, new() {
private readonly Func<string, bool> _predicate;
public ActivatingFilter(Func<string, bool> predicate) {
_predicate = predicate;
}
public ActivatingFilter(params string[] modelTypes)
: this(modelType => modelTypes.Contains(modelType)) {
}
public void Activating(ActivatingModelContext context) {
if (_predicate(context.ModelType))
context.Builder.Weld<TPart>();
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Orchard.Models.Driver {
public class ActivatingModelContext {
public string ModelType { get; set; }
public ModelBuilder Builder { get; set; }
}
public class ActivatedModelContext {
public string ModelType { get; set; }
public IModel Instance { get; set; }
}
}

View File

@@ -0,0 +1,5 @@
namespace Orchard.Models.Driver {
public interface IModelActivatingFilter : IModelFilter {
void Activating(ActivatingModelContext context);
}
}

View File

@@ -1,10 +1,10 @@
namespace Orchard.Models.Driver {
public interface IModelDriver : IDependency {
void New(NewModelContext context);
void Newed(NewedModelContext context);
void Create(CreateModelContext context);
void Activating(ActivatingModelContext context);
void Activated(ActivatedModelContext context);
void Creating(CreateModelContext context);
void Created(CreateModelContext context);
void Load(LoadModelContext context);
void Loading(LoadModelContext context);
void Loaded(LoadModelContext context);
void GetEditors(GetModelEditorsContext context);

View File

@@ -0,0 +1,4 @@
namespace Orchard.Models.Driver {
public interface IModelFilter {
}
}

View File

@@ -0,0 +1,9 @@
namespace Orchard.Models.Driver {
public interface IModelStorageFilter : IModelFilter {
void Activated(ActivatedModelContext context);
void Creating(CreateModelContext context);
void Created(CreateModelContext context);
void Loading(LoadModelContext context);
void Loaded(LoadModelContext context);
}
}

View File

@@ -1,30 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Logging;
namespace Orchard.Models.Driver {
public abstract class ModelDriver : IModelDriver {
protected ModelDriver() {
Filters = new List<IModelFilter>();
Logger = NullLogger.Instance;
}
public List<IModelFilter> Filters { get; set; }
public ILogger Logger { get; set; }
void IModelDriver.New(NewModelContext context) { New(context); }
void IModelDriver.Newed(NewedModelContext context) { Newed(context); }
void IModelDriver.Create(CreateModelContext context) { Create(context); }
void IModelDriver.Created(CreateModelContext context) { Created(context); }
void IModelDriver.Load(LoadModelContext context) { Load(context); }
void IModelDriver.Loaded(LoadModelContext context) { Loaded(context); }
public void AddOnActivated<TPart>(Action<ActivatedModelContext, TPart> handler) where TPart : class, IModel {
Filters.Add(new InlineStorageFilter<TPart> { OnActivated = handler });
}
public void AddOnCreating<TPart>(Action<CreateModelContext, TPart> handler) where TPart : class, IModel {
Filters.Add(new InlineStorageFilter<TPart> { OnCreating = handler });
}
public void AddOnLoaded<TPart>(Action<LoadModelContext, TPart> handler) where TPart : class, IModel {
Filters.Add(new InlineStorageFilter<TPart> { OnLoaded = handler });
}
class InlineStorageFilter<TPart> : StorageFilterBase<TPart> where TPart : class, IModel {
public Action<ActivatedModelContext, TPart> OnActivated { get; set; }
public Action<CreateModelContext, TPart> OnCreating { get; set; }
public Action<CreateModelContext, TPart> OnCreated { get; set; }
public Action<LoadModelContext, TPart> OnLoading { get; set; }
public Action<LoadModelContext, TPart> OnLoaded { get; set; }
protected override void Activated(ActivatedModelContext context, TPart instance) { if (OnActivated != null) OnActivated(context, instance); }
protected override void Creating(CreateModelContext context, TPart instance) { if (OnCreating != null) OnCreating(context, instance); }
protected override void Loaded(LoadModelContext context, TPart instance) { if (OnLoaded != null) OnLoaded(context, instance); }
}
void IModelDriver.Activating(ActivatingModelContext context) {
foreach (var filter in Filters.OfType<IModelActivatingFilter>())
filter.Activating(context);
Activating(context);
}
void IModelDriver.Activated(ActivatedModelContext context) {
foreach (var filter in Filters.OfType<IModelStorageFilter>())
filter.Activated(context);
Activated(context);
}
void IModelDriver.Creating(CreateModelContext context) {
foreach (var filter in Filters.OfType<IModelStorageFilter>())
filter.Creating(context);
Creating(context);
}
void IModelDriver.Created(CreateModelContext context) {
foreach (var filter in Filters.OfType<IModelStorageFilter>())
filter.Created(context);
Created(context);
}
void IModelDriver.Loading(LoadModelContext context) {
foreach (var filter in Filters.OfType<IModelStorageFilter>())
filter.Loading(context);
Loading(context);
}
void IModelDriver.Loaded(LoadModelContext context) {
foreach (var filter in Filters.OfType<IModelStorageFilter>())
filter.Loaded(context);
Loaded(context);
}
void IModelDriver.GetEditors(GetModelEditorsContext context) { GetEditors(context); }
void IModelDriver.UpdateEditors(UpdateModelContext context) { UpdateEditors(context); }
protected virtual void New(NewModelContext context) { }
protected virtual void Newed(NewedModelContext context) { }
protected virtual void Activating(ActivatingModelContext context) { }
protected virtual void Activated(ActivatedModelContext context) { }
protected virtual void Load(LoadModelContext context) { }
protected virtual void Loading(LoadModelContext context) { }
protected virtual void Loaded(LoadModelContext context) { }
protected virtual void Create(CreateModelContext context) { }
protected virtual void Creating(CreateModelContext context) { }
protected virtual void Created(CreateModelContext context) { }
protected virtual void GetEditors(GetModelEditorsContext context) {}

View File

@@ -1,35 +0,0 @@
using Orchard.Data;
using Orchard.Models.Records;
namespace Orchard.Models.Driver {
public abstract class ModelDriverWithRecord<TRecord> : ModelDriver where TRecord : ModelPartRecord, new() {
private readonly IRepository<TRecord> _repository;
protected ModelDriverWithRecord(IRepository<TRecord> repository) {
_repository = repository;
}
protected override void Newed(NewedModelContext context) {
var instance = context.Instance.As<ModelPartWithRecord<TRecord>>();
if (instance != null && instance.Record == null) {
instance.Record = new TRecord();
}
}
protected override void Create(CreateModelContext context) {
var instance = context.Instance.As<ModelPartWithRecord<TRecord>>();
if (instance != null && instance.Record != null) {
instance.Record.Model = context.Record;
_repository.Create(instance.Record);
}
}
protected override void Load(LoadModelContext context) {
var instance = context.Instance.As<ModelPartWithRecord<TRecord>>();
if (instance != null) {
instance.Record = _repository.Get(context.Id);
}
}
}
}

View File

@@ -0,0 +1,36 @@
namespace Orchard.Models.Driver {
public abstract class StorageFilterBase<TPart> : IModelStorageFilter where TPart : class, IModel {
protected virtual void Activated(ActivatedModelContext context, TPart instance) { }
protected virtual void Creating(CreateModelContext context, TPart instance) { }
protected virtual void Created(CreateModelContext context, TPart instance) { }
protected virtual void Loading(LoadModelContext context, TPart instance) { }
protected virtual void Loaded(LoadModelContext context, TPart instance) { }
void IModelStorageFilter.Activated(ActivatedModelContext context) {
if (context.Instance.Is<TPart>())
Activated(context, context.Instance.As<TPart>());
}
void IModelStorageFilter.Creating(CreateModelContext context) {
if (context.Instance.Is<TPart>())
Creating(context, context.Instance.As<TPart>());
}
void IModelStorageFilter.Created(CreateModelContext context) {
if (context.Instance.Is<TPart>())
Created(context, context.Instance.As<TPart>());
}
void IModelStorageFilter.Loading(LoadModelContext context) {
if (context.Instance.Is<TPart>())
Loading(context, context.Instance.As<TPart>());
}
void IModelStorageFilter.Loaded(LoadModelContext context) {
if (context.Instance.Is<TPart>())
Loaded(context, context.Instance.As<TPart>());
}
}
}

View File

@@ -0,0 +1,25 @@
using Orchard.Data;
using Orchard.Models.Records;
namespace Orchard.Models.Driver {
public class StorageFilterForRecord<TRecord> : StorageFilterBase<ModelPartWithRecord<TRecord>> where TRecord : ModelPartRecord,new() {
private readonly IRepository<TRecord> _repository;
public StorageFilterForRecord(IRepository<TRecord> repository) {
_repository = repository;
}
protected override void Activated(ActivatedModelContext context, ModelPartWithRecord<TRecord> instance) {
instance.Record = new TRecord();
}
protected override void Creating(CreateModelContext context, ModelPartWithRecord<TRecord> instance) {
instance.Record.Model = context.Record;
_repository.Create(instance.Record);
}
protected override void Loading(LoadModelContext context, ModelPartWithRecord<TRecord> instance) {
instance.Record = _repository.Get(instance.Id);
}
}
}

View File

@@ -124,6 +124,10 @@
<Compile Include="Localization\Localizer.cs" />
<Compile Include="Localization\LocalizedString.cs" />
<Compile Include="Localization\NullLocalizer.cs" />
<Compile Include="Models\Driver\ActivatingFilter.cs" />
<Compile Include="Models\Driver\IModelActivatingFilter.cs" />
<Compile Include="Models\Driver\IModelFilter.cs" />
<Compile Include="Models\Driver\IModelStorageFilter.cs" />
<Compile Include="Models\Driver\IModelUpdater.cs" />
<Compile Include="Environment\ServiceLocator.cs" />
<Compile Include="Logging\CastleLogger.cs" />
@@ -139,9 +143,10 @@
<Compile Include="Models\Driver\LoadModelContext.cs" />
<Compile Include="Models\Driver\ModelBuilder.cs" />
<Compile Include="Models\Driver\ModelDriver.cs" />
<Compile Include="Models\Driver\ModelDriverWithRecord.cs" />
<Compile Include="Models\Driver\NewModelContext.cs" />
<Compile Include="Models\Driver\ActivatingModelContext.cs" />
<Compile Include="Models\Driver\GetModelEditorsContext.cs" />
<Compile Include="Models\Driver\StorageFilterForRecord.cs" />
<Compile Include="Models\Driver\StorageFilterBase.cs" />
<Compile Include="Models\IModelManager.cs" />
<Compile Include="Models\Driver\IModelDriver.cs" />
<Compile Include="Models\IModel.cs" />