Slugs in pages module no longer case sensitive. Introduces concept of transient and singleton dependencies. Singleton dependencies really mean single instance per shell.

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4042638
This commit is contained in:
loudej
2009-11-29 09:08:25 +00:00
parent 940e2bce07
commit 9852c56cb2
13 changed files with 290 additions and 30 deletions

View File

@@ -90,6 +90,7 @@
<Compile Include="Pages\Controllers\TemplatesControllerTests.cs" />
<Compile Include="Pages\Services\PageManagerTests.cs" />
<Compile Include="Pages\Services\PageSchedulerTests.cs" />
<Compile Include="Pages\Services\SlugConstraintTests.cs" />
<Compile Include="Pages\Services\Templates\CommentExtractorTests.cs" />
<Compile Include="Pages\Services\Templates\TemplateMetadataParserTests.cs" />
<Compile Include="Pages\Services\Templates\TemplateProviderTests.cs" />

View File

@@ -27,6 +27,8 @@ namespace Orchard.Tests.Packages.Pages.Controllers {
var revision = _pageManager.CreatePage(new CreatePageParams(null, "slug", null));
_pageManager.Publish(revision, new PublishOptions());
_container.Resolve<ISlugConstraint>().SetCurrentlyPublishedSlugs(_pageManager.GetCurrentlyPublishedSlugs());
}
public override void Register(ContainerBuilder builder) {
@@ -35,6 +37,7 @@ namespace Orchard.Tests.Packages.Pages.Controllers {
builder.Register<TemplateProvider>().As<ITemplateProvider>();
builder.Register<TemplateMetadataParser>().As<ITemplateMetadataParser>();
builder.Register(new StubTemplateEntryProvider()).As<ITemplateEntryProvider>();
builder.Register<SlugConstraint>().As<ISlugConstraint>();
}
protected override IEnumerable<Type> DatabaseTypes {
@@ -69,5 +72,13 @@ namespace Orchard.Tests.Packages.Pages.Controllers {
var page = (PageRevision) (((ViewResult) result).ViewData.Model);
Assert.That(page.Slug, Is.EqualTo("slug"));
}
[Test]
public void TheWrongCaseShouldStillWork() {
var result = _controller.Show("sLUg");
var page = (PageRevision)(((ViewResult)result).ViewData.Model);
Assert.That(page.Slug, Is.EqualTo("slug"));
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Routing;
using NUnit.Framework;
using Orchard.CmsPages.Services;
namespace Orchard.Tests.Packages.Pages.Services {
[TestFixture]
public class SlugConstraintTests {
[Test]
public void MatchShouldBeTrueWhenSlugIsInSet() {
var slugConstraint = new SlugConstraint();
var before = slugConstraint.Match(null, null, "slug", new RouteValueDictionary {{"slug", "foo"}},
RouteDirection.IncomingRequest);
Assert.That(before, Is.False);
slugConstraint.SetCurrentlyPublishedSlugs(new[]{"foo"});
var after = slugConstraint.Match(null, null, "slug", new RouteValueDictionary { { "slug", "foo" } },
RouteDirection.IncomingRequest);
Assert.That(after, Is.True);
}
[Test]
public void MatchShouldIgnoreCase() {
var slugConstraint = new SlugConstraint();
slugConstraint.SetCurrentlyPublishedSlugs(new[]{"foo", "bAr"});
var foo = slugConstraint.Match(null, null, "slug", new RouteValueDictionary { { "slug", "FOO" } },
RouteDirection.IncomingRequest);
Assert.That(foo, Is.True);
var bar = slugConstraint.Match(null, null, "slug", new RouteValueDictionary { { "slug", "bar" } },
RouteDirection.IncomingRequest);
Assert.That(bar, Is.True);
}
[Test]
public void CollisionsShouldNotThrowExceptions() {
var slugConstraint = new SlugConstraint();
slugConstraint.SetCurrentlyPublishedSlugs(new[] { "foo", "FOO" });
}
}
}

View File

@@ -16,6 +16,7 @@ using Orchard.Mvc;
using Orchard.Mvc.ModelBinders;
using Orchard.Mvc.Routes;
using Orchard.Packages;
using Orchard.Tests.Environment.TestDependencies;
using Orchard.Tests.Mvc.Routes;
using Orchard.Tests.Stubs;
@@ -44,7 +45,7 @@ namespace Orchard.Tests.Environment {
builder.Register(_controllerBuilder);
builder.Register(_routeCollection);
builder.Register(_modelBinderDictionary);
builder.Register(new ViewEngineCollection{new WebFormViewEngine()});
builder.Register(new ViewEngineCollection { new WebFormViewEngine() });
builder.Register(new StuPackageManager()).As<IPackageManager>();
});
}
@@ -78,7 +79,11 @@ namespace Orchard.Tests.Environment {
}
public IEnumerable<Type> GetDependencyTypes() {
return Enumerable.Empty<Type>();
return new[] {
typeof (TestDependency),
typeof (TestSingletonDependency),
typeof(TestTransientDependency)
};
}
public IEnumerable<Type> GetRecordTypes() {
@@ -93,5 +98,105 @@ namespace Orchard.Tests.Environment {
var runtime2 = host.CreateShell();
Assert.That(runtime1, Is.Not.SameAs(runtime2));
}
[Test]
public void NormalDependenciesShouldBeUniquePerRequestContainer() {
var host = (DefaultOrchardHost)_container.Resolve<IOrchardHost>();
var container1 = host.CreateShellContainer();
var container2 = host.CreateShellContainer();
var requestContainer1a = container1.CreateInnerContainer();
var requestContainer1b = container1.CreateInnerContainer();
var requestContainer2a = container2.CreateInnerContainer();
var requestContainer2b = container2.CreateInnerContainer();
var dep1 = container1.Resolve<ITestDependency>();
var dep1a = requestContainer1a.Resolve<ITestDependency>();
var dep1b = requestContainer1b.Resolve<ITestDependency>();
var dep2 = container2.Resolve<ITestDependency>();
var dep2a = requestContainer2a.Resolve<ITestDependency>();
var dep2b = requestContainer2b.Resolve<ITestDependency>();
Assert.That(dep1, Is.Not.SameAs(dep2));
Assert.That(dep1, Is.Not.SameAs(dep1a));
Assert.That(dep1, Is.Not.SameAs(dep1b));
Assert.That(dep2, Is.Not.SameAs(dep2a));
Assert.That(dep2, Is.Not.SameAs(dep2b));
var again1 = container1.Resolve<ITestDependency>();
var again1a = requestContainer1a.Resolve<ITestDependency>();
var again1b = requestContainer1b.Resolve<ITestDependency>();
var again2 = container2.Resolve<ITestDependency>();
var again2a = requestContainer2a.Resolve<ITestDependency>();
var again2b = requestContainer2b.Resolve<ITestDependency>();
Assert.That(again1, Is.SameAs(dep1));
Assert.That(again1a, Is.SameAs(dep1a));
Assert.That(again1b, Is.SameAs(dep1b));
Assert.That(again2, Is.SameAs(dep2));
Assert.That(again2a, Is.SameAs(dep2a));
Assert.That(again2b, Is.SameAs(dep2b));
}
[Test]
public void SingletonDependenciesShouldBeUniquePerShell() {
var host = (DefaultOrchardHost)_container.Resolve<IOrchardHost>();
var container1 = host.CreateShellContainer();
var container2 = host.CreateShellContainer();
var requestContainer1a = container1.CreateInnerContainer();
var requestContainer1b = container1.CreateInnerContainer();
var requestContainer2a = container2.CreateInnerContainer();
var requestContainer2b = container2.CreateInnerContainer();
var dep1 = container1.Resolve<ITestSingletonDependency>();
var dep1a = requestContainer1a.Resolve<ITestSingletonDependency>();
var dep1b = requestContainer1b.Resolve<ITestSingletonDependency>();
var dep2 = container2.Resolve<ITestSingletonDependency>();
var dep2a = requestContainer2a.Resolve<ITestSingletonDependency>();
var dep2b = requestContainer2b.Resolve<ITestSingletonDependency>();
Assert.That(dep1, Is.Not.SameAs(dep2));
Assert.That(dep1, Is.SameAs(dep1a));
Assert.That(dep1, Is.SameAs(dep1b));
Assert.That(dep2, Is.SameAs(dep2a));
Assert.That(dep2, Is.SameAs(dep2b));
}
[Test]
public void TransientDependenciesShouldBeUniquePerResolve() {
var host = (DefaultOrchardHost)_container.Resolve<IOrchardHost>();
var container1 = host.CreateShellContainer();
var container2 = host.CreateShellContainer();
var requestContainer1a = container1.CreateInnerContainer();
var requestContainer1b = container1.CreateInnerContainer();
var requestContainer2a = container2.CreateInnerContainer();
var requestContainer2b = container2.CreateInnerContainer();
var dep1 = container1.Resolve<ITestTransientDependency>();
var dep1a = requestContainer1a.Resolve<ITestTransientDependency>();
var dep1b = requestContainer1b.Resolve<ITestTransientDependency>();
var dep2 = container2.Resolve<ITestTransientDependency>();
var dep2a = requestContainer2a.Resolve<ITestTransientDependency>();
var dep2b = requestContainer2b.Resolve<ITestTransientDependency>();
Assert.That(dep1, Is.Not.SameAs(dep2));
Assert.That(dep1, Is.Not.SameAs(dep1a));
Assert.That(dep1, Is.Not.SameAs(dep1b));
Assert.That(dep2, Is.Not.SameAs(dep2a));
Assert.That(dep2, Is.Not.SameAs(dep2b));
var again1 = container1.Resolve<ITestTransientDependency>();
var again1a = requestContainer1a.Resolve<ITestTransientDependency>();
var again1b = requestContainer1b.Resolve<ITestTransientDependency>();
var again2 = container2.Resolve<ITestTransientDependency>();
var again2a = requestContainer2a.Resolve<ITestTransientDependency>();
var again2b = requestContainer2b.Resolve<ITestTransientDependency>();
Assert.That(again1, Is.Not.SameAs(dep1));
Assert.That(again1a, Is.Not.SameAs(dep1a));
Assert.That(again1b, Is.Not.SameAs(dep1b));
Assert.That(again2, Is.Not.SameAs(dep2));
Assert.That(again2a, Is.Not.SameAs(dep2a));
Assert.That(again2b, Is.Not.SameAs(dep2b));
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Orchard.Tests.Environment.TestDependencies {
public interface ITestDependency : IDependency {
}
public class TestDependency : ITestDependency{
}
public interface ITestSingletonDependency : ISingletonDependency {
}
public class TestSingletonDependency : ITestSingletonDependency {
}
public interface ITestTransientDependency : ITransientDependency {
}
public class TestTransientDependency : ITestTransientDependency {
}
}

View File

@@ -104,6 +104,7 @@
<Compile Include="Environment\DefaultOrchardHostTests.cs" />
<Compile Include="Environment\DefaultOrchardShellTests.cs" />
<Compile Include="Environment\OrchardStarterTests.cs" />
<Compile Include="Environment\TestDependencies\TestDependency.cs" />
<Compile Include="EventsTests.cs" />
<Compile Include="Localization\NullLocalizerTests.cs" />
<Compile Include="Logging\LoggingModuleTests.cs" />

View File

@@ -6,9 +6,11 @@ using Orchard.CmsPages.Services;
namespace Orchard.CmsPages.Controllers {
public class TemplatesController : Controller {
private readonly IPageManager _pageManager;
private readonly ISlugConstraint _slugConstraint;
public TemplatesController(IPageManager pageManager) {
public TemplatesController(IPageManager pageManager, ISlugConstraint slugConstraint) {
_pageManager = pageManager;
_slugConstraint = slugConstraint;
}
public ActionResult Show(string slug) {
@@ -17,7 +19,9 @@ namespace Orchard.CmsPages.Controllers {
throw new ArgumentNullException("slug");
}
var revision = _pageManager.GetPublishedBySlug(slug);
var correctedSlug = _slugConstraint.LookupPublishedSlug(slug);
var revision = _pageManager.GetPublishedBySlug(correctedSlug);
if (revision == null) {
//TODO: Error message
throw new HttpException(404, "slug " + slug + " was not found");

View File

@@ -96,6 +96,7 @@
<Compile Include="Services\Templates\MetadataEntry.cs" />
<Compile Include="Services\Templates\TemplateMetadataParser.cs" />
<Compile Include="Services\XmlRpcHandler.cs" />
<Compile Include="Services\SlugConstraint.cs" />
<Compile Include="ViewModels\ChooseTemplateViewModel.cs" />
<Compile Include="ViewModels\PageCreateViewModel.cs" />
<Compile Include="ViewModels\PageEditViewModel.cs" />

View File

@@ -1,18 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.CmsPages.Services;
using Orchard.Mvc.Routes;
namespace Orchard.CmsPages {
public class Routes : IRouteProvider, IRouteConstraint {
private readonly IPageManager _pageManager;
public Routes(IPageManager pageManager) {
_pageManager = pageManager;
public class Routes : IRouteProvider {
private readonly ISlugConstraint _slugConstraint;
public Routes(ISlugConstraint slugConstraint) {
_slugConstraint = slugConstraint;
}
public void GetRoutes(ICollection<RouteDescriptor> routes) {
@@ -21,7 +19,6 @@ namespace Orchard.CmsPages {
}
public IEnumerable<RouteDescriptor> GetRoutes() {
IRouteConstraint slugConstraint = this;
return new[] {
new RouteDescriptor {
Priority = 10,
@@ -33,7 +30,7 @@ namespace Orchard.CmsPages {
{"action", "show"}
},
new RouteValueDictionary {
{"slug", slugConstraint}
{"slug", _slugConstraint}
},
new RouteValueDictionary {
{"area", "Orchard.CmsPages"}
@@ -43,15 +40,5 @@ namespace Orchard.CmsPages {
};
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
//TEMP: direct db call...
object value;
if (values.TryGetValue(parameterName, out value)) {
var parameterValue = Convert.ToString(value);
bool result = _pageManager.GetCurrentlyPublishedSlugs().Count(slug => slug == parameterValue) != 0;
return result;
}
return false;
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using Orchard.Tasks;
namespace Orchard.CmsPages.Services {
public interface ISlugConstraint : IRouteConstraint, ISingletonDependency {
void SetCurrentlyPublishedSlugs(IEnumerable<string> slugs);
string LookupPublishedSlug(string slug);
}
public class SlugConstraint : ISlugConstraint {
private IDictionary<string, string> _currentlyPublishedSlugs = new Dictionary<string,string>();
public void SetCurrentlyPublishedSlugs(IEnumerable<string> values) {
_currentlyPublishedSlugs = values
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToDictionary(value => value, StringComparer.OrdinalIgnoreCase);
}
public string LookupPublishedSlug(string slug) {
string actual;
if (_currentlyPublishedSlugs.TryGetValue(slug, out actual))
return actual;
return slug;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
object value;
if (values.TryGetValue(parameterName, out value)) {
var parameterValue = Convert.ToString(value);
return _currentlyPublishedSlugs.ContainsKey(parameterValue);
}
return false;
}
}
public class SlugConstraintUpdater : IBackgroundTask{
private readonly IPageManager _pageManager;
private readonly ISlugConstraint _slugConstraint;
public SlugConstraintUpdater(IPageManager pageManager, ISlugConstraint slugConstraint) {
_pageManager = pageManager;
_slugConstraint = slugConstraint;
}
public void Sweep() {
_slugConstraint.SetCurrentlyPublishedSlugs(_pageManager.GetCurrentlyPublishedSlugs());
}
}
}

View File

@@ -45,7 +45,12 @@ namespace Orchard.Environment {
protected virtual IOrchardShell CreateShell() {
var shellContainer = CreateShellContainer();
return shellContainer.Resolve<IOrchardShell>();
}
public virtual IContainer CreateShellContainer() {
// add module types to container being built
var addingModulesAndServices = new ContainerBuilder();
foreach (var moduleType in _compositionStrategy.GetModuleTypes()) {
@@ -55,8 +60,18 @@ namespace Orchard.Environment {
// add components by the IDependency interfaces they expose
foreach (var serviceType in _compositionStrategy.GetDependencyTypes()) {
foreach (var interfaceType in serviceType.GetInterfaces())
if (typeof(IDependency).IsAssignableFrom(interfaceType))
addingModulesAndServices.Register(serviceType).As(interfaceType).ContainerScoped();
if (typeof(IDependency).IsAssignableFrom(interfaceType)) {
var registrar = addingModulesAndServices.Register(serviceType).As(interfaceType);
if (typeof(ISingletonDependency).IsAssignableFrom(interfaceType)){
registrar.SingletonScoped();
}
else if (typeof(ITransientDependency).IsAssignableFrom(interfaceType)){
registrar.FactoryScoped();
}
else {
registrar.ContainerScoped();
}
}
}
var shellContainer = _container.CreateInnerContainer();
@@ -69,8 +84,7 @@ namespace Orchard.Environment {
addingModules.RegisterModule(module);
}
addingModules.Build(shellContainer);
return shellContainer.Resolve<IOrchardShell>();
return shellContainer;
}
#region IOrchardHost Members

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -7,7 +6,6 @@ using Orchard.Logging;
using Orchard.Mvc.ModelBinders;
using Orchard.Mvc.Routes;
using Orchard.Packages;
using Orchard.Tasks;
namespace Orchard.Environment {
public class DefaultOrchardShell : IOrchardShell {

View File

@@ -1,4 +1,11 @@
namespace Orchard {
public interface IDependency {
}
public interface ISingletonDependency : IDependency {
}
public interface ITransientDependency : IDependency {
}
}