mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Incremental work towards cache/volatile system
--HG-- branch : dev
This commit is contained in:
@@ -18,7 +18,18 @@ namespace Orchard.Profile.Tests {
|
||||
|
||||
[Given(@"I am logged in")]
|
||||
public void GivenIAmLoggedIn() {
|
||||
DoRequest("/Users/Account/LogOn", "userNameOrEmail=admin&password=profiling-secret&rememberMe=false");
|
||||
DoRequest("/Users/Account/LogOn");
|
||||
|
||||
const string requestVerificationTokenName = "__RequestVerificationToken";
|
||||
const string valueMarker = "value=\"";
|
||||
|
||||
var tokenIndex = _text.IndexOf(requestVerificationTokenName);
|
||||
var valueIndex = _text.IndexOf(valueMarker, tokenIndex);
|
||||
var valueStart = valueIndex + valueMarker.Length;
|
||||
var valueEnd = _text.IndexOf("\"", valueStart);
|
||||
var requestVerificationTokenValue = _text.Substring(valueStart, valueEnd - valueStart);
|
||||
|
||||
DoRequest("/Users/Account/LogOn", "userNameOrEmail=admin&password=profiling-secret&rememberMe=false&" + requestVerificationTokenName + "=" + requestVerificationTokenValue);
|
||||
}
|
||||
|
||||
[When(@"I go to ""(.*)""")]
|
||||
|
@@ -3,6 +3,12 @@
|
||||
As a developer
|
||||
I want to generate a fixed number of repeatable requests
|
||||
|
||||
Scenario: Warmup
|
||||
Given I am logged in
|
||||
When I go to "/admin"
|
||||
When I go to "/blog0"
|
||||
When I go to "/"
|
||||
|
||||
Scenario: Dashboard
|
||||
Given I am logged in
|
||||
When I go to "/admin" 40 times
|
||||
|
57
src/Orchard.Profile/Tests/Profiling.feature.cs
generated
57
src/Orchard.Profile/Tests/Profiling.feature.cs
generated
@@ -2,7 +2,7 @@
|
||||
// <auto-generated>
|
||||
// This code was generated by SpecFlow (http://www.specflow.org/).
|
||||
// SpecFlow Version:1.2.0.0
|
||||
// Runtime Version:2.0.50727.3603
|
||||
// Runtime Version:2.0.50727.4927
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
@@ -51,15 +51,34 @@ namespace Orchard.Profile.Tests
|
||||
}
|
||||
|
||||
[NUnit.Framework.TestAttribute()]
|
||||
[NUnit.Framework.DescriptionAttribute("Dashboard")]
|
||||
public virtual void Dashboard()
|
||||
[NUnit.Framework.DescriptionAttribute("Warmup")]
|
||||
public virtual void Warmup()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Dashboard", ((string[])(null)));
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Warmup", ((string[])(null)));
|
||||
#line 6
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 7
|
||||
testRunner.Given("I am logged in");
|
||||
#line 8
|
||||
testRunner.When("I go to \"/admin\"");
|
||||
#line 9
|
||||
testRunner.When("I go to \"/blog0\"");
|
||||
#line 10
|
||||
testRunner.When("I go to \"/\"");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
}
|
||||
|
||||
[NUnit.Framework.TestAttribute()]
|
||||
[NUnit.Framework.DescriptionAttribute("Dashboard")]
|
||||
public virtual void Dashboard()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Dashboard", ((string[])(null)));
|
||||
#line 12
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 13
|
||||
testRunner.Given("I am logged in");
|
||||
#line 14
|
||||
testRunner.When("I go to \"/admin\" 40 times");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
@@ -70,19 +89,19 @@ this.ScenarioSetup(scenarioInfo);
|
||||
public virtual void HittingBlogs()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Hitting blogs", ((string[])(null)));
|
||||
#line 10
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 11
|
||||
testRunner.Given("I am logged in");
|
||||
#line 12
|
||||
testRunner.When("I go to \"/blog0\" 10 times");
|
||||
#line 13
|
||||
testRunner.When("I go to \"/blog1\" 10 times");
|
||||
#line 14
|
||||
testRunner.When("I go to \"/blog2\" 10 times");
|
||||
#line 15
|
||||
testRunner.When("I go to \"/blog3\" 10 times");
|
||||
#line 16
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 17
|
||||
testRunner.Given("I am logged in");
|
||||
#line 18
|
||||
testRunner.When("I go to \"/blog0\" 10 times");
|
||||
#line 19
|
||||
testRunner.When("I go to \"/blog1\" 10 times");
|
||||
#line 20
|
||||
testRunner.When("I go to \"/blog2\" 10 times");
|
||||
#line 21
|
||||
testRunner.When("I go to \"/blog3\" 10 times");
|
||||
#line 22
|
||||
testRunner.When("I go to \"/blog4\" 10 times");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
@@ -93,11 +112,11 @@ this.ScenarioSetup(scenarioInfo);
|
||||
public virtual void HittingHomePage()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Hitting home page", ((string[])(null)));
|
||||
#line 18
|
||||
#line 24
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 19
|
||||
#line 25
|
||||
testRunner.Given("I am logged in");
|
||||
#line 20
|
||||
#line 26
|
||||
testRunner.When("I go to \"/\" 40 times");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
|
@@ -1,6 +1,3 @@
|
||||
help commands
|
||||
setup /SiteName:Profiling /AdminUsername:admin /AdminPassword:profiling-secret /DatabaseProvider:SQLite /EnabledFeatures:Orchard.Framework,Common,Dashboard,Feeds,HomePage,Navigation,Scheduling,Settings,XmlRpc,Orchard.Users,Orchard.Roles,TinyMce,Orchard.Modules,Orchard.Themes,Orchard.MultiTenancy,Orchard.Pages,Orchard.Blogs,Orchard.Comments,Futures.Widgets,Orchard.Media,Orchard.Tags,Orchard.DevTools
|
||||
help commands
|
||||
tenant list
|
||||
feature list
|
||||
setup /SiteName:Profiling /AdminUsername:admin /AdminPassword:profiling-secret /DatabaseProvider:SQLite /EnabledFeatures:Orchard.Framework,Common,Dashboard,Feeds,HomePage,Navigation,Scheduling,Settings,XmlRpc,Orchard.Users,Orchard.Roles,TinyMce,Orchard.Modules,Orchard.Themes,Orchard.MultiTenancy,Orchard.Pages,Orchard.Blogs,Orchard.Comments,Futures.Widgets,Orchard.Media,Orchard.Tags,Orchard.DevTools
|
||||
add profiling data
|
||||
feature disable Orchard.DevTools
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.FileSystems;
|
||||
|
||||
namespace Orchard.Tests.Environment.Configuration {
|
||||
[TestFixture]
|
||||
|
@@ -15,6 +15,7 @@ using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.Extensions.Folders;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Environment.ShellBuilders;
|
||||
using Orchard.Environment.Topology;
|
||||
using Orchard.Environment.Topology.Models;
|
||||
|
@@ -49,7 +49,7 @@ namespace Orchard.Tests.Environment.Extensions {
|
||||
|
||||
[Test]
|
||||
public void NamesFromFoldersWithModuleTxtShouldBeListed() {
|
||||
var folders = new ModuleFolders(new[] { _tempFolderName });
|
||||
var folders = new ModuleFolders(new[] { _tempFolderName },null,null);
|
||||
var names = folders.ListNames();
|
||||
Assert.That(names.Count(), Is.EqualTo(2));
|
||||
Assert.That(names, Has.Some.EqualTo("Sample1"));
|
||||
@@ -58,7 +58,7 @@ namespace Orchard.Tests.Environment.Extensions {
|
||||
|
||||
[Test]
|
||||
public void ModuleTxtShouldBeParsedAndReturnedAsYamlDocument() {
|
||||
var folders = new ModuleFolders(new[] { _tempFolderName });
|
||||
var folders = new ModuleFolders(new[] { _tempFolderName },null,null);
|
||||
var sample1 = folders.ParseManifest("Sample1");
|
||||
var mapping = (Mapping)sample1.YamlDocument.Root;
|
||||
var entities = mapping.Entities
|
||||
|
@@ -51,6 +51,10 @@ namespace Orchard.Tests.Environment.Extensions {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<ExtensionDescriptor> AvailableExtensions() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class StubLoaders : IExtensionLoader {
|
||||
|
@@ -4,6 +4,7 @@ using System.Xml;
|
||||
using Autofac;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Environment.Topology;
|
||||
using Orchard.Environment.Topology.Models;
|
||||
|
||||
|
@@ -56,6 +56,14 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\fluentnhibernate\FluentNHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Machine.Migrations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5c474de7a495cff1, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\machine.migrations\Machine.Migrations.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Machine.Migrations.NHibernate, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\machine.migrations\Machine.Migrations.NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Moq, Version=4.0.812.4, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\moq\Moq.dll</HintPath>
|
||||
|
@@ -73,7 +73,6 @@
|
||||
<Compile Include="Models\ShowDebugLink.cs" />
|
||||
<Compile Include="Models\Simple.cs" />
|
||||
<Compile Include="Permissions.cs" />
|
||||
<Compile Include="Profiler.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ViewModels\ContentIndexViewModel.cs" />
|
||||
<Compile Include="ViewModels\ContentDetailsViewModel.cs" />
|
||||
|
@@ -1,13 +0,0 @@
|
||||
using Orchard.Environment;
|
||||
|
||||
namespace Orchard.DevTools {
|
||||
public class Profiler : IOrchardShellEvents {
|
||||
public void Activated() {
|
||||
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize();
|
||||
}
|
||||
|
||||
public void Terminating() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,8 +8,8 @@ using Orchard.Core.Navigation.Models;
|
||||
using Orchard.Core.Settings.Models;
|
||||
using Orchard.Data;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Environment.ShellBuilders;
|
||||
using Orchard.Environment.Topology;
|
||||
using Orchard.Environment.Topology.Models;
|
||||
|
@@ -57,7 +57,7 @@ namespace Orchard.Users.Controllers {
|
||||
[HttpPost]
|
||||
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
|
||||
Justification = "Needs to take same parameter type as Controller.Redirect()")]
|
||||
public ActionResult LogOn(string userNameOrEmail, string password, bool rememberMe) {
|
||||
public ActionResult LogOn(string userNameOrEmail, string password, bool rememberMe, string returnUrl) {
|
||||
var user = ValidateLogOn(userNameOrEmail, password);
|
||||
if (!ModelState.IsValid) {
|
||||
return View("LogOn", new LogOnViewModel {Title = "Log On"});
|
||||
@@ -65,13 +65,19 @@ namespace Orchard.Users.Controllers {
|
||||
|
||||
_authenticationService.SignIn(user, rememberMe);
|
||||
|
||||
return this.ReturnUrlRedirect();
|
||||
if (string.IsNullOrEmpty(returnUrl))
|
||||
return new RedirectResult("~/");
|
||||
|
||||
return new RedirectResult(returnUrl);
|
||||
}
|
||||
|
||||
public ActionResult LogOff() {
|
||||
public ActionResult LogOff(string returnUrl) {
|
||||
_authenticationService.SignOut();
|
||||
|
||||
return this.ReturnUrlRedirect();
|
||||
if (string.IsNullOrEmpty(returnUrl))
|
||||
return new RedirectResult("~/");
|
||||
|
||||
return new RedirectResult(returnUrl);
|
||||
}
|
||||
|
||||
int MinPasswordLength {
|
||||
|
@@ -393,7 +393,7 @@
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>False</AutoAssignPort>
|
||||
<DevelopmentServerPort>30320</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>80</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>
|
||||
</IISUrl>
|
||||
|
@@ -1,4 +1,14 @@
|
||||
namespace Orchard.Caching {
|
||||
public class AcquireContext {
|
||||
using System;
|
||||
using Orchard.Caching.Providers;
|
||||
|
||||
namespace Orchard.Caching {
|
||||
public class AcquireContext<TKey> {
|
||||
public AcquireContext(TKey key, Action<IVolatileSignal> monitor) {
|
||||
Key = key;
|
||||
IsInvalid = monitor;
|
||||
}
|
||||
|
||||
public TKey Key { get; private set; }
|
||||
public Action<IVolatileSignal> IsInvalid { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Caching.Providers;
|
||||
|
||||
namespace Orchard.Caching {
|
||||
public class Cache<TKey, TResult> : ICache<TKey, TResult> {
|
||||
@@ -9,22 +10,21 @@ namespace Orchard.Caching {
|
||||
_entries = new Dictionary<TKey, CacheEntry>();
|
||||
}
|
||||
|
||||
#region Implementation of ICache<TKey,TResult>
|
||||
|
||||
public TResult Get(TKey key, Func<AcquireContext, TResult> acquire) {
|
||||
public TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire) {
|
||||
CacheEntry entry;
|
||||
if (!_entries.TryGetValue(key, out entry)) {
|
||||
AcquireContext context = new AcquireContext();
|
||||
entry = new CacheEntry {Result = acquire(context)};
|
||||
entry = new CacheEntry { VolatileItems = new List<IVolatileSignal>() };
|
||||
|
||||
var context = new AcquireContext<TKey>(key, volatileItem => entry.VolatileItems.Add(volatileItem));
|
||||
entry.Result = acquire(context);
|
||||
_entries.Add(key, entry);
|
||||
}
|
||||
return entry.Result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class CacheEntry {
|
||||
private class CacheEntry {
|
||||
public TResult Result { get; set; }
|
||||
public IList<IVolatileSignal> VolatileItems { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ namespace Orchard.Caching {
|
||||
|
||||
#region Implementation of ICacheManager
|
||||
|
||||
public TResult Get<TKey, TResult>(TKey key, Func<AcquireContext, TResult> acquire) {
|
||||
public TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire) {
|
||||
return GetCache<TKey, TResult>().Get(key, acquire);
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,6 @@
|
||||
|
||||
namespace Orchard.Caching {
|
||||
public interface ICache<TKey, TResult> {
|
||||
TResult Get(TKey key, Func<AcquireContext, TResult> acquire);
|
||||
TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Orchard.Caching {
|
||||
public interface ICacheManager : ISingletonDependency {
|
||||
TResult Get<TKey, TResult>(TKey key, Func<AcquireContext, TResult> acquire);
|
||||
TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire);
|
||||
ICache<TKey, TResult> GetCache<TKey, TResult>();
|
||||
}
|
||||
}
|
||||
|
5
src/Orchard/Caching/Providers/IVolatileProvider.cs
Normal file
5
src/Orchard/Caching/Providers/IVolatileProvider.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Orchard.Caching.Providers {
|
||||
public interface IVolatileProvider : IDependency {
|
||||
void Enlist(IVolatileSink sink);
|
||||
}
|
||||
}
|
5
src/Orchard/Caching/Providers/IVolatileSignal.cs
Normal file
5
src/Orchard/Caching/Providers/IVolatileSignal.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Orchard.Caching.Providers {
|
||||
public interface IVolatileSignal {
|
||||
IVolatileProvider Provider { get; set; }
|
||||
}
|
||||
}
|
5
src/Orchard/Caching/Providers/IVolatileSink.cs
Normal file
5
src/Orchard/Caching/Providers/IVolatileSink.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Orchard.Caching.Providers {
|
||||
public interface IVolatileSink {
|
||||
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ using System.IO;
|
||||
using NHibernate;
|
||||
using Orchard.Data.Builders;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Environment.Topology;
|
||||
using Orchard.Environment.Topology.Models;
|
||||
using Orchard.Logging;
|
||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Yaml.Serialization;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.Environment.Configuration {
|
||||
|
@@ -34,13 +34,7 @@ namespace Orchard.Environment.Extensions {
|
||||
// This method does not load extension types, simply parses extension manifests from
|
||||
// the filesystem.
|
||||
public IEnumerable<ExtensionDescriptor> AvailableExtensions() {
|
||||
var availableExtensions = new List<ExtensionDescriptor>();
|
||||
foreach (var folder in _folders) {
|
||||
foreach (var name in folder.ListNames()) {
|
||||
availableExtensions.Add(GetDescriptorForExtension(name, folder));
|
||||
}
|
||||
}
|
||||
return availableExtensions;
|
||||
return _folders.SelectMany(folder=>folder.AvailableExtensions());
|
||||
}
|
||||
|
||||
public IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors) {
|
||||
@@ -57,65 +51,6 @@ namespace Orchard.Environment.Extensions {
|
||||
return _activeExtensions;
|
||||
}
|
||||
|
||||
|
||||
private static ExtensionDescriptor GetDescriptorForExtension(string name, IExtensionFolders folder) {
|
||||
string extensionType = folder is ThemeFolders ? "Theme" : "Module";
|
||||
var parseResult = folder.ParseManifest(name);
|
||||
var mapping = (Mapping)parseResult.YamlDocument.Root;
|
||||
var fields = mapping.Entities
|
||||
.Where(x => x.Key is Scalar)
|
||||
.ToDictionary(x => ((Scalar)x.Key).Text, x => x.Value);
|
||||
|
||||
var extensionDescriptor = new ExtensionDescriptor {
|
||||
Location = parseResult.Location,
|
||||
Name = name,
|
||||
ExtensionType = extensionType,
|
||||
DisplayName = GetValue(fields, "name") ?? name,
|
||||
Description = GetValue(fields, "description"),
|
||||
Version = GetValue(fields, "version"),
|
||||
OrchardVersion = GetValue(fields, "orchardversion"),
|
||||
Author = GetValue(fields, "author"),
|
||||
WebSite = GetValue(fields, "website"),
|
||||
Tags = GetValue(fields, "tags"),
|
||||
AntiForgery = GetValue(fields, "antiforgery"),
|
||||
};
|
||||
extensionDescriptor.Features = GetFeaturesForExtension(GetMapping(fields, "features"), extensionDescriptor);
|
||||
return extensionDescriptor;
|
||||
}
|
||||
|
||||
private static IEnumerable<FeatureDescriptor> GetFeaturesForExtension(Mapping features, ExtensionDescriptor extensionDescriptor) {
|
||||
List<FeatureDescriptor> featureDescriptors = new List<FeatureDescriptor>();
|
||||
if (features != null) {
|
||||
foreach (var entity in features.Entities) {
|
||||
FeatureDescriptor featureDescriptor = new FeatureDescriptor {
|
||||
Extension = extensionDescriptor,
|
||||
Name = entity.Key.ToString(),
|
||||
};
|
||||
Mapping featureMapping = (Mapping)entity.Value;
|
||||
foreach (var featureEntity in featureMapping.Entities) {
|
||||
if (String.Equals(featureEntity.Key.ToString(), "description", StringComparison.OrdinalIgnoreCase)) {
|
||||
featureDescriptor.Description = featureEntity.Value.ToString();
|
||||
}
|
||||
else if (String.Equals(featureEntity.Key.ToString(), "category", StringComparison.OrdinalIgnoreCase)) {
|
||||
featureDescriptor.Category = featureEntity.Value.ToString();
|
||||
}
|
||||
else if (String.Equals(featureEntity.Key.ToString(), "dependencies", StringComparison.OrdinalIgnoreCase)) {
|
||||
featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureEntity.Value.ToString());
|
||||
}
|
||||
}
|
||||
featureDescriptors.Add(featureDescriptor);
|
||||
}
|
||||
}
|
||||
if (!featureDescriptors.Any(fd => fd.Name == extensionDescriptor.Name)) {
|
||||
featureDescriptors.Add(new FeatureDescriptor {
|
||||
Name = extensionDescriptor.Name,
|
||||
Dependencies = new string[0],
|
||||
Extension = extensionDescriptor,
|
||||
});
|
||||
}
|
||||
return featureDescriptors;
|
||||
}
|
||||
|
||||
private Feature LoadFeature(FeatureDescriptor featureDescriptor) {
|
||||
var featureName = featureDescriptor.Name;
|
||||
string extensionName = GetExtensionForFeature(featureName);
|
||||
@@ -237,28 +172,5 @@ namespace Orchard.Environment.Extensions {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string[] ParseFeatureDependenciesEntry(string dependenciesEntry) {
|
||||
List<string> dependencies = new List<string>();
|
||||
foreach (var s in dependenciesEntry.Split(',')) {
|
||||
dependencies.Add(s.Trim());
|
||||
}
|
||||
return dependencies.ToArray();
|
||||
}
|
||||
|
||||
private static Mapping GetMapping(
|
||||
IDictionary<string, DataItem> fields,
|
||||
string key) {
|
||||
|
||||
DataItem value;
|
||||
return fields.TryGetValue(key, out value) ? (Mapping)value : null;
|
||||
}
|
||||
|
||||
private static string GetValue(
|
||||
IDictionary<string, DataItem> fields,
|
||||
string key) {
|
||||
|
||||
DataItem value;
|
||||
return fields.TryGetValue(key, out value) ? value.ToString() : null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.FileSystems;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Folders {
|
||||
public class AreaFolders : ExtensionFolders {
|
||||
public AreaFolders(IEnumerable<string> paths) :
|
||||
base(paths, "Module.txt", true/*isManifestOptional*/) {
|
||||
public AreaFolders(IEnumerable<string> paths, ICacheManager cacheManager, IVirtualPathProvider virtualPathProvider) :
|
||||
base(paths, "Module.txt", true/*isManifestOptional*/, cacheManager, virtualPathProvider) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,19 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web.Hosting;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Helpers;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Yaml.Grammar;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Folders {
|
||||
public class ParseResult {
|
||||
public string Location { get; set; }
|
||||
public string Name { get; set; }
|
||||
public YamlDocument YamlDocument { get; set; }
|
||||
}
|
||||
|
||||
public class ExtensionFolders : IExtensionFolders {
|
||||
private readonly IEnumerable<string> _paths;
|
||||
private readonly string _manifestName;
|
||||
private readonly string _extensionType;
|
||||
private readonly bool _manifestIsOptional;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||
|
||||
public ExtensionFolders(IEnumerable<string> paths, string manifestName, bool manifestIsOptional) {
|
||||
public ExtensionFolders(
|
||||
IEnumerable<string> paths,
|
||||
string manifestName,
|
||||
bool manifestIsOptional,
|
||||
ICacheManager cacheManager,
|
||||
IVirtualPathProvider virtualPathProvider) {
|
||||
_paths = paths;
|
||||
_manifestName = manifestName;
|
||||
_extensionType = manifestName == "Theme.txt" ? "Theme" : "Module";
|
||||
_manifestIsOptional = manifestIsOptional;
|
||||
_cacheManager = cacheManager;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
}
|
||||
|
||||
public IEnumerable<ExtensionDescriptor> AvailableExtensions() {
|
||||
var virtualPaths = _cacheManager.Get("", ctx => {
|
||||
var x = 5;
|
||||
return new[] { "hello" };
|
||||
});
|
||||
|
||||
|
||||
var list = new List<ExtensionDescriptor>();
|
||||
foreach (var virtualPath in virtualPaths) {
|
||||
list.Add(GetExtensionDescriptor(virtualPath));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
ExtensionDescriptor GetExtensionDescriptor(string virtualPath) {
|
||||
return _cacheManager.Get(virtualPath, context => {
|
||||
|
||||
context.IsInvalid(_virtualPathProvider.WhenPathChanges(virtualPath));
|
||||
|
||||
var text = _virtualPathProvider.ReadAllText(virtualPath);
|
||||
|
||||
var parseResult = ParseManifest(text);
|
||||
return GetDescriptorForExtension(parseResult.Name, parseResult);
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<string> ListNames() {
|
||||
@@ -43,10 +91,10 @@ namespace Orchard.Environment.Extensions.Folders {
|
||||
if (File.Exists(extensionManifestPath)) {
|
||||
var yamlStream = YamlParser.Load(extensionManifestPath);
|
||||
return new ParseResult {
|
||||
Location = path,
|
||||
Name = name,
|
||||
YamlDocument = yamlStream.Documents.Single()
|
||||
};
|
||||
Location = path,
|
||||
Name = name,
|
||||
YamlDocument = yamlStream.Documents.Single()
|
||||
};
|
||||
}
|
||||
|
||||
if (_manifestIsOptional) {
|
||||
@@ -55,13 +103,94 @@ namespace Orchard.Environment.Extensions.Folders {
|
||||
bool success;
|
||||
var yamlStream = parser.ParseYamlStream(yamlInput, out success);
|
||||
return new ParseResult {
|
||||
Location = path,
|
||||
Name = name,
|
||||
YamlDocument = yamlStream.Documents.Single()
|
||||
};
|
||||
Location = path,
|
||||
Name = name,
|
||||
YamlDocument = yamlStream.Documents.Single()
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private ExtensionDescriptor GetDescriptorForExtension(string name, ParseResult parseResult) {
|
||||
var mapping = (Mapping)parseResult.YamlDocument.Root;
|
||||
var fields = mapping.Entities
|
||||
.Where(x => x.Key is Scalar)
|
||||
.ToDictionary(x => ((Scalar)x.Key).Text, x => x.Value);
|
||||
|
||||
var extensionDescriptor = new ExtensionDescriptor {
|
||||
Location = parseResult.Location,
|
||||
Name = name,
|
||||
ExtensionType = _extensionType,
|
||||
DisplayName = GetValue(fields, "name") ?? name,
|
||||
Description = GetValue(fields, "description"),
|
||||
Version = GetValue(fields, "version"),
|
||||
OrchardVersion = GetValue(fields, "orchardversion"),
|
||||
Author = GetValue(fields, "author"),
|
||||
WebSite = GetValue(fields, "website"),
|
||||
Tags = GetValue(fields, "tags"),
|
||||
AntiForgery = GetValue(fields, "antiforgery"),
|
||||
};
|
||||
extensionDescriptor.Features = GetFeaturesForExtension(GetMapping(fields, "features"), extensionDescriptor);
|
||||
return extensionDescriptor;
|
||||
}
|
||||
|
||||
private static IEnumerable<FeatureDescriptor> GetFeaturesForExtension(Mapping features, ExtensionDescriptor extensionDescriptor) {
|
||||
List<FeatureDescriptor> featureDescriptors = new List<FeatureDescriptor>();
|
||||
if (features != null) {
|
||||
foreach (var entity in features.Entities) {
|
||||
FeatureDescriptor featureDescriptor = new FeatureDescriptor {
|
||||
Extension = extensionDescriptor,
|
||||
Name = entity.Key.ToString(),
|
||||
};
|
||||
Mapping featureMapping = (Mapping)entity.Value;
|
||||
foreach (var featureEntity in featureMapping.Entities) {
|
||||
if (String.Equals(featureEntity.Key.ToString(), "description", StringComparison.OrdinalIgnoreCase)) {
|
||||
featureDescriptor.Description = featureEntity.Value.ToString();
|
||||
}
|
||||
else if (String.Equals(featureEntity.Key.ToString(), "category", StringComparison.OrdinalIgnoreCase)) {
|
||||
featureDescriptor.Category = featureEntity.Value.ToString();
|
||||
}
|
||||
else if (String.Equals(featureEntity.Key.ToString(), "dependencies", StringComparison.OrdinalIgnoreCase)) {
|
||||
featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureEntity.Value.ToString());
|
||||
}
|
||||
}
|
||||
featureDescriptors.Add(featureDescriptor);
|
||||
}
|
||||
}
|
||||
if (!featureDescriptors.Any(fd => fd.Name == extensionDescriptor.Name)) {
|
||||
featureDescriptors.Add(new FeatureDescriptor {
|
||||
Name = extensionDescriptor.Name,
|
||||
Dependencies = new string[0],
|
||||
Extension = extensionDescriptor,
|
||||
});
|
||||
}
|
||||
return featureDescriptors;
|
||||
}
|
||||
|
||||
private static string[] ParseFeatureDependenciesEntry(string dependenciesEntry) {
|
||||
List<string> dependencies = new List<string>();
|
||||
foreach (var s in dependenciesEntry.Split(',')) {
|
||||
dependencies.Add(s.Trim());
|
||||
}
|
||||
return dependencies.ToArray();
|
||||
}
|
||||
|
||||
private static Mapping GetMapping(
|
||||
IDictionary<string, DataItem> fields,
|
||||
string key) {
|
||||
|
||||
DataItem value;
|
||||
return fields.TryGetValue(key, out value) ? (Mapping)value : null;
|
||||
}
|
||||
|
||||
private static string GetValue(
|
||||
IDictionary<string, DataItem> fields,
|
||||
string key) {
|
||||
|
||||
DataItem value;
|
||||
return fields.TryGetValue(key, out value) ? value.ToString() : null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Yaml.Grammar;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Folders {
|
||||
public interface IExtensionFolders {
|
||||
IEnumerable<string> ListNames();
|
||||
ParseResult ParseManifest(string name);
|
||||
IEnumerable<ExtensionDescriptor> AvailableExtensions();
|
||||
}
|
||||
|
||||
public class ParseResult {
|
||||
public string Location { get; set; }
|
||||
public string Name { get; set; }
|
||||
public YamlDocument YamlDocument { get; set; }
|
||||
}
|
||||
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.FileSystems;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Folders {
|
||||
public class ModuleFolders : ExtensionFolders {
|
||||
public ModuleFolders(IEnumerable<string> paths) :
|
||||
base(paths, "Module.txt", false/*isManifestOptional*/) {
|
||||
public ModuleFolders(IEnumerable<string> paths, ICacheManager cacheManager, IVirtualPathProvider virtualPathProvider) :
|
||||
base(paths, "Module.txt", false/*isManifestOptional*/, cacheManager, virtualPathProvider) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.FileSystems;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Folders {
|
||||
public class ThemeFolders : ExtensionFolders {
|
||||
public ThemeFolders(IEnumerable<string> paths) :
|
||||
base(paths, "Theme.txt", false/*manifestIsOptional*/) {
|
||||
public ThemeFolders(IEnumerable<string> paths, ICacheManager cacheManager, IVirtualPathProvider virtualPathProvider) :
|
||||
base(paths, "Theme.txt", false/*manifestIsOptional*/, cacheManager, virtualPathProvider) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,33 +3,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web.Hosting;
|
||||
using Orchard.Environment.FileSystems;
|
||||
|
||||
namespace Orchard.Environment.Configuration {
|
||||
/// <summary>
|
||||
/// Abstraction of App_Data folder
|
||||
/// Expected to work on physical filesystem, but decouples core
|
||||
/// system from web hosting apis
|
||||
/// </summary>
|
||||
public interface IAppDataFolder {
|
||||
IEnumerable<string> ListFiles(string path);
|
||||
IEnumerable<string> ListDirectories(string path);
|
||||
|
||||
void CreateFile(string path, string content);
|
||||
string ReadFile(string path);
|
||||
void DeleteFile(string path);
|
||||
bool FileExists(string path);
|
||||
|
||||
string CreateDirectory(string path);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// May be called to initialize component when not in a hosting environment
|
||||
/// app domain
|
||||
/// </summary>
|
||||
void SetBasePath(string basePath);
|
||||
string MapPath(string path);
|
||||
}
|
||||
|
||||
public class AppDataFolder : IAppDataFolder {
|
||||
protected string _basePath;
|
||||
|
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Web.Hosting;
|
||||
using Orchard.Caching.Providers;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.Environment.FileSystems {
|
||||
public class DefaultVirtualPathProvider : IVirtualPathProvider {
|
||||
private readonly IClock _clock;
|
||||
private readonly IList<IVolatileSink> _sinks = new List<IVolatileSink>();
|
||||
//private string _cachePrefix = Guid.NewGuid().ToString("n");
|
||||
|
||||
public DefaultVirtualPathProvider(IClock clock) {
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
public string ReadAllText(string virtualPath) {
|
||||
using (var stream = VirtualPathProvider.OpenFile(virtualPath)) {
|
||||
using (var reader = new StreamReader(stream)) {
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
//var cd = HostingEnvironment.VirtualPathProvider.. .GetCacheDependency(virtualPath, null, _clock.UtcNow);
|
||||
//HostingEnvironment.Cache.Add(
|
||||
// _cachePrefix + virtualPath,
|
||||
// virtualPath,
|
||||
// cd,
|
||||
// NoAbsoluteExpiration,
|
||||
// );
|
||||
}
|
||||
|
||||
public IVolatileSignal WhenPathChanges(string virtualPath) {
|
||||
|
||||
return new VirtualPathSignal(this, virtualPath);
|
||||
}
|
||||
|
||||
|
||||
class VirtualPathSignal : IVolatileSignal {
|
||||
private readonly string _virtualPath;
|
||||
|
||||
public VirtualPathSignal(DefaultVirtualPathProvider provider, string virtualPath) {
|
||||
_virtualPath = virtualPath;
|
||||
Provider = provider;
|
||||
}
|
||||
|
||||
public IVolatileProvider Provider { get; set; }
|
||||
}
|
||||
|
||||
public void Enlist(IVolatileSink sink) {
|
||||
_sinks.Add(sink);
|
||||
}
|
||||
}
|
||||
}
|
28
src/Orchard/Environment/FileSystems/IAppDataFolder.cs
Normal file
28
src/Orchard/Environment/FileSystems/IAppDataFolder.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.Environment.FileSystems {
|
||||
/// <summary>
|
||||
/// Abstraction of App_Data folder
|
||||
/// Expected to work on physical filesystem, but decouples core
|
||||
/// system from web hosting apis
|
||||
/// </summary>
|
||||
public interface IAppDataFolder {
|
||||
IEnumerable<string> ListFiles(string path);
|
||||
IEnumerable<string> ListDirectories(string path);
|
||||
|
||||
void CreateFile(string path, string content);
|
||||
string ReadFile(string path);
|
||||
void DeleteFile(string path);
|
||||
bool FileExists(string path);
|
||||
|
||||
string CreateDirectory(string path);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// May be called to initialize component when not in a hosting environment
|
||||
/// app domain
|
||||
/// </summary>
|
||||
void SetBasePath(string basePath);
|
||||
string MapPath(string path);
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using Orchard.Caching.Providers;
|
||||
|
||||
namespace Orchard.Environment.FileSystems {
|
||||
public interface IVirtualPathProvider : IVolatileProvider {
|
||||
IVolatileSignal WhenPathChanges(string path);
|
||||
|
||||
string ReadAllText(string virtualPath);
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.Extensions.Folders;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Environment.ShellBuilders;
|
||||
using Orchard.Environment.Topology;
|
||||
using Orchard.Events;
|
||||
|
@@ -106,7 +106,8 @@ namespace Orchard.Environment.ShellBuilders {
|
||||
|
||||
private IRegistrationBuilder<object, ConcreteReflectionActivatorData, SingleRegistrationStyle> RegisterType(ContainerBuilder builder, ShellTopologyItem item) {
|
||||
return builder.RegisterType(item.Type)
|
||||
.WithProperty("Feature", item.Feature);
|
||||
.WithProperty("Feature", item.Feature)
|
||||
.WithMetadata("Feature", item.Feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.FileSystems;
|
||||
using Orchard.Environment.Topology.Models;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
|
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Orchard.Mvc.Extensions {
|
||||
public static class ControllerExtensions {
|
||||
public static RedirectResult ReturnUrlRedirect(this Controller controller) {
|
||||
string returnUrl = controller.Request.QueryString["ReturnUrl"];
|
||||
|
||||
// prevents phishing attacks by using only relative urls
|
||||
if(!returnUrl.StartsWith("/")) {
|
||||
return new RedirectResult("~/");
|
||||
}
|
||||
|
||||
return new RedirectResult(returnUrl);
|
||||
}
|
||||
}
|
||||
}
|
@@ -63,6 +63,18 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\sharpziplib\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Machine.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5c474de7a495cff1, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\machine.migrations\Machine.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Machine.Migrations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5c474de7a495cff1, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\machine.migrations\Machine.Migrations.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Machine.Migrations.NHibernate, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\machine.migrations\Machine.Migrations.NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NHibernate, Version=2.1.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\fluentnhibernate\NHibernate.dll</HintPath>
|
||||
@@ -134,8 +146,14 @@
|
||||
<Compile Include="Caching\AcquireContext.cs" />
|
||||
<Compile Include="Caching\Cache.cs" />
|
||||
<Compile Include="Caching\DefaultCacheManager.cs" />
|
||||
<Compile Include="Caching\Providers\IVolatileSink.cs" />
|
||||
<Compile Include="Environment\FileSystems\DefaultVirtualPathProvider.cs" />
|
||||
<Compile Include="Environment\FileSystems\IAppDataFolder.cs" />
|
||||
<Compile Include="Environment\FileSystems\IVirtualPathProvider.cs" />
|
||||
<Compile Include="Caching\ICache.cs" />
|
||||
<Compile Include="Caching\ICacheManager.cs" />
|
||||
<Compile Include="Caching\Providers\IVolatileSignal.cs" />
|
||||
<Compile Include="Caching\Providers\IVolatileProvider.cs" />
|
||||
<Compile Include="Commands\CommandParameters.cs" />
|
||||
<Compile Include="Commands\CommandDescriptor.cs" />
|
||||
<Compile Include="Commands\CommandHandlerDescriptor.cs" />
|
||||
@@ -173,7 +191,7 @@
|
||||
<Compile Include="Environment\AutofacUtil\DynamicProxy2\DynamicProxyContext.cs" />
|
||||
<Compile Include="Environment\AutofacUtil\DynamicProxy2\DynamicProxyExtensions.cs" />
|
||||
<Compile Include="Environment\AutofacUtil\DynamicProxy2\ConstructorFinderWrapper.cs" />
|
||||
<Compile Include="Environment\Configuration\AppDataFolder.cs" />
|
||||
<Compile Include="Environment\FileSystems\AppDataFolder.cs" />
|
||||
<Compile Include="Environment\Configuration\ShellSettingsManager.cs" />
|
||||
<Compile Include="Environment\AutofacUtil\ContainerUpdater.cs" />
|
||||
<Compile Include="Environment\ShellBuilders\ShellContextFactory.cs" />
|
||||
@@ -204,7 +222,6 @@
|
||||
<Compile Include="Modules\IModuleFeature.cs" />
|
||||
<Compile Include="Modules\IModuleService.cs" />
|
||||
<Compile Include="Mvc\AntiForgery\ValidateAntiForgeryTokenOrchardAttribute.cs" />
|
||||
<Compile Include="Mvc\Extensions\ControllerExtensions.cs" />
|
||||
<Compile Include="Mvc\Extensions\ModelStateDictionaryExtensions.cs" />
|
||||
<Compile Include="Mvc\Html\FileRegistrationContextExtensions.cs" />
|
||||
<Compile Include="Mvc\Extensions\UrlHelperExtensions.cs" />
|
||||
|
Reference in New Issue
Block a user