SimpleInjector Web API Attribute Filter Won't Return Response - asp.net-mvc

I have followed this tutorial in order to inject dependencies into a custom filter via an method attribute.
https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=98
Everything looks like its working, it hits the filter and calls the OnActionExecuting() method.
The problem I'm having is that when the user isn't authenticated, I want to return a custom response via context.Response, but it just carries on into the action result as-if the filter wasn't there.
Below are the various pieces of code taken from the above URL, plus my custom filter.
[Route("api/user/create")]
[AuthoriseRequest("Can_Create_User")]
[System.Web.Http.HttpPost]
.
public class AuthoriseRequest : Attribute
{
public string PermissionName { get; set; }
public AuthoriseRequest(string permissionName)
{
PermissionName = permissionName;
}
}
.
public class AuthoriseRequestActionFilter : IActionFilter<AuthoriseRequest>
{ ... }
.
public void OnActionExecuting(AuthoriseRequest request, HttpActionContext context)
{
var createUserRequest = (CreateUserRequest)context.ActionArguments["createUserRequest"];
var validator = _requestValidator.Validate(createUserRequest);
if (validator.IsValid())
{
var isAuthenticated =
_authenticationManager.AuthenticateRequest(...);
if (!isAuthenticated)
{
//THIS DOESN'T SET THE RESPONSE
var unauthorisedRequestResponse = new APIResponse<string>(null, false, validator.ToString());
context.Response = context.Request.CreateResponse(
HttpStatusCode.OK,
unauthorisedRequestResponse);
}
}
}
.
public sealed class ActionFilterDispatcher : IActionFilter
{
private readonly Func<Type, IEnumerable> container;
public ActionFilterDispatcher(Func<Type, IEnumerable> container)
{
this.container = container;
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext context,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var descriptor = context.ActionDescriptor;
var attributes = descriptor.ControllerDescriptor.GetCustomAttributes<Attribute>(true)
.Concat(descriptor.GetCustomAttributes<Attribute>(true));
foreach (var attribute in attributes)
{
Type filterType = typeof(IActionFilter<>).MakeGenericType(attribute.GetType());
IEnumerable filters = this.container.Invoke(filterType);
foreach (dynamic actionFilter in filters)
{
actionFilter.OnActionExecuting((dynamic)attribute, context);
}
}
return continuation();
}
public bool AllowMultiple { get { return true; } }
}
.
public static void RegisterGlobalFilters(GlobalFilterCollection filters, Container container)
{
GlobalConfiguration.Configuration.Filters.Add(new ActionFilterDispatcher(container.GetAllInstances));
container.RegisterCollection(typeof(IActionFilter<>), typeof(IActionFilter<>).Assembly);
filters.Add(new HandleErrorAttribute());
}
Thanks.

Related

How to get the ActionDescription UniqueId from within OnResultExecuted

In ASP.NET MVC, does anyone know a trick to access the ActionDescriptor.UniqueId from within OnResultExecuted? I need to pass information from OnActionExecuting to OnResultExecuted in a way that will work if multiple actions are executed during the one HttpRequest.
For example:
private Dictionary<string,Foo> _foos
{
get { return HttpContext.Current.Items["foos"] as Dictionary<string,Foo>; }
set { HttpContext.Current.Items["foos"] = value; }
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var foos = _foos;
foos[context.ActionDescriptor.UniqueId] = new Foo();
_foos = foos;
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var actionUniqueId = ????
var foo = _foos[actionUniqueId]
}
You can custom the FilterAttributeFilterProvider and ActionFilterAttribute to implement it.
First you can create a filter that inherit the ActionFilterAttribute and contains ActionDescriptor property:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public ActionDescriptor ActionDescriptor { get; set; }
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var actionUniqueId = ActionDescriptor.UniqueId;
//code..
}
}
Then you need create a filter provider inherit the FilterAttributeFilterProvider and override the GetFilters method:
public class MyFilterProvider : FilterAttributeFilterProvider
{
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (controllerContext.Controller != null)
{
foreach (FilterAttribute attr in GetControllerAttributes(controllerContext, actionDescriptor))
{
var myAttr = attr as MyActionFilterAttribute;
if (myAttr != null)
{
myAttr.ActionDescriptor = actionDescriptor;
}
yield return new Filter(attr, FilterScope.Controller, order: null);
}
foreach (FilterAttribute attr in GetActionAttributes(controllerContext, actionDescriptor))
{
var myAttr = attr as MyActionFilterAttribute;
if (myAttr != null)
{
myAttr.ActionDescriptor = actionDescriptor;
}
yield return new Filter(attr, FilterScope.Action, order: null);
}
}
}
}
You can see at the GetFilters method, We set the ActionDescriptor property if the filter type is MyActionFilterAttribute.
Finally, At Global.asax, you need use MyFilterProvider instance to replace the FilterAttributeFilterProvider instance in Providers collection:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//replace the FilterAttributeFilterProvider in providers collection
for (int i = 0; i < FilterProviders.Providers.Count; i++)
{
if (FilterProviders.Providers[i] is FilterAttributeFilterProvider)
{
FilterProviders.Providers[i] = new MyFilterProvider();
break;
}
}
//other global init code...
}
}

Including a MVC View or string in a javascript bundle using Bundling framework

I'm trying to bundle several javascript files together, but I also need to include a variable from app.config in the js.
My thought was to use a Controller to return a string to set the variable, so going to
~/Javascript/Index would return var foo = "bar"; This works fine.
But when I try to build a bundle, the static files are being included, but the string (or view) isn't showing up. In looking around I found that the Optimizing framework was limited to static files up until 1.1 when support for VirtualPathProviders was implemented.
I upgraded to the latest package, but I can't find any information on how to get a mix of static files and ones generated by a Controller/View to bundle. I guess I just want to use the MVC path provider?
My other thought was to try to use the bundling engine to build a string of the bundled static files and then just append my string and return it all to the browser. But, I can't find a method that allows me to use the bundling engine to return a result of the bundling process.
Integration of dynamic content into the bundling process requires the following steps:
Writing the logic that requests / builds the required content. Generating content from Controller directly requires a bit of work:
public static class ControllerActionHelper
{
public static string RenderControllerActionToString(string virtualPath)
{
HttpContext httpContext = CreateHttpContext(virtualPath);
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(httpContext);
RequestContext httpResponse = new RequestContext()
{
HttpContext = httpContextWrapper,
RouteData = RouteTable.Routes.GetRouteData(httpContextWrapper)
};
// Set HttpContext.Current if RenderActionToString is called outside of a request
if (HttpContext.Current == null)
{
HttpContext.Current = httpContext;
}
IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
IController controller = controllerFactory.CreateController(httpResponse,
httpResponse.RouteData.GetRequiredString("controller"));
controller.Execute(httpResponse);
return httpResponse.HttpContext.Response.Output.ToString();
}
private static HttpContext CreateHttpContext(string virtualPath)
{
HttpRequest httpRequest = new HttpRequest(string.Empty, ToDummyAbsoluteUrl(virtualPath), string.Empty);
HttpResponse httpResponse = new HttpResponse(new StringWriter());
return new HttpContext(httpRequest, httpResponse);
}
private static string ToDummyAbsoluteUrl(string virtualPath)
{
return string.Format("http://dummy.net{0}", VirtualPathUtility.ToAbsolute(virtualPath));
}
}
Implement a virtual path provider that wraps the existing one and intercept all virtual paths that should deliver the dynamic content.
public class ControllerActionVirtualPathProvider : VirtualPathProvider
{
public ControllerActionVirtualPathProvider(VirtualPathProvider virtualPathProvider)
{
// Wrap an existing virtual path provider
VirtualPathProvider = virtualPathProvider;
}
protected VirtualPathProvider VirtualPathProvider { get; set; }
public override string CombineVirtualPaths(string basePath, string relativePath)
{
return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath);
}
public override bool DirectoryExists(string virtualDir)
{
return VirtualPathProvider.DirectoryExists(virtualDir);
}
public override bool FileExists(string virtualPath)
{
if (ControllerActionHelper.IsControllerActionRoute(virtualPath))
{
return true;
}
return VirtualPathProvider.FileExists(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies,
DateTime utcStart)
{
AggregateCacheDependency aggregateCacheDependency = new AggregateCacheDependency();
List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
// Create CacheDependencies for our virtual Controller Action paths
foreach (string virtualPathDependency in virtualPathDependenciesCopy.ToList())
{
if (ControllerActionHelper.IsControllerActionRoute(virtualPathDependency))
{
aggregateCacheDependency.Add(new ControllerActionCacheDependency(virtualPathDependency));
virtualPathDependenciesCopy.Remove(virtualPathDependency);
}
}
// Aggregate them with the base cache dependency for virtual file paths
aggregateCacheDependency.Add(VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy,
utcStart));
return aggregateCacheDependency;
}
public override string GetCacheKey(string virtualPath)
{
return VirtualPathProvider.GetCacheKey(virtualPath);
}
public override VirtualDirectory GetDirectory(string virtualDir)
{
return VirtualPathProvider.GetDirectory(virtualDir);
}
public override VirtualFile GetFile(string virtualPath)
{
if (ControllerActionHelper.IsControllerActionRoute(virtualPath))
{
return new ControllerActionVirtualFile(virtualPath,
new MemoryStream(Encoding.Default.GetBytes(ControllerActionHelper.RenderControllerActionToString(virtualPath))));
}
return VirtualPathProvider.GetFile(virtualPath);
}
public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
{
return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies);
}
public override object InitializeLifetimeService()
{
return VirtualPathProvider.InitializeLifetimeService();
}
}
public class ControllerActionVirtualFile : VirtualFile
{
public CustomVirtualFile (string virtualPath, Stream stream)
: base(virtualPath)
{
Stream = stream;
}
public Stream Stream { get; private set; }
public override Stream Open()
{
return Stream;
}
}
You also have to implement CacheDependency if you need it:
public class ControllerActionCacheDependency : CacheDependency
{
public ControllerActionCacheDependency(string virtualPath, int actualizationTime = 10000)
{
VirtualPath = virtualPath;
LastContent = GetContentFromControllerAction();
Timer = new Timer(CheckDependencyCallback, this, actualizationTime, actualizationTime);
}
private string LastContent { get; set; }
private Timer Timer { get; set; }
private string VirtualPath { get; set; }
protected override void DependencyDispose()
{
if (Timer != null)
{
Timer.Dispose();
}
base.DependencyDispose();
}
private void CheckDependencyCallback(object sender)
{
if (Monitor.TryEnter(Timer))
{
try
{
string contentFromAction = GetContentFromControllerAction();
if (contentFromAction != LastContent)
{
LastContent = contentFromAction;
NotifyDependencyChanged(sender, EventArgs.Empty);
}
}
finally
{
Monitor.Exit(Timer);
}
}
}
private string GetContentFromControllerAction()
{
return ControllerActionHelper.RenderControllerActionToString(VirtualPath);
}
}
Register your virtual path provider:
public static void RegisterBundles(BundleCollection bundles)
{
// Set the virtual path provider
BundleTable.VirtualPathProvider = new ControllerActionVirtualPathProvider(BundleTable.VirtualPathProvider);
bundles.Add(new Bundle("~/bundle")
.Include("~/Content/static.js")
.Include("~/JavaScript/Route1")
.Include("~/JavaScript/Route2"));
}
Optional: Add Intellisense support to your views. Use <script> tags within your View and let them be removed by a custom ViewResult:
public class DynamicContentViewResult : ViewResult
{
public DynamicContentViewResult()
{
StripTags = false;
}
public string ContentType { get; set; }
public bool StripTags { get; set; }
public string TagName { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (string.IsNullOrEmpty(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}
ViewEngineResult result = null;
if (View == null)
{
result = FindView(context);
View = result.View;
}
string viewResult;
using (StringWriter viewContentWriter = new StringWriter())
{
ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, viewContentWriter);
View.Render(viewContext, viewContentWriter);
if (result != null)
{
result.ViewEngine.ReleaseView(context, View);
}
viewResult = viewContentWriter.ToString();
// Strip Tags
if (StripTags)
{
string regex = string.Format("<{0}[^>]*>(.*?)</{0}>", TagName);
Match res = Regex.Match(viewResult, regex,
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline);
if (res.Success && res.Groups.Count > 1)
{
viewResult = res.Groups[1].Value;
}
else
{
throw new InvalidProgramException(
string.Format("Dynamic content produced by View '{0}' expected to be wrapped in '{1}' tag.", ViewName, TagName));
}
}
}
context.HttpContext.Response.ContentType = ContentType;
context.HttpContext.Response.Output.Write(viewResult);
}
}
Use an extension method or add an helper function to your controller:
public static DynamicContentViewResult JavaScriptView(this Controller controller, string viewName, string masterName, object model)
{
if (model != null)
{
controller.ViewData.Model = model;
}
return new DynamicContentViewResult
{
ViewName = viewName,
MasterName = masterName,
ViewData = controller.ViewData,
TempData = controller.TempData,
ViewEngineCollection = controller.ViewEngineCollection,
ContentType = "text/javascript",
TagName = "script",
StripTags = true
};
}
The steps are similiar for other type of dynamic contents. See Bundling and Minification and Embedded Resources for example.
I added a proof of concept repository to GitHub if you want to try it out.

Web API Filter stopped other filters from being hit

When I add the HttpHeader filter, the other filters stop working. Im not sure why.
BaseApiController.cs
[ExceptionHandling, ApiValidation, HttpHeader("X-Robots-Tag", "noindex, nofollow")]
public abstract class BaseApiController : System.Web.Http.ApiController
{
HttpHeaderFilter.cs
namespace Tournaments.Models.Mvc.Filters
{
public class HttpHeaderAttribute : System.Web.Http.Filters.FilterAttribute
{
public string Name { get; set; }
public string Value { get; set; }
public HttpHeaderAttribute(string name, string value)
{
Name = name;
Value = value;
}
public override bool AllowMultiple
{
get { return true; }
}
}
public class HttpHeaderFilter : System.Web.Http.Filters.IActionFilter
{
private readonly string _name;
private readonly string _value;
public HttpHeaderFilter(string name, string value)
{
_name = name;
_value = value;
}
public bool AllowMultiple
{
get { return true; }
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
return continuation().ContinueWith<HttpResponseMessage>((tsk) =>
{
HttpResponseMessage response = tsk.Result;
response.Headers.Add(_name, _value);
return response;
}, TaskContinuationOptions.OnlyOnRanToCompletion);;
}
}
}
Global.asax
protected override IKernel CreateKernel()
{
return Bootstrap.Configure((kernel) =>
{
kernel.Bind<IUnitOfWork>().To<UnitOfWork>();
kernel.Bind<IDatabaseFactory>().To<DatabaseFactory>().InRequestScope();
//kernel.Bind<IDatabaseFactory>().To<DatabaseFactory>().InScope(q => HttpContext.Current ?? StandardScopeCallbacks.Thread(q));
kernel.Bind<IEmailService>().To<EmailService>().WithConstructorArgument("templates", emailTemplates);
kernel.Bind<IPaymentMethod>().To<AuthorizeNetProvider>().Named("AuthorizeNet");
kernel.Bind<IPaymentMethod>().To<StripeProvider>().Named("Stripe");
kernel.Bind<List<IPaymentMethod>>().ToConstant(kernel.GetAll<IPaymentMethod>().ToList());
ServiceBindings.Register(kernel);
RepositoryBindings.Register(kernel);
ValidationBindings.Register(kernel);
kernel.BindHttpFilter<ApiValidationFilter>(System.Web.Http.Filters.FilterScope.Action)
.WhenControllerHas<ApiValidationAttribute>();
kernel.BindHttpFilter<HttpHeaderFilter>(System.Web.Http.Filters.FilterScope.Controller)
.WhenControllerHas<HttpHeaderAttribute>()
.WithConstructorArgumentFromControllerAttribute<HttpHeaderAttribute>("name", q => q.Name)
.WithConstructorArgumentFromControllerAttribute<HttpHeaderAttribute>("value", q => q.Value);
GlobalConfiguration.Configuration.Filters.Add(new ExceptionHandlingAttribute());
GlobalConfiguration.Configuration.DependencyResolver = new Common.Ioc.NinjectDependencyResolver(kernel);
GlobalConfiguration.Configuration.Formatters.Add(new CustomXmlFormatter());
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
});
}
I added this
TaskContinuationOptions.ExecuteSynchronously
Instead of
TaskContinuationOptions.OnlyOnRanToCompletion
and it works fine now.

ASP.NET bundling/minification: including dynamically generated Javascript

I have a site that dynamically generates Javascript. The generated code describes type-metadata and some server-side constants so that the clients can easily consume the server's services - so it's very cacheable.
The generated Javascript is served by an ASP.NET MVC controller; so it has a Uri; say ~/MyGeneratedJs.
I'd like to include this Javascript in a Javascript bundle with other static Javascript files (e.g. jQuery etc): so just like static files I want it to be referenced separately in debug mode and in minified form bundled with the other files in non-debug mode.
How can I include dynamically generated Javascript in a bundle?
With VirtualPathProviders this is now possible. Integration of dynamic content into the bundling process requires the following steps:
Writing the logic that requests / builds the required content. Generating content from Controller directly requires a bit of work:
public static class ControllerActionHelper
{
public static string RenderControllerActionToString(string virtualPath)
{
HttpContext httpContext = CreateHttpContext(virtualPath);
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(httpContext);
RequestContext httpResponse = new RequestContext()
{
HttpContext = httpContextWrapper,
RouteData = RouteTable.Routes.GetRouteData(httpContextWrapper)
};
// Set HttpContext.Current if RenderActionToString is called outside of a request
if (HttpContext.Current == null)
{
HttpContext.Current = httpContext;
}
IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
IController controller = controllerFactory.CreateController(httpResponse,
httpResponse.RouteData.GetRequiredString("controller"));
controller.Execute(httpResponse);
return httpResponse.HttpContext.Response.Output.ToString();
}
private static HttpContext CreateHttpContext(string virtualPath)
{
HttpRequest httpRequest = new HttpRequest(string.Empty, ToDummyAbsoluteUrl(virtualPath), string.Empty);
HttpResponse httpResponse = new HttpResponse(new StringWriter());
return new HttpContext(httpRequest, httpResponse);
}
private static string ToDummyAbsoluteUrl(string virtualPath)
{
return string.Format("http://dummy.net{0}", VirtualPathUtility.ToAbsolute(virtualPath));
}
}
Implement a virtual path provider that wraps the existing one and intercept all virtual paths that should deliver the dynamic content.
public class ControllerActionVirtualPathProvider : VirtualPathProvider
{
public ControllerActionVirtualPathProvider(VirtualPathProvider virtualPathProvider)
{
// Wrap an existing virtual path provider
VirtualPathProvider = virtualPathProvider;
}
protected VirtualPathProvider VirtualPathProvider { get; set; }
public override string CombineVirtualPaths(string basePath, string relativePath)
{
return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath);
}
public override bool DirectoryExists(string virtualDir)
{
return VirtualPathProvider.DirectoryExists(virtualDir);
}
public override bool FileExists(string virtualPath)
{
if (ControllerActionHelper.IsControllerActionRoute(virtualPath))
{
return true;
}
return VirtualPathProvider.FileExists(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies,
DateTime utcStart)
{
AggregateCacheDependency aggregateCacheDependency = new AggregateCacheDependency();
List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
// Create CacheDependencies for our virtual Controller Action paths
foreach (string virtualPathDependency in virtualPathDependenciesCopy.ToList())
{
if (ControllerActionHelper.IsControllerActionRoute(virtualPathDependency))
{
aggregateCacheDependency.Add(new ControllerActionCacheDependency(virtualPathDependency));
virtualPathDependenciesCopy.Remove(virtualPathDependency);
}
}
// Aggregate them with the base cache dependency for virtual file paths
aggregateCacheDependency.Add(VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy,
utcStart));
return aggregateCacheDependency;
}
public override string GetCacheKey(string virtualPath)
{
return VirtualPathProvider.GetCacheKey(virtualPath);
}
public override VirtualDirectory GetDirectory(string virtualDir)
{
return VirtualPathProvider.GetDirectory(virtualDir);
}
public override VirtualFile GetFile(string virtualPath)
{
if (ControllerActionHelper.IsControllerActionRoute(virtualPath))
{
return new ControllerActionVirtualFile(virtualPath,
new MemoryStream(Encoding.Default.GetBytes(ControllerActionHelper.RenderControllerActionToString(virtualPath))));
}
return VirtualPathProvider.GetFile(virtualPath);
}
public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
{
return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies);
}
public override object InitializeLifetimeService()
{
return VirtualPathProvider.InitializeLifetimeService();
}
}
public class ControllerActionVirtualFile : VirtualFile
{
public CustomVirtualFile (string virtualPath, Stream stream)
: base(virtualPath)
{
Stream = stream;
}
public Stream Stream { get; private set; }
public override Stream Open()
{
return Stream;
}
}
You also have to implement CacheDependency if you need it:
public class ControllerActionCacheDependency : CacheDependency
{
public ControllerActionCacheDependency(string virtualPath, int actualizationTime = 10000)
{
VirtualPath = virtualPath;
LastContent = GetContentFromControllerAction();
Timer = new Timer(CheckDependencyCallback, this, actualizationTime, actualizationTime);
}
private string LastContent { get; set; }
private Timer Timer { get; set; }
private string VirtualPath { get; set; }
protected override void DependencyDispose()
{
if (Timer != null)
{
Timer.Dispose();
}
base.DependencyDispose();
}
private void CheckDependencyCallback(object sender)
{
if (Monitor.TryEnter(Timer))
{
try
{
string contentFromAction = GetContentFromControllerAction();
if (contentFromAction != LastContent)
{
LastContent = contentFromAction;
NotifyDependencyChanged(sender, EventArgs.Empty);
}
}
finally
{
Monitor.Exit(Timer);
}
}
}
private string GetContentFromControllerAction()
{
return ControllerActionHelper.RenderControllerActionToString(VirtualPath);
}
}
Register your virtual path provider:
public static void RegisterBundles(BundleCollection bundles)
{
// Set the virtual path provider
BundleTable.VirtualPathProvider = new ControllerActionVirtualPathProvider(BundleTable.VirtualPathProvider);
bundles.Add(new Bundle("~/bundle")
.Include("~/Content/static.js")
.Include("~/JavaScript/Route1")
.Include("~/JavaScript/Route2"));
}
Optional: Add Intellisense support to your views. Use <script> tags within your View and let them be removed by a custom ViewResult:
public class DynamicContentViewResult : ViewResult
{
public DynamicContentViewResult()
{
StripTags = false;
}
public string ContentType { get; set; }
public bool StripTags { get; set; }
public string TagName { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (string.IsNullOrEmpty(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}
ViewEngineResult result = null;
if (View == null)
{
result = FindView(context);
View = result.View;
}
string viewResult;
using (StringWriter viewContentWriter = new StringWriter())
{
ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, viewContentWriter);
View.Render(viewContext, viewContentWriter);
if (result != null)
{
result.ViewEngine.ReleaseView(context, View);
}
viewResult = viewContentWriter.ToString();
// Strip Tags
if (StripTags)
{
string regex = string.Format("<{0}[^>]*>(.*?)</{0}>", TagName);
Match res = Regex.Match(viewResult, regex,
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline);
if (res.Success && res.Groups.Count > 1)
{
viewResult = res.Groups[1].Value;
}
else
{
throw new InvalidProgramException(
string.Format("Dynamic content produced by View '{0}' expected to be wrapped in '{1}' tag.", ViewName, TagName));
}
}
}
context.HttpContext.Response.ContentType = ContentType;
context.HttpContext.Response.Output.Write(viewResult);
}
}
Use an extension method or add an helper function to your controller:
public static DynamicContentViewResult JavaScriptView(this Controller controller, string viewName, string masterName, object model)
{
if (model != null)
{
controller.ViewData.Model = model;
}
return new DynamicContentViewResult
{
ViewName = viewName,
MasterName = masterName,
ViewData = controller.ViewData,
TempData = controller.TempData,
ViewEngineCollection = controller.ViewEngineCollection,
ContentType = "text/javascript",
TagName = "script",
StripTags = true
};
}
The steps are similiar for other type of dynamic contents. See Bundling and Minification and Embedded Resources for example.
I added a proof of concept repository to GitHub if you want to try it out.
Darin is right, currently bundling only works on static files. But if you can add a placeholder file with up to date content, bundling does setup file change notifications which will detect automatically when the placeholder file changes.
Also we are going to be moving to using VirtualPathProviders soon which might be a way to serve dynamically generated content.
Update: The 1.1-alpha1 release is out now which has support for VPP
This is not possible. Bundles work only with static files.

How to disable a global filter in ASP.Net MVC selectively

I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]

Resources