maintenance: plugin refactoring

This commit is contained in:
Gary Sharp
2022-12-04 13:40:16 +11:00
parent 05593a1466
commit 4f9f0fd0a8
12 changed files with 402 additions and 129 deletions
+1
View File
@@ -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;
} }
+15 -3
View File
@@ -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
@@ -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);
} }
} }
+22 -3
View File
@@ -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 };
}
} }
} }