maintenance: plugin refactoring
This commit is contained in:
@@ -450,6 +450,7 @@ namespace Disco.Services.Plugins
|
|||||||
|
|
||||||
handler.Manifest = this;
|
handler.Manifest = this;
|
||||||
handler.HostController = HostController;
|
handler.HostController = HostController;
|
||||||
|
handler.Url = HostController.Url;
|
||||||
|
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ namespace Disco.Services.Plugins
|
|||||||
{
|
{
|
||||||
public PluginManifest Manifest { get; set; }
|
public PluginManifest Manifest { get; set; }
|
||||||
public Controller HostController { get; set; }
|
public Controller HostController { get; set; }
|
||||||
|
public UrlHelper Url { get; set; }
|
||||||
|
|
||||||
protected DiscoDataContext Database;
|
protected DiscoDataContext Database;
|
||||||
private Lazy<WebHelper> plugin;
|
private Lazy<WebHelper> plugin;
|
||||||
protected WebHelper Plugin
|
protected WebHelper Plugin
|
||||||
@@ -29,10 +31,7 @@ namespace Disco.Services.Plugins
|
|||||||
public PluginWebHandler()
|
public PluginWebHandler()
|
||||||
{
|
{
|
||||||
plugin = new Lazy<WebHelper>(new Func<WebHelper>(() => new WebHelper(HostController.HttpContext, Manifest)));
|
plugin = new Lazy<WebHelper>(new Func<WebHelper>(() => new WebHelper(HostController.HttpContext, Manifest)));
|
||||||
}
|
|
||||||
|
|
||||||
public void OnActionExecuting()
|
|
||||||
{
|
|
||||||
Database = new DiscoDataContext();
|
Database = new DiscoDataContext();
|
||||||
Database.Configuration.LazyLoadingEnabled = false;
|
Database.Configuration.LazyLoadingEnabled = false;
|
||||||
}
|
}
|
||||||
@@ -169,6 +168,19 @@ namespace Disco.Services.Plugins
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Status Codes
|
||||||
|
|
||||||
|
public ActionResult Ok()
|
||||||
|
{
|
||||||
|
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
public ActionResult Ok(string description)
|
||||||
|
{
|
||||||
|
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Content
|
#region Content
|
||||||
public ActionResult Content(string content, string contentType, Encoding contentEncoding)
|
public ActionResult Content(string content, string contentType, Encoding contentEncoding)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,115 +9,332 @@ namespace Disco.Services.Plugins
|
|||||||
{
|
{
|
||||||
public abstract class PluginWebHandlerController : PluginWebHandler
|
public abstract class PluginWebHandlerController : PluginWebHandler
|
||||||
{
|
{
|
||||||
|
public ControllerContext ControllerContext => HostController.ControllerContext;
|
||||||
|
|
||||||
public override ActionResult ExecuteAction(string ActionName)
|
public void OnAuthorization(AuthorizationContext authorizationContext, string actionName)
|
||||||
|
{
|
||||||
|
var actionDescriptor = (PluginActionDescriptor)authorizationContext.ActionDescriptor;
|
||||||
|
|
||||||
|
var authorizationFilters = actionDescriptor.GetAuthorizationFilters;
|
||||||
|
foreach (var filter in authorizationFilters)
|
||||||
|
{
|
||||||
|
filter.OnAuthorization(authorizationContext);
|
||||||
|
if (authorizationContext.Result != null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ActionResult ExecuteAction(string actionName)
|
||||||
{
|
{
|
||||||
var handlerType = GetType();
|
var handlerType = GetType();
|
||||||
var methodDescriptor = FindControllerMethod(Manifest, handlerType, ActionName);
|
var methodDescriptor = FindControllerMethod(ControllerContext, Manifest, handlerType, actionName);
|
||||||
|
|
||||||
if (methodDescriptor == null)
|
if (methodDescriptor == null)
|
||||||
return HttpNotFound("Unknown Plugin Method");
|
return HttpNotFound("No such plugin method");
|
||||||
|
|
||||||
// Authorize Method
|
var authorizationContext = new AuthorizationContext(ControllerContext, methodDescriptor);
|
||||||
if (methodDescriptor.Authorizers.Length > 0)
|
OnAuthorization(authorizationContext, actionName);
|
||||||
|
if (authorizationContext.Result != null)
|
||||||
|
return authorizationContext.Result;
|
||||||
|
|
||||||
|
return methodDescriptor.Execute(this, ControllerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PluginActionDescriptor FindControllerMethod(ControllerContext context, PluginManifest manifest, Type handler, string actionName)
|
||||||
|
{
|
||||||
|
var descriptors = GetWebHandler(manifest, handler);
|
||||||
|
return (PluginActionDescriptor)descriptors.FindAction(context, actionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Method Cache
|
||||||
|
private static readonly Dictionary<Type, PluginControllerDescription> pluginControllerCache = new Dictionary<Type, PluginControllerDescription>();
|
||||||
|
private static PluginControllerDescription GetWebHandler(PluginManifest manifest, Type handler)
|
||||||
|
{
|
||||||
|
if (!pluginControllerCache.TryGetValue(handler, out var result))
|
||||||
{
|
{
|
||||||
var attributeDenied = methodDescriptor.Authorizers.FirstOrDefault(a => !a.IsAuthorized(HostController.HttpContext));
|
// Cache Miss
|
||||||
if (attributeDenied != null)
|
result = new PluginControllerDescription(manifest, handler);
|
||||||
{
|
pluginControllerCache[handler] = result;
|
||||||
var message = attributeDenied.HandleUnauthorizedMessage();
|
|
||||||
var resource = string.Format("{0} [{1}]", attributeDenied.AuthorizeResource, HostController.Request.RawUrl);
|
|
||||||
|
|
||||||
if (CurrentUser != null)
|
|
||||||
AuthorizationLog.LogAccessDenied(CurrentUser.UserId, resource, message);
|
|
||||||
|
|
||||||
return new HttpUnauthorizedResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var methodParams = BuildMethodParameters(Manifest, handlerType, methodDescriptor.MethodInfo, ActionName, HostController);
|
return result;
|
||||||
|
|
||||||
return (ActionResult)methodDescriptor.MethodInfo.Invoke(this, methodParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static WebHandlerCachedItem FindControllerMethod(PluginManifest Manifest, Type Handler, string ActionName)
|
private class PluginParameterDescriptor : ParameterDescriptor
|
||||||
{
|
{
|
||||||
var descriptors = CacheWebHandler(Manifest, Handler);
|
private static readonly object[] emptyObjectArray = new object[0];
|
||||||
WebHandlerCachedItem method;
|
|
||||||
if (descriptors.TryGetValue(ActionName.ToLower(), out method))
|
|
||||||
return method; // Not Found
|
|
||||||
else
|
|
||||||
return null; // Not Found
|
|
||||||
}
|
|
||||||
private static object[] BuildMethodParameters(PluginManifest Manifest, Type Handler, MethodInfo methodInfo, string ActionName, Controller HostController)
|
|
||||||
{
|
|
||||||
var methodParams = methodInfo.GetParameters();
|
|
||||||
var result = new object[methodParams.Length];
|
|
||||||
|
|
||||||
for (int i = 0; i < methodParams.Length; i++)
|
private readonly PluginActionDescriptor actionDescriptor;
|
||||||
|
public override ActionDescriptor ActionDescriptor => actionDescriptor;
|
||||||
|
public override string ParameterName { get; }
|
||||||
|
public override Type ParameterType { get; }
|
||||||
|
public override ParameterBindingInfo BindingInfo { get; }
|
||||||
|
public override object DefaultValue { get; }
|
||||||
|
|
||||||
|
public PluginParameterDescriptor(PluginActionDescriptor actionDescriptor, string parameterName, Type parameterType, IModelBinder modelBinder, object defaultValue)
|
||||||
{
|
{
|
||||||
var methodParam = methodParams[i];
|
this.actionDescriptor = actionDescriptor;
|
||||||
|
ParameterName = parameterName;
|
||||||
|
ParameterType = parameterType;
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
BindingInfo = new PluginParameterBindingInfo(modelBinder);
|
||||||
|
}
|
||||||
|
|
||||||
Type parameterType = methodParam.ParameterType;
|
public object BindParameter(ControllerContext controllerContext)
|
||||||
IModelBinder modelBinder = ModelBinders.Binders.GetBinder(parameterType);
|
{
|
||||||
IValueProvider valueProvider = HostController.ValueProvider;
|
IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
|
||||||
string parameterName = methodParam.Name;
|
|
||||||
|
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext()
|
ModelBindingContext bindingContext = new ModelBindingContext()
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
|
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, ParameterType),
|
||||||
ModelName = parameterName,
|
ModelName = ParameterName,
|
||||||
ModelState = HostController.ViewData.ModelState,
|
ModelState = controllerContext.Controller.ViewData.ModelState,
|
||||||
PropertyFilter = (p) => true,
|
PropertyFilter = (p) => true,
|
||||||
ValueProvider = valueProvider
|
ValueProvider = valueProvider
|
||||||
};
|
};
|
||||||
|
|
||||||
var parameterValue = modelBinder.BindModel(HostController.ControllerContext, bindingContext);
|
var parameterValue = BindingInfo.Binder.BindModel(controllerContext.Controller.ControllerContext, bindingContext);
|
||||||
|
|
||||||
if (parameterValue == null && methodParam.HasDefaultValue)
|
if (parameterValue == null)
|
||||||
parameterValue = methodParam.DefaultValue;
|
parameterValue = DefaultValue;
|
||||||
|
|
||||||
result[i] = parameterValue;
|
return parameterValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
public override object[] GetCustomAttributes(bool inherit)
|
||||||
|
{
|
||||||
|
return emptyObjectArray;
|
||||||
|
}
|
||||||
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
return emptyObjectArray;
|
||||||
|
}
|
||||||
|
public override bool IsDefined(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PluginParameterBindingInfo : ParameterBindingInfo
|
||||||
|
{
|
||||||
|
private static readonly string[] emptyStringArray = new string[0];
|
||||||
|
public override IModelBinder Binder { get; }
|
||||||
|
public override ICollection<string> Exclude { get; } = emptyStringArray;
|
||||||
|
public override ICollection<string> Include { get; } = emptyStringArray;
|
||||||
|
|
||||||
|
public PluginParameterBindingInfo(IModelBinder modelBinder)
|
||||||
|
{
|
||||||
|
Binder = modelBinder;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Method Cache
|
private class PluginActionDescriptor : ActionDescriptor
|
||||||
private static Dictionary<Type, Dictionary<string, WebHandlerCachedItem>> WebHandlerCachedItems = new Dictionary<Type, Dictionary<string, WebHandlerCachedItem>>();
|
|
||||||
private static Dictionary<string, WebHandlerCachedItem> CacheWebHandler(PluginManifest Manifest, Type Handler)
|
|
||||||
{
|
{
|
||||||
Dictionary<string, WebHandlerCachedItem> result;
|
private readonly PluginControllerDescription controllerDescription;
|
||||||
|
private readonly MethodInfo methodInfo;
|
||||||
|
private readonly IAuthorizationFilter[] authorizationFilters;
|
||||||
|
private readonly PluginParameterDescriptor[] parameterDescriptors;
|
||||||
|
public override string UniqueId { get; }
|
||||||
|
public override string ActionName { get; }
|
||||||
|
public override ControllerDescriptor ControllerDescriptor => controllerDescription;
|
||||||
|
|
||||||
if (!WebHandlerCachedItems.TryGetValue(Handler, out result))
|
public PluginActionDescriptor(PluginControllerDescription controllerDescription, string methodName, MethodInfo methodInfo)
|
||||||
{
|
{
|
||||||
// Cache Miss
|
this.controllerDescription = controllerDescription;
|
||||||
result = new Dictionary<string, WebHandlerCachedItem>();
|
this.methodInfo = methodInfo;
|
||||||
var methods = Array.FindAll<MethodInfo>(Handler.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), mi => { return !mi.IsSpecialName && typeof(ActionResult).IsAssignableFrom(mi.ReturnType); });
|
UniqueId = $"{ControllerDescriptor.UniqueId}_{methodName}";
|
||||||
|
ActionName = methodName;
|
||||||
|
authorizationFilters = DiscoverAuthorizationFilters();
|
||||||
|
parameterDescriptors = DiscoverParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IAuthorizationFilter[] DiscoverAuthorizationFilters()
|
||||||
|
{
|
||||||
|
var filters = methodInfo.GetCustomAttributes<FilterAttribute>(true).OfType<IAuthorizationFilter>().ToList();
|
||||||
|
|
||||||
|
foreach (var authorizer in filters.OfType<DiscoAuthorizeBaseAttribute>())
|
||||||
|
authorizer.AuthorizeResource = string.Format("[Plugin]::{0}::{1}", controllerDescription.Manifest.Id, methodInfo.Name);
|
||||||
|
|
||||||
|
return filters.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginParameterDescriptor[] DiscoverParameters()
|
||||||
|
{
|
||||||
|
var methodParams = methodInfo.GetParameters();
|
||||||
|
var result = new List<PluginParameterDescriptor>(methodParams.Length);
|
||||||
|
|
||||||
|
for (int i = 0; i < methodParams.Length; i++)
|
||||||
|
{
|
||||||
|
var methodParam = methodParams[i];
|
||||||
|
|
||||||
|
IModelBinder modelBinder = ModelBinders.Binders.GetBinder(methodParam.ParameterType);
|
||||||
|
|
||||||
|
var defaultValue = (object)null;
|
||||||
|
if (methodParam.DefaultValue != DBNull.Value)
|
||||||
|
defaultValue = methodParam.DefaultValue;
|
||||||
|
|
||||||
|
result.Add(new PluginParameterDescriptor(this, methodParam.Name, methodParam.ParameterType, modelBinder, defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionResult Execute(PluginWebHandlerController pluginController, ControllerContext controllerContext)
|
||||||
|
{
|
||||||
|
var methodParameters = BuildMethodParameters(methodInfo, controllerContext.Controller);
|
||||||
|
|
||||||
|
return (ActionResult)methodInfo.Invoke(pluginController, methodParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object[] BuildMethodParameters(MethodInfo methodInfo, ControllerBase HostController)
|
||||||
|
{
|
||||||
|
var methodParams = methodInfo.GetParameters();
|
||||||
|
var result = new object[methodParams.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < methodParams.Length; i++)
|
||||||
|
{
|
||||||
|
var methodParam = methodParams[i];
|
||||||
|
|
||||||
|
Type parameterType = methodParam.ParameterType;
|
||||||
|
IModelBinder modelBinder = ModelBinders.Binders.GetBinder(parameterType);
|
||||||
|
IValueProvider valueProvider = HostController.ValueProvider;
|
||||||
|
string parameterName = methodParam.Name;
|
||||||
|
|
||||||
|
ModelBindingContext bindingContext = new ModelBindingContext()
|
||||||
|
{
|
||||||
|
FallbackToEmptyPrefix = true,
|
||||||
|
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
|
||||||
|
ModelName = parameterName,
|
||||||
|
ModelState = HostController.ViewData.ModelState,
|
||||||
|
PropertyFilter = (p) => true,
|
||||||
|
ValueProvider = valueProvider
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameterValue = modelBinder.BindModel(HostController.ControllerContext, bindingContext);
|
||||||
|
|
||||||
|
if (parameterValue == null && methodParam.HasDefaultValue)
|
||||||
|
parameterValue = methodParam.DefaultValue;
|
||||||
|
|
||||||
|
result[i] = parameterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object[] BuildMethodParameters(ControllerContext controllerContext)
|
||||||
|
{
|
||||||
|
var parameters = new object[parameterDescriptors.Length];
|
||||||
|
for (int i = 0; i < parameterDescriptors.Length; i++)
|
||||||
|
{
|
||||||
|
parameters[i] = parameterDescriptors[i].BindParameter(controllerContext);
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ParameterDescriptor[] GetParameters()
|
||||||
|
{
|
||||||
|
return parameterDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IAuthorizationFilter> GetAuthorizationFilters => authorizationFilters;
|
||||||
|
|
||||||
|
public override object[] GetCustomAttributes(bool inherit)
|
||||||
|
{
|
||||||
|
return authorizationFilters;
|
||||||
|
}
|
||||||
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
var result = new List<IAuthorizationFilter>(authorizationFilters.Length);
|
||||||
|
foreach (var filter in authorizationFilters)
|
||||||
|
{
|
||||||
|
if (attributeType.IsAssignableFrom(filter.GetType()))
|
||||||
|
result.Add(filter);
|
||||||
|
}
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
public override bool IsDefined(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
foreach (var filter in authorizationFilters)
|
||||||
|
{
|
||||||
|
if (attributeType.IsAssignableFrom(filter.GetType()))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
|
||||||
|
{
|
||||||
|
return authorizationFilters.OfType<FilterAttribute>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PluginControllerDescription : ControllerDescriptor
|
||||||
|
{
|
||||||
|
private static readonly object[] emptyObjectArray = new object[0];
|
||||||
|
private readonly Dictionary<string, PluginActionDescriptor> actions;
|
||||||
|
public override string ControllerName { get; }
|
||||||
|
|
||||||
|
public PluginManifest Manifest { get; }
|
||||||
|
public override Type ControllerType { get; }
|
||||||
|
public override string UniqueId { get; }
|
||||||
|
|
||||||
|
public PluginControllerDescription(PluginManifest manifest, Type controllerType)
|
||||||
|
{
|
||||||
|
Manifest = manifest;
|
||||||
|
ControllerType = controllerType;
|
||||||
|
actions = DiscoverActions();
|
||||||
|
ControllerName = controllerType.Name;
|
||||||
|
UniqueId = $"DiscoPlugin_{manifest.Id}_{controllerType.Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, PluginActionDescriptor> DiscoverActions()
|
||||||
|
{
|
||||||
|
var actions = new Dictionary<string, PluginActionDescriptor>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var methods = Array.FindAll(ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), mi => { return !mi.IsSpecialName && typeof(ActionResult).IsAssignableFrom(mi.ReturnType); });
|
||||||
foreach (var method in methods)
|
foreach (var method in methods)
|
||||||
{
|
{
|
||||||
var authorizers = method.GetCustomAttributes<DiscoAuthorizeBaseAttribute>().ToArray();
|
actions.Add(method.Name, new PluginActionDescriptor(this, method.Name, method));
|
||||||
foreach (var authorizer in authorizers)
|
|
||||||
authorizer.AuthorizeResource = string.Format("[Plugin]::{0}::{1}", Manifest.Id, method.Name);
|
|
||||||
|
|
||||||
var item = new WebHandlerCachedItem()
|
|
||||||
{
|
|
||||||
Method = method.Name,
|
|
||||||
MethodInfo = method,
|
|
||||||
Authorizers = authorizers
|
|
||||||
};
|
|
||||||
result.Add(item.Method.ToLower(), item);
|
|
||||||
}
|
}
|
||||||
WebHandlerCachedItems[Handler] = result;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
|
||||||
}
|
{
|
||||||
private class WebHandlerCachedItem
|
if (actions.TryGetValue(actionName, out var action))
|
||||||
{
|
return action;
|
||||||
public string Method { get; set; }
|
else
|
||||||
public MethodInfo MethodInfo { get; set; }
|
return null;
|
||||||
public DiscoAuthorizeBaseAttribute[] Authorizers { get; set; }
|
}
|
||||||
|
|
||||||
|
public override ActionDescriptor[] GetCanonicalActions()
|
||||||
|
{
|
||||||
|
return actions.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object[] GetCustomAttributes(bool inherit)
|
||||||
|
{
|
||||||
|
return emptyObjectArray;
|
||||||
|
}
|
||||||
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
return emptyObjectArray;
|
||||||
|
}
|
||||||
|
public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<FilterAttribute>();
|
||||||
|
}
|
||||||
|
public override bool IsDefined(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,24 @@ namespace Disco.Services.Web
|
|||||||
{
|
{
|
||||||
public static class BundleExtensions
|
public static class BundleExtensions
|
||||||
{
|
{
|
||||||
public static void BundleDeferred(this HtmlHelper htmlHelper, string BundleUrl)
|
public static void BundleDeferred(this HttpContextBase httpContext, string BundleUrl)
|
||||||
{
|
{
|
||||||
// Ensure 'App-Relative' Url:
|
// Ensure 'App-Relative' Url:
|
||||||
BundleUrl = BundleUrl.StartsWith("~/") ? BundleUrl : (BundleUrl.StartsWith("/") ? string.Concat("~", BundleUrl) : string.Concat("~/", BundleUrl));
|
BundleUrl = BundleUrl.StartsWith("~/") ? BundleUrl : (BundleUrl.StartsWith("/") ? string.Concat("~", BundleUrl) : string.Concat("~/", BundleUrl));
|
||||||
|
|
||||||
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] as List<string>;
|
var deferredBundles = httpContext.Items[BundleTable.DeferredKey] as List<string>;
|
||||||
if (deferredBundles == null)
|
if (deferredBundles == null)
|
||||||
{
|
{
|
||||||
deferredBundles = new List<string>();
|
deferredBundles = new List<string>();
|
||||||
htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] = deferredBundles;
|
httpContext.Items[BundleTable.DeferredKey] = deferredBundles;
|
||||||
}
|
}
|
||||||
if (!deferredBundles.Contains(BundleUrl))
|
if (!deferredBundles.Contains(BundleUrl))
|
||||||
deferredBundles.Add(BundleUrl);
|
deferredBundles.Add(BundleUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void BundleDeferred(this HtmlHelper htmlHelper, string BundleUrl)
|
||||||
|
=> htmlHelper.ViewContext.HttpContext.BundleDeferred(BundleUrl);
|
||||||
|
|
||||||
public static HtmlString BundleRenderDeferred(this HtmlHelper htmlHelper)
|
public static HtmlString BundleRenderDeferred(this HtmlHelper htmlHelper)
|
||||||
{
|
{
|
||||||
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] as List<string>;
|
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] as List<string>;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Disco.Web.Areas.Config.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region Plugin Configuration
|
#region Plugin Configuration
|
||||||
[DiscoAuthorize(Claims.Config.Plugin.Configure), HttpPost]
|
[DiscoAuthorize(Claims.Config.Plugin.Configure), HttpPost, ValidateInput(false)]
|
||||||
public virtual ActionResult Configure(string PluginId, FormCollection form)
|
public virtual ActionResult Configure(string PluginId, FormCollection form)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(PluginId))
|
if (string.IsNullOrEmpty(PluginId))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/// <reference path="../Core/jquery-2.1.1.js" />
|
/// <reference path="../../Core/jquery-2.1.1.js" />
|
||||||
function DiscoExpressionEditor(host, validateUrl, expression) {
|
function DiscoExpressionEditor(host, validateUrl, expression) {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.hostDocument = null;
|
this.hostDocument = null;
|
||||||
|
|||||||
@@ -2,63 +2,71 @@ if (!document.DiscoFunctions) {
|
|||||||
document.DiscoFunctions = {};
|
document.DiscoFunctions = {};
|
||||||
}
|
}
|
||||||
if (!document.DiscoFunctions.PropertyChangeHelper) {
|
if (!document.DiscoFunctions.PropertyChangeHelper) {
|
||||||
document.DiscoFunctions.PropertyValue = function (PropertyField) {
|
document.DiscoFunctions.PropertyValue = function (propertyField) {
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'input' && PropertyField.attr('type') == 'checkbox') {
|
if (propertyField[0].nodeName.toLowerCase() == 'input' && propertyField.attr('type') == 'checkbox') {
|
||||||
return PropertyField.is(':checked');
|
return propertyField.is(':checked');
|
||||||
}
|
}
|
||||||
return PropertyField.val();
|
return propertyField.val();
|
||||||
};
|
};
|
||||||
document.DiscoFunctions.PropertyChangeHelper = function (PropertyField, FieldWatermark, UpdateUrl, UpdatePropertyName) {
|
document.DiscoFunctions.PropertyChangeHelper = function (propertyField, fieldWatermark, updateUrl, updatePropertyName, data) {
|
||||||
var fieldValue = document.DiscoFunctions.PropertyValue(PropertyField);
|
var fieldValue = document.DiscoFunctions.PropertyValue(propertyField);
|
||||||
var fieldChangeToken = null;
|
var fieldChangeToken = null;
|
||||||
var $ajaxSave = PropertyField.nextAll('.ajaxSave').first();
|
var $ajaxSave = propertyField.nextAll('.ajaxSave').first();
|
||||||
var $ajaxLoading = PropertyField.nextAll('.ajaxLoading').first();
|
var $ajaxLoading = propertyField.nextAll('.ajaxLoading').first();
|
||||||
var fieldChangeFunction = function () {
|
var fieldChangeFunction = function () {
|
||||||
$ajaxSave.hide();
|
$ajaxSave.hide();
|
||||||
var changedValue = document.DiscoFunctions.PropertyValue(PropertyField);
|
var changedValue = document.DiscoFunctions.PropertyValue(propertyField);
|
||||||
if (fieldValue != changedValue) {
|
if (fieldValue != changedValue) {
|
||||||
fieldValue = changedValue;
|
fieldValue = changedValue;
|
||||||
if (fieldChangeToken)
|
if (fieldChangeToken)
|
||||||
window.clearTimeout(fieldChangeToken);
|
window.clearTimeout(fieldChangeToken);
|
||||||
fieldChangeToken = window.setTimeout(function () {
|
fieldChangeToken = window.setTimeout(function () {
|
||||||
$ajaxLoading.show();
|
$ajaxLoading.show();
|
||||||
var data = {};
|
if (!data) {
|
||||||
data[UpdatePropertyName] = fieldValue;
|
data = {};
|
||||||
$.getJSON(UpdateUrl, data, function (response, result) {
|
}
|
||||||
|
data[updatePropertyName] = fieldValue;
|
||||||
|
|
||||||
|
$.getJSON(updateUrl, data, function (response, result) {
|
||||||
if (result != 'success' || response != 'OK') {
|
if (result != 'success' || response != 'OK') {
|
||||||
alert('Unable to change property "' + UpdatePropertyName + '":\n' + response);
|
alert('Unable to change property "' + updatePropertyName + '":\n' + response);
|
||||||
$ajaxLoading.hide();
|
$ajaxLoading.hide();
|
||||||
} else {
|
} else {
|
||||||
$ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow');
|
$ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow');
|
||||||
}
|
}
|
||||||
|
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||||
|
alert('Unable to change property "' + updatePropertyName + '":\n' + errorThrown);
|
||||||
|
$ajaxLoading.hide();
|
||||||
})
|
})
|
||||||
fieldChangeToken = null;
|
fieldChangeToken = null;
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'input' && PropertyField.attr('type') == 'checkbox') {
|
if (propertyField[0].nodeName.toLowerCase() == 'input' && propertyField.attr('type') == 'checkbox') {
|
||||||
PropertyField.click(fieldChangeFunction);
|
propertyField.click(fieldChangeFunction);
|
||||||
} else {
|
} else {
|
||||||
PropertyField.change(fieldChangeFunction);
|
propertyField.change(fieldChangeFunction);
|
||||||
}
|
}
|
||||||
// For Input Text Boxes
|
// For Input Text Boxes
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'input' && PropertyField.attr('type') == 'text') {
|
if (propertyField[0].nodeName.toLowerCase() == 'input' && propertyField.attr('type') == 'text') {
|
||||||
PropertyField.keydown(function (e) {
|
propertyField.keydown(function (e) {
|
||||||
$ajaxSave.show();
|
$ajaxSave.show();
|
||||||
if (e.which == 13) {
|
if (e.which == 13) {
|
||||||
$(this).blur();
|
$(this).blur();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.watermark(FieldWatermark)
|
|
||||||
.blur(function () {
|
.blur(function () {
|
||||||
$ajaxSave.hide();
|
$ajaxSave.hide();
|
||||||
}).focus(function () {
|
}).focus(function () {
|
||||||
$(this).select();
|
$(this).select();
|
||||||
});
|
});
|
||||||
|
if (fieldWatermark) {
|
||||||
|
propertyField.watermark(fieldWatermark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// For TextAreas
|
// For TextAreas
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'textarea') {
|
if (propertyField[0].nodeName.toLowerCase() == 'textarea') {
|
||||||
PropertyField.keydown(function () {
|
propertyField.keydown(function () {
|
||||||
$ajaxSave.show();
|
$ajaxSave.show();
|
||||||
}).blur(function () {
|
}).blur(function () {
|
||||||
$ajaxSave.hide();
|
$ajaxSave.hide();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+29
-21
@@ -2,63 +2,71 @@
|
|||||||
document.DiscoFunctions = {};
|
document.DiscoFunctions = {};
|
||||||
}
|
}
|
||||||
if (!document.DiscoFunctions.PropertyChangeHelper) {
|
if (!document.DiscoFunctions.PropertyChangeHelper) {
|
||||||
document.DiscoFunctions.PropertyValue = function (PropertyField) {
|
document.DiscoFunctions.PropertyValue = function (propertyField) {
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'input' && PropertyField.attr('type') == 'checkbox') {
|
if (propertyField[0].nodeName.toLowerCase() == 'input' && propertyField.attr('type') == 'checkbox') {
|
||||||
return PropertyField.is(':checked');
|
return propertyField.is(':checked');
|
||||||
}
|
}
|
||||||
return PropertyField.val();
|
return propertyField.val();
|
||||||
};
|
};
|
||||||
document.DiscoFunctions.PropertyChangeHelper = function (PropertyField, FieldWatermark, UpdateUrl, UpdatePropertyName) {
|
document.DiscoFunctions.PropertyChangeHelper = function (propertyField, fieldWatermark, updateUrl, updatePropertyName, data) {
|
||||||
var fieldValue = document.DiscoFunctions.PropertyValue(PropertyField);
|
var fieldValue = document.DiscoFunctions.PropertyValue(propertyField);
|
||||||
var fieldChangeToken = null;
|
var fieldChangeToken = null;
|
||||||
var $ajaxSave = PropertyField.nextAll('.ajaxSave').first();
|
var $ajaxSave = propertyField.nextAll('.ajaxSave').first();
|
||||||
var $ajaxLoading = PropertyField.nextAll('.ajaxLoading').first();
|
var $ajaxLoading = propertyField.nextAll('.ajaxLoading').first();
|
||||||
var fieldChangeFunction = function () {
|
var fieldChangeFunction = function () {
|
||||||
$ajaxSave.hide();
|
$ajaxSave.hide();
|
||||||
var changedValue = document.DiscoFunctions.PropertyValue(PropertyField);
|
var changedValue = document.DiscoFunctions.PropertyValue(propertyField);
|
||||||
if (fieldValue != changedValue) {
|
if (fieldValue != changedValue) {
|
||||||
fieldValue = changedValue;
|
fieldValue = changedValue;
|
||||||
if (fieldChangeToken)
|
if (fieldChangeToken)
|
||||||
window.clearTimeout(fieldChangeToken);
|
window.clearTimeout(fieldChangeToken);
|
||||||
fieldChangeToken = window.setTimeout(function () {
|
fieldChangeToken = window.setTimeout(function () {
|
||||||
$ajaxLoading.show();
|
$ajaxLoading.show();
|
||||||
var data = {};
|
if (!data) {
|
||||||
data[UpdatePropertyName] = fieldValue;
|
data = {};
|
||||||
$.getJSON(UpdateUrl, data, function (response, result) {
|
}
|
||||||
|
data[updatePropertyName] = fieldValue;
|
||||||
|
|
||||||
|
$.getJSON(updateUrl, data, function (response, result) {
|
||||||
if (result != 'success' || response != 'OK') {
|
if (result != 'success' || response != 'OK') {
|
||||||
alert('Unable to change property "' + UpdatePropertyName + '":\n' + response);
|
alert('Unable to change property "' + updatePropertyName + '":\n' + response);
|
||||||
$ajaxLoading.hide();
|
$ajaxLoading.hide();
|
||||||
} else {
|
} else {
|
||||||
$ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow');
|
$ajaxLoading.hide().next('.ajaxOk').show().delay('fast').fadeOut('slow');
|
||||||
}
|
}
|
||||||
|
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||||
|
alert('Unable to change property "' + updatePropertyName + '":\n' + errorThrown);
|
||||||
|
$ajaxLoading.hide();
|
||||||
})
|
})
|
||||||
fieldChangeToken = null;
|
fieldChangeToken = null;
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'input' && PropertyField.attr('type') == 'checkbox') {
|
if (propertyField[0].nodeName.toLowerCase() == 'input' && propertyField.attr('type') == 'checkbox') {
|
||||||
PropertyField.click(fieldChangeFunction);
|
propertyField.click(fieldChangeFunction);
|
||||||
} else {
|
} else {
|
||||||
PropertyField.change(fieldChangeFunction);
|
propertyField.change(fieldChangeFunction);
|
||||||
}
|
}
|
||||||
// For Input Text Boxes
|
// For Input Text Boxes
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'input' && PropertyField.attr('type') == 'text') {
|
if (propertyField[0].nodeName.toLowerCase() == 'input' && propertyField.attr('type') == 'text') {
|
||||||
PropertyField.keydown(function (e) {
|
propertyField.keydown(function (e) {
|
||||||
$ajaxSave.show();
|
$ajaxSave.show();
|
||||||
if (e.which == 13) {
|
if (e.which == 13) {
|
||||||
$(this).blur();
|
$(this).blur();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.watermark(FieldWatermark)
|
|
||||||
.blur(function () {
|
.blur(function () {
|
||||||
$ajaxSave.hide();
|
$ajaxSave.hide();
|
||||||
}).focus(function () {
|
}).focus(function () {
|
||||||
$(this).select();
|
$(this).select();
|
||||||
});
|
});
|
||||||
|
if (fieldWatermark) {
|
||||||
|
propertyField.watermark(fieldWatermark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// For TextAreas
|
// For TextAreas
|
||||||
if (PropertyField[0].nodeName.toLowerCase() == 'textarea') {
|
if (propertyField[0].nodeName.toLowerCase() == 'textarea') {
|
||||||
PropertyField.keydown(function () {
|
propertyField.keydown(function () {
|
||||||
$ajaxSave.show();
|
$ajaxSave.show();
|
||||||
}).blur(function () {
|
}).blur(function () {
|
||||||
$ajaxSave.hide();
|
$ajaxSave.hide();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/// <reference path="../Core/jquery-2.1.1.js" />
|
/// <reference path="../../Core/jquery-2.1.1.js" />
|
||||||
(function ($) {
|
(function ($) {
|
||||||
|
|
||||||
var checkboxBulkSelectMethods = {
|
var checkboxBulkSelectMethods = {
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ namespace Disco.Web.Controllers
|
|||||||
{
|
{
|
||||||
public partial class PluginWebHandlerController : Controller
|
public partial class PluginWebHandlerController : Controller
|
||||||
{
|
{
|
||||||
|
protected override void OnAuthorization(AuthorizationContext filterContext)
|
||||||
|
{
|
||||||
|
base.OnAuthorization(filterContext);
|
||||||
|
}
|
||||||
|
|
||||||
[OutputCache(Duration = 0, Location = System.Web.UI.OutputCacheLocation.None)]
|
[OutputCache(Duration = 0, Location = System.Web.UI.OutputCacheLocation.None)]
|
||||||
public virtual ActionResult Index(string PluginId, string PluginAction)
|
public virtual ActionResult Index(string PluginId, string PluginAction)
|
||||||
{
|
{
|
||||||
@@ -24,7 +29,6 @@ namespace Disco.Web.Controllers
|
|||||||
{
|
{
|
||||||
using (var pluginWebHandler = manifest.CreateWebHandler(this))
|
using (var pluginWebHandler = manifest.CreateWebHandler(this))
|
||||||
{
|
{
|
||||||
pluginWebHandler.OnActionExecuting();
|
|
||||||
return pluginWebHandler.ExecuteAction(PluginAction);
|
return pluginWebHandler.ExecuteAction(PluginAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.Web.Mvc;
|
using RazorGenerator.Mvc;
|
||||||
|
using System;
|
||||||
|
using System.Web.Mvc;
|
||||||
using System.Web.Routing;
|
using System.Web.Routing;
|
||||||
|
|
||||||
namespace Disco.Web.Extensions
|
namespace Disco.Web
|
||||||
{
|
{
|
||||||
public static class ControllerExtensions
|
public static class ControllerExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
public static ActionResult RedirectToAction(this Controller controller, ActionResult result, string urlFragment)
|
public static ActionResult RedirectToAction(this Controller controller, ActionResult result, string urlFragment)
|
||||||
{
|
{
|
||||||
var callInfo = result.GetT4MVCResult();
|
var callInfo = result.GetT4MVCResult();
|
||||||
@@ -24,5 +25,23 @@ namespace Disco.Web.Extensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] _viewFileNames = new string[] { "cshtml" };
|
||||||
|
public static ActionResult PrecompiledPartialView<ViewType>(this Controller controller, object model) where ViewType : WebViewPage
|
||||||
|
{
|
||||||
|
return PrecompiledPartialView(controller, typeof(ViewType), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ActionResult PrecompiledPartialView(this Controller controller, Type viewType, object model)
|
||||||
|
{
|
||||||
|
if (!typeof(WebViewPage).IsAssignableFrom(viewType))
|
||||||
|
throw new ArgumentException("ViewType must be assignable from WebViewPage", "viewType");
|
||||||
|
|
||||||
|
IView v = new PrecompiledMvcView(controller.Request.Path, viewType, false, _viewFileNames);
|
||||||
|
|
||||||
|
if (model != null)
|
||||||
|
controller.ViewData.Model = model;
|
||||||
|
|
||||||
|
return new PartialViewResult { View = v, ViewData = controller.ViewData, TempData = controller.TempData };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user