mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-14 10:54:50 +08:00
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:
@@ -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" />
|
||||
|
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" });
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
}
|
||||
}
|
@@ -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" />
|
||||
|
@@ -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");
|
||||
|
@@ -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" />
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -1,4 +1,11 @@
|
||||
namespace Orchard {
|
||||
public interface IDependency {
|
||||
}
|
||||
|
||||
public interface ISingletonDependency : IDependency {
|
||||
|
||||
}
|
||||
public interface ITransientDependency : IDependency {
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user