Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored Action Filters #261

Merged
merged 8 commits into from
Nov 15, 2016
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ public class DotvvmRuntimeConfiguration
/// Gets filters that are applied for all requests.
/// </summary>
[JsonIgnore()]
public List<ActionFilterAttribute> GlobalFilters { get; private set; }
public List<IActionFilter> GlobalFilters { get; private set; }


/// <summary>
/// Initializes a new instance of the <see cref="DotvvmRuntimeConfiguration"/> class.
/// </summary>
public DotvvmRuntimeConfiguration()
{
GlobalFilters = new List<ActionFilterAttribute>();
GlobalFilters = new List<IActionFilter>();
}
}
}
62 changes: 17 additions & 45 deletions src/DotVVM.Framework/Hosting/DotvvmPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,6 @@ public class DotvvmPresenter : IDotvvmPresenter

public string ApplicationPath { get; }

///// <summary>
///// Initializes a new instance of the <see cref="DotvvmPresenter"/> class.
///// </summary>
//public DotvvmPresenter(
// IDotvvmViewBuilder dotvvmViewBuilder,
// IViewModelLoader viewModelLoader,
// IViewModelSerializer viewModelSerializer,
// IOutputRenderer outputRenderer,
// ICsrfProtector csrfProtector
//)
//{
// DotvvmViewBuilder = dotvvmViewBuilder;
// ViewModelLoader = viewModelLoader;
// ViewModelSerializer = viewModelSerializer;
// OutputRenderer = outputRenderer;
// CsrfProtector = csrfProtector;
//}

/// <summary>
/// Processes the request.
/// </summary>
Expand Down Expand Up @@ -113,8 +95,10 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context)
context.ViewModel = ViewModelLoader.InitializeViewModel(context, page);

// get action filters
var globalFilters = context.Configuration.Runtime.GlobalFilters.ToList();
var viewModelFilters = context.ViewModel.GetType().GetTypeInfo().GetCustomAttributes<ActionFilterAttribute>(true).ToList();
var viewModelFilters = ActionFilterHelper.GetActionFilters<IViewModelActionFilter>(context.ViewModel.GetType().GetTypeInfo());
viewModelFilters.AddRange(context.Configuration.Runtime.GlobalFilters.OfType<IViewModelActionFilter>());
var requestFilters = ActionFilterHelper.GetActionFilters<IRequestActionFilter>(context.ViewModel.GetType().GetTypeInfo());
foreach (var f in requestFilters) await f.OnPageLoadingAsync(context);

try
{
Expand All @@ -123,7 +107,7 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context)
page.DataContext = context.ViewModel;

// run OnViewModelCreated on action filters
foreach (var filter in globalFilters.Concat(viewModelFilters))
foreach (var filter in viewModelFilters)
{
await filter.OnViewModelCreatedAsync(context);
}
Expand Down Expand Up @@ -180,8 +164,9 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context)
if (actionInfo != null)
{
// get filters
var methodFilters = actionInfo.Binding.ActionFilters == null ? globalFilters.Concat(viewModelFilters).ToArray() :
globalFilters.Concat(viewModelFilters).Concat(actionInfo.Binding.ActionFilters).ToArray();
var methodFilters = context.Configuration.Runtime.GlobalFilters.OfType<ICommandActionFilter>()
.Concat(ActionFilterHelper.GetActionFilters<ICommandActionFilter>(context.ViewModel.GetType().GetTypeInfo()));
if (actionInfo.Binding.ActionFilters != null) methodFilters = methodFilters.Concat(actionInfo.Binding.ActionFilters);

await ExecuteCommand(actionInfo, context, methodFilters);
}
Expand All @@ -205,7 +190,7 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context)
}

// run OnResponseRendering on action filters
foreach (var filter in globalFilters.Concat(viewModelFilters))
foreach (var filter in viewModelFilters)
{
await filter.OnResponseRenderingAsync(context);
}
Expand All @@ -229,27 +214,15 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context)
{
ViewModelLoader.DisposeViewModel(context.ViewModel);
}

foreach (var f in requestFilters) await f.OnPageLoadedAsync(context);
}
catch (DotvvmInterruptRequestExecutionException)
{
throw;
}
catch (DotvvmHttpException)
{
throw;
}
catch (DotvvmControlException) when (!context.Configuration.Debug)
{
throw;
}
catch (DotvvmCompilationException) when (!context.Configuration.Debug)
{
throw;
}
catch (DotvvmInterruptRequestExecutionException) { throw; }
catch (DotvvmHttpException) { throw; }
catch (Exception ex)
{
// run OnPageException on action filters
foreach (var filter in globalFilters.Concat(viewModelFilters))
foreach (var filter in requestFilters)
{
await filter.OnPageExceptionAsync(context, ex);

Expand Down Expand Up @@ -293,9 +266,8 @@ public async Task ProcessStaticCommandRequest(IDotvvmRequestContext context)
IsControlCommand = false,
Action = () => methodInfo.Invoke(target, methodArguments)
};
var filters = context.Configuration.Runtime.GlobalFilters
.Concat(methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes<ActionFilterAttribute>())
.Concat(methodInfo.GetCustomAttributes<ActionFilterAttribute>())
var filters = context.Configuration.Runtime.GlobalFilters.OfType<ICommandActionFilter>()
.Concat(ActionFilterHelper.GetActionFilters<ICommandActionFilter>(methodInfo))
.ToArray();

var result = await ExecuteCommand(actionInfo, context, filters);
Expand All @@ -306,7 +278,7 @@ public async Task ProcessStaticCommandRequest(IDotvvmRequestContext context)
}
}

protected async Task<object> ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable<ActionFilterAttribute> methodFilters)
protected async Task<object> ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable<ICommandActionFilter> methodFilters)
{
// run OnCommandExecuting on action filters
foreach (var filter in methodFilters)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using DotVVM.Framework.Runtime.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace DotVVM.Framework.Hosting.Middlewares
Expand Down Expand Up @@ -61,14 +63,28 @@ public async Task<bool> Handle(IDotvvmRequestContext context)
context.Route = route;
context.Parameters = parameters;

var presenter = route.GetPresenter();
var filters = ActionFilterHelper.GetActionFilters<IRequestActionFilter>(presenter.GetType().GetTypeInfo());
filters.AddRange(context.Configuration.Runtime.GlobalFilters.OfType<IRequestActionFilter>());
try
{
await route.ProcessRequest(context);
foreach (var f in filters) await f.OnPageLoadingAsync(context);
await presenter.ProcessRequest(context);
foreach (var f in filters) await f.OnPageLoadedAsync(context);
}
catch (DotvvmInterruptRequestExecutionException)
{
// the response has already been generated, do nothing
}
catch(Exception exception)
{
foreach (var f in filters)
{
await f.OnPageExceptionAsync(context, exception);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is now called also for DotvvmControlException and DotvvmHttpException rethrown in DotvvmPresenter (L66, L221). Is this intended?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DotvvmPresenter rethrows only HttpException, DotvvmControlException should be handled like any other. I'll fix it.

if (context.IsCommandExceptionHandled) context.InterruptRequest();
Copy link
Contributor

@djanosik djanosik Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check IsPageExceptionHandled instead?

}
throw;
}
return true;
}
}
Expand Down
6 changes: 1 addition & 5 deletions src/DotVVM.Framework/Routing/DotvvmRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,6 @@ protected override string BuildUrlCore(Dictionary<string, object> values)
/// <summary>
/// Processes the request.
/// </summary>
public override Task ProcessRequest(IDotvvmRequestContext context)
{
context.Presenter = presenterFactory();
return context.Presenter.ProcessRequest(context);
}
public override IDotvvmPresenter GetPresenter() => presenterFactory();
}
}
6 changes: 1 addition & 5 deletions src/DotVVM.Framework/Routing/RouteBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ public string BuildUrl(IDictionary<string, object> routeValues)
/// <remarks>The default values are already included in the <paramref name="values"/> collection.</remarks>
protected abstract string BuildUrlCore(Dictionary<string, object> values);





/// <summary>
/// Adds or updates the parameter collection with the specified values from the anonymous object.
/// </summary>
Expand Down Expand Up @@ -186,7 +182,7 @@ public static void AddOrUpdateParameterCollection(IDictionary<string, object> ta
/// <summary>
/// Processes the request.
/// </summary>
public abstract Task ProcessRequest(IDotvvmRequestContext context);
public abstract IDotvvmPresenter GetPresenter();

}
}
33 changes: 27 additions & 6 deletions src/DotVVM.Framework/Runtime/Filters/ActionFilterAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
using System;
using System.Threading.Tasks;
using DotVVM.Framework.Hosting;
using DotVVM.Framework.Utils;

namespace DotVVM.Framework.Runtime.Filters
{
/// <summary>
/// Allows to add custom logic before and after a command is executed on a ViewModel.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public abstract class ActionFilterAttribute : Attribute
public abstract class ActionFilterAttribute : Attribute, IRequestActionFilter, ICommandActionFilter, IViewModelActionFilter
{
/// <summary>
/// Called after the viewmodel object is created.
/// </summary>
protected internal virtual Task OnViewModelCreatedAsync(IDotvvmRequestContext context)
=> Task.FromResult(0);
=> TaskUtils.GetCompletedTask();

/// <summary>
/// Called before the command is executed.
/// </summary>
protected internal virtual Task OnCommandExecutingAsync(IDotvvmRequestContext context, ActionInfo actionInfo)
=> Task.FromResult(0);
=> TaskUtils.GetCompletedTask();

/// <summary>
/// Called after the command is executed.
/// </summary>
protected internal virtual Task OnCommandExecutedAsync(IDotvvmRequestContext context, ActionInfo actionInfo, Exception exception)
=> Task.FromResult(0);
=> TaskUtils.GetCompletedTask();

/// <summary>
/// Called before the response is rendered.
/// </summary>
protected internal virtual Task OnResponseRenderingAsync(IDotvvmRequestContext context)
=> Task.FromResult(0);
=> TaskUtils.GetCompletedTask();

/// <summary>
/// Called when an exception occurs during the processing of the page events.
/// </summary>
protected internal virtual Task OnPageExceptionAsync(IDotvvmRequestContext context, Exception exception)
=> Task.FromResult(0);
=> TaskUtils.GetCompletedTask();

/// <summary>
/// Called before page is processed.
/// </summary>
protected internal virtual Task OnPageLoadingAsync(IDotvvmRequestContext context)
=> TaskUtils.GetCompletedTask();

/// <summary>
/// Called after page is processed and ready to be sent to client.
/// </summary>
protected internal virtual Task OnPageLoadedAsync(IDotvvmRequestContext context)
=> TaskUtils.GetCompletedTask();

Task IRequestActionFilter.OnPageExceptionAsync(IDotvvmRequestContext context, Exception exception) => OnPageExceptionAsync(context, exception);
Task ICommandActionFilter.OnCommandExecutingAsync(IDotvvmRequestContext context, ActionInfo actionInfo) => OnCommandExecutingAsync(context, actionInfo);
Task ICommandActionFilter.OnCommandExecutedAsync(IDotvvmRequestContext context, ActionInfo actionInfo, Exception exception) => OnCommandExecutedAsync(context, actionInfo, exception);
Task IViewModelActionFilter.OnViewModelCreatedAsync(IDotvvmRequestContext context) => OnViewModelCreatedAsync(context);
Task IViewModelActionFilter.OnResponseRenderingAsync(IDotvvmRequestContext context) => OnResponseRenderingAsync(context);
Task IRequestActionFilter.OnPageLoadingAsync(IDotvvmRequestContext context) => OnPageLoadingAsync(context);
Task IRequestActionFilter.OnPageLoadedAsync(IDotvvmRequestContext context) => OnPageLoadedAsync(context);
}
}
22 changes: 22 additions & 0 deletions src/DotVVM.Framework/Runtime/Filters/ActionFilterHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using DotVVM.Framework.Utils;

namespace DotVVM.Framework.Runtime.Filters
{
public static class ActionFilterHelper
{
public static List<T> GetActionFilters<T>(MemberInfo memberInfo, bool includeParents = true)
{
var result = new List<T>();
do
{
result.AddRange(memberInfo.CastTo<ICustomAttributeProvider>().GetCustomAttributes<T>());
} while (includeParents && (memberInfo = memberInfo.DeclaringType?.GetTypeInfo()) != null);
return result;
}
}
}
11 changes: 11 additions & 0 deletions src/DotVVM.Framework/Runtime/Filters/IActionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotVVM.Framework.Runtime.Filters
{
public interface IActionFilter
{
}
}
21 changes: 21 additions & 0 deletions src/DotVVM.Framework/Runtime/Filters/ICommandActionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using DotVVM.Framework.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotVVM.Framework.Runtime.Filters
{
public interface ICommandActionFilter
{
/// <summary>
/// Called before the command is executed.
/// </summary>
Task OnCommandExecutingAsync(IDotvvmRequestContext context, ActionInfo actionInfo);

/// <summary>
/// Called after the command is executed.
/// </summary>
Task OnCommandExecutedAsync(IDotvvmRequestContext context, ActionInfo actionInfo, Exception exception);
}
}
24 changes: 24 additions & 0 deletions src/DotVVM.Framework/Runtime/Filters/IRequestActionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using DotVVM.Framework.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotVVM.Framework.Runtime.Filters
{
public interface IRequestActionFilter: IActionFilter
{
/// <summary>
/// Called before page is processed.
/// </summary>
Task OnPageLoadingAsync(IDotvvmRequestContext context);
/// <summary>
/// Called after page is processed and ready to be sent to client.
/// </summary>
Task OnPageLoadedAsync(IDotvvmRequestContext context);
/// <summary>
/// Called when an exception occurs during the processing of the page.
/// </summary>
Task OnPageExceptionAsync(IDotvvmRequestContext context, Exception exception);
}
}
20 changes: 20 additions & 0 deletions src/DotVVM.Framework/Runtime/Filters/IViewModelActionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using DotVVM.Framework.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotVVM.Framework.Runtime.Filters
{
public interface IViewModelActionFilter
{
/// <summary>
/// Called after the viewmodel object is created.
/// </summary>
Task OnViewModelCreatedAsync(IDotvvmRequestContext context);
/// <summary>
/// Called before the response is rendered.
/// </summary>
Task OnResponseRenderingAsync(IDotvvmRequestContext context);
}
}