I'd like to use AOP to intercept calls to all methods within ASP.NET Controllers and ApiControllers.
Following http://structuremap.github.io/dynamic-interception/ I tried to get it to work as follows.
The interceptor at present does nothing much, but provide a way to see the method name and its attributes:
public class AuthorisationInterceptor : ISyncInterceptionBehavior
{
public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation)
{
var classType = methodInvocation.MethodInfo.DeclaringType;
var classAttributes = classType.Attributes;
string methodName = methodInvocation.MethodInfo.Name;
var methodAttributes = methodInvocation.MethodInfo.Attributes;
//var argument = methodInvocation.GetArgument("value");
return methodInvocation.InvokeNext();
}
}
The issue is how to attach it -- without getting errors.
I've tried a couple of different approaches, both raise the same type of error..
"Decorator Interceptor failed during object construction. Specified type is not an interface,Parameter name: interfaceToProxy"
The issue is that ASP.MVC is asking for the Controllers directly (eg: 'AboutController', and not 'IAboutController').
public class AppCoreControllerConvention : ICustomRegistrationConvention
{
public void ScanTypes(TypeSet types, Registry registry)
{
// Attach a policy to intercept all Controllers before attaching Controllers...but it raises error.
// "Decorator Interceptor failed during object construction. Specified type is not an interface,Parameter name: interfaceToProxy"
registry.Policies.Interceptors(
new DynamicProxyInterceptorPolicy(
x => (x.IsConcrete() | !x.IsOpenGeneric()) & (x.CanBeCastTo<Controller>() | x.CanBeCastTo<ApiController>()),
new IInterceptionBehavior[]
{
new AuthorisationInterceptor(),
new AuditingInterceptor()
}
));
// Now find all Controllers/ApiControllers:
var foundControllers = types.FindTypes(
TypeClassification.Concretes | TypeClassification.Closed)
.Where(x => x.CanBeCastTo<Controller>() | x.CanBeCastTo<ApiController>())
.ToArray();
// to register them with StructureMap as themselves (ie, no 'Use' statement):
foreach (var serviceType in foundControllers)
{
registry.For(serviceType).LifecycleIs(new UniquePerRequestLifecycle());
// Although when I tried use/fore, it also raised {"Specified type is not an interface\r\nParameter name: interfaceToProxy"}
// AttachBehaviour(registry, serviceType);
}
}
//private static void AttachBehaviour(Registry registry, Type serviceType)
//{
// var dynamicProxyInterceptorType = typeof(StructureMap.DynamicInterception.DynamicProxyInterceptor<>);
// var genericDynamicProxyInterceptorType = dynamicProxyInterceptorType.MakeGenericType(new[] { serviceType });
// var interceptorBehaviors = new StructureMap.DynamicInterception.IInterceptionBehavior[]
// {
// new AuthorisationInterceptor(),
// new AuditingInterceptor()
// };
// var args = new[] { interceptorBehaviors };
// // Create
// IInterceptor interceptor =
// (StructureMap.Building.Interception.IInterceptor)Activator.CreateInstance(
// genericDynamicProxyInterceptorType,
// (BindingFlags)0,
// null,
// args,
// null);
// // Attach interceptors to Service:
// registry.For(serviceType).Use(serviceType).InterceptWith(interceptor);
//}
}
I'm using:
<package id="StructureMap" version="4.5.1" targetFramework="net461" />
<package id="StructureMap.DynamicInterception" version="1.1.1" targetFramework="net461" />
<package id="StructureMap.MVC5" version="3.1.1.134" targetFramework="net461" />
<package id="structuremap.web" version="4.0.0.315" targetFramework="net461" />
<package id="StructureMap.WebApi2" version="3.0.4.125" targetFramework="net461" />
Thanks for any recommendation on how to proceed.
PS: I'm not sure if I exactly understood what https://stackoverflow.com/a/47582778/9314395 was recommending, but the following did not magically produce any interception:
registry.For<IController>().InterceptWith(new DynamicProxyInterceptor<IController>(new IInterceptionBehavior[]{new AuthorisationInterceptor()}));
Related
When an error occurs on my ASP.NET MVC Server (running on IIS), the server currently serves a static page. This is configured in the httpErrors element in my web.config, like so:
<httpErrors errorMode="Custom" existingResponse="Replace">
<error statusCode="404" path="404.htm" responseMode="File" />
<error statusCode="500" path="500.htm" responseMode="File" />
</httpErrors>
When inspecting the response from the server, I see a cache-control: private response header. This is good, though I want to control how long this page is cached. How can I add max-age=x to this cache-control header?
If I understand your problem statement correctly your main goal was to have control over max-age, rather than fancy <customErrors> setup. It seems logical to try and control the header from an Action Filter.
In web.config
I've got this system.web setup:
<system.web>
<compilation debug="true" targetFramework="4.6.1"/> <!-- framework version for reference -->
<httpRuntime targetFramework="4.6.1"/>
<customErrors mode="On">
</customErrors> <!-- I didn't try adding custom pages here, but this blog seem to have a solution: https://benfoster.io/blog/aspnet-mvc-custom-error-pages -->
</system.web>
In MaxAgeFilter.cs
public class MaxAgeFilter : ActionFilterAttribute, IResultFilter, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = (int)HttpStatusCode.InternalServerError;
if (filterContext.Exception is HttpException)
{
statusCode = (filterContext.Exception as HttpException).GetHttpCode();
}
else if (filterContext.Exception is UnauthorizedAccessException)
{
statusCode = (int)HttpStatusCode.Forbidden;
}
var result = CreateActionResult(filterContext, statusCode);
filterContext.Result = result;
// Prepare the response code.
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromSeconds(10)); // this requires a lot of extra plumbing which I suspect is necessary because if you were to rely on default error response - the cache will get overriden, see original SO answer: https://stackoverflow.com/questions/8144695/asp-net-mvc-custom-handleerror-filter-specify-view-based-on-exception-type
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromMinutes(10)); // this is easy - you just pass it to the current cache and magic works
base.OnResultExecuted(filterContext);
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
var viewName = SelectFirstView(ctx,
"~/Views/Shared/Error.cshtml",
"~/Views/Shared/Error.cshtml",
statusCodeName,
"Error");
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
protected string SelectFirstView(ControllerContext ctx, params string[] viewNames)
{
return viewNames.First(view => ViewExists(ctx, view));
}
protected bool ViewExists(ControllerContext ctx, string name)
{
var result = ViewEngines.Engines.FindView(ctx, name, null);
return result.View != null;
}
}
as you see, handling an exception basically requires rebuilding the whole Response. For this I pretty much took the code from this SO answer here
Finally, you decide whether you want this attribute on your controllers, actions or set up globally:
App_Start/FilterConfig.cs
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MaxAgeFilter());
}
}
If you're using custom error pages, you can set headers within the view. That way you set different cache lengths depending on which error page is being displayed, as you said.
#{
TimeSpan duration = new TimeSpan(0, 30, 0);
Response.Cache.SetMaxAge(duration);
}
I tried a lot of ways and it appear to work only when I set custom error page for 404 error.
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-Control" value="max-age:12300" />
</customHeaders>
</httpProtocol>
</system.webServer>
I am trying to integrate Ninject in my WebAPI 2 project but I am getting following error:
{
"message": "An error has occurred.",
"exceptionMessage": "An error occurred when trying to create a controller of type 'BrandController'. Make sure that the controller has a parameterless public constructor.",
"exceptionType": "System.InvalidOperationException",
"stackTrace": " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
"innerException": {
"message": "An error has occurred.",
"exceptionMessage": "Type 'ADAS.GoTango.WebApi.Controllers.BrandController' does not have a default constructor",
"exceptionType": "System.ArgumentException",
"stackTrace": " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}
My package config is:
<package id="Ninject" version="3.2.2.0" targetFramework="net45" />
<package id="Ninject.Web.Common" version="3.2.3.0" targetFramework="net45" />
<package id="Ninject.Web.Common.WebHost" version="3.2.3.0" targetFramework="net45" />
<package id="Ninject.Web.WebApi" version="3.2.4.0" targetFramework="net45" />
<package id="Ninject.Web.WebApi.WebHost" version="3.2.4.0" targetFramework="net45" />
<package id="Ninject.WebApi.DependencyResolver" version="0.1.4758.24814" targetFramework="net45" />
My code :
public class BrandController : BaseApiController
{
readonly BrandsBusiness _brandsBusiness;
public BrandController(BrandsBusiness brandsBusiness)
{
_brandsBusiness = brandsBusiness;
}
//public BrandController()
//{
// _brandsBusiness = new BrandsBusiness(new BrandEfStore());
//}
public IHttpActionResult Get()
{
try
{
var allActiveBrands = _brandsBusiness.GetAllActiveBrands();
return Ok(allActiveBrands);
}
catch (Exception exception)
{
Logger.Error(exception);
return InternalServerError();
}
}
}
and NinjectWebCommon.cs file is
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
RegisterServices(kernel);
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
var configuration = new HttpConfiguration();
kernel.Bind<DefaultModelValidatorProviders>().ToConstant(new DefaultModelValidatorProviders(configuration.Services.GetServices(typeof(ModelValidatorProvider)).Cast<ModelValidatorProvider>()));
kernel.Bind<BrandsBusiness>().ToSelf().InRequestScope();
kernel.Bind<IBrandManagement>().To<BrandEfStore>().InRequestScope();
}
I have alredy tried :
Parameterless constructor error with Ninject bindings in .NET Web Api 2.1
Ninject.ActivationException thrown only on first web request (WebAPI 2, OWIN 3, Ninject 3)
but none of them worked.
While I don't know Ninject, I'd imagine that you'll need to make sure that the CreateKernel method is called. You'd normally add an Application_Start method in your global.asax.
You may need to make the CreateKernel method internal or public in order to be able to call if from there.
I am developing an android app using Xamarin and for push notifications I am using PushSharp.
I am having some trouble with receiving push notifications while the app is not running (after a reboot for example). Here is the Service code:
[BroadcastReceiver(Permission=GCMConstants.PERMISSION_GCM_INTENTS)]
[IntentFilter(new string[] { GCMConstants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "com.xxx" })]
[IntentFilter(new string[] { GCMConstants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "com.xxx" })]
[IntentFilter(new string[] { GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "com.xxx" })]
[IntentFilter(new[] { Android.Content.Intent.ActionBootCompleted })]
public class PushHandlerBroadcastReceiver : PushHandlerBroadcastReceiverBase<PushHandlerService>
{
//IMPORTANT: Change this to your own Sender ID!
//The SENDER_ID is your Google API Console App Project ID.
// Be sure to get the right Project ID from your Google APIs Console. It's not the named project ID that appears in the Overview,
// but instead the numeric project id in the url: eg: https://code.google.com/apis/console/?pli=1#project:785671162406:overview
// where 785671162406 is the project id, which is the SENDER_ID to use!
public static string[] SENDER_IDS = new string[] {"1234"};
public const string TAG = "PushSharp-GCM";
}
And here is the appManifest that is created:
<receiver android:permission="com.google.android.c2dm.permission.SEND" android:name="xxx.PushHandlerBroadcastReceiver">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.xxx" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.xxx" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gcm.intent.RETRY" />
<category android:name="com.xxx" />
</intent-filter>
</receiver>
<service android:name="xxx.PushHandlerService" />
My service code is very basic:
[Service] //Must use the service tag
public class PushHandlerService : PushHandlerServiceBase
{
public PushHandlerService () : base (PushHandlerBroadcastReceiver.SENDER_IDS)
{
}
protected override void OnRegistered (Context context, string registrationId)
{
...
}
protected override void OnUnRegistered (Context context, string registrationId)
{
...
}
protected override void OnMessage (Context context, Intent intent)
{
...
}
protected override bool OnRecoverableError (Context context, string errorId)
{
...
}
protected override void OnError (Context context, string errorId)
{
...
}
void createNotification (string title, string desc, Intent intent)
{
...
}
}
Am I missing something? why is the service not started once the phone is rebooted. Should I be doing something in the broadcast receiver? Should I register to the push notifications in the service constructor (to handle the case where the app is not started yet)?
If your service does not start on reboot you can add a BroadcastReceiver to your project which starts it:
[BroadcastReceiver]
[IntentFilter(new[] { Intent.ActionBootCompleted })]
public class MyBootReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
MyNotificationService.RunIntentInService(context, intent);
SetResult(Result.Ok, null, null);
}
}
If you are using PushSharp you can probably get away with adding that filter to the PushHandlerBroadcastReceiverBase implementation.
I'm trying to implement IChartStorageHandler via .Net4.5/MVC4 application to use on Azure based on http://goo.gl/WAapl
I couldn't be able to hit none of the breakpoints that I set in the class below. So my question is, "is there any trick that I can use to force it"? Thank you!
You may find the details about the parameters at http://msdn.microsoft.com/en-us/library/dd456629.aspx
namespace MvcWebRole1.Codes
{
public class ChartImageHandler : IChartStorageHandler
{
public ChartImageHandler()
{
throw new NotImplementedException();
}
#region IChartStorageHandler Members
public void Delete(string key)
{
throw new NotImplementedException();
}
public bool Exists(string key)
{
throw new NotImplementedException();
}
public byte[] Load(string key)
{
throw new NotImplementedException();
}
public void Save(string key, byte[] data)
{
throw new NotImplementedException();
}
#endregion
}
}
web.config part #1
<appSettings>
<add key="ChartImageHandler" value="handler=MvcWebRole1.Codes.ChartImageHandler, MvcWebRole1; webDevServerUseConfigSettings=false;" />
</appSettings>
web.config part #2
<system.webServer>
<handlers>
<remove name="ChartImageHandler"/>
<add name="ChartImageHandler" path="ChartImg.axd" verb="GET,HEAD,POST" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode" />
</handlers>
</system.webServer>
controller
public ActionResult ChartImage()
{
// get data
string path = HttpContext.Server.MapPath("~/App_Data/Test.csv");
IEnumerable<Bar> data = BarRepository.Get(path);
// generate chart
byte[] chartResult = data.Generator();
// return the chart
return File(chartResult, "image/png");
}
public static class ChartOperations
{
private static Chart _chart1;
public static byte[] Generator(this IEnumerable<Bar> data)
{
// initial variable tasks
_chart1 = new Chart() {DataSource = data.ToList()};
// generate the chart
DoTheHardWork();
// save chart to memory string
var image = new MemoryStream();
_chart1.SaveImage(image);
var result = image.GetBuffer();
return result;
}
}
Can you check if you have any entries for ChartImageHandler under system.web/httpHandlers? If yes, then please remove it.
also, it might not relate to this but in you web.config part #1, shouldn't you mention storage=file; as well so as to make it look like :
<add key="ChartImageHandler" value="storage=file;handler=MvcWebRole1.Codes.ChartImageHandler, MvcWebRole1; webDevServerUseConfigSettings=false;" />
This might be a stupid question to ask but when you are trying to hit a break point in your handler are you actually opening a page that includes ASP.NET Charts? The Chart handler will be hit only when charts are being loaded.
Perhaps you could launch the debugger as soon as your class is created?
public ChartImageHandler()
{
System.Diagnostics.Debugger.Launch();
//throw new NotImplementedException();
}
More at http://msdn.microsoft.com/en-us/library/system.diagnostics.debugger.launch.aspx
How to inject IServiceLocator to my class constructor?
When I tried to do this via my config, described above I got an Exception that it could not to create a RequestHandlersFactory class because unity could't find the constructor with serviceLocator and assemblyName.
I got two interfaces
public interface IPublicService
{
[OperationContract]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
Response Handle(Request request);
}
public interface IRequestHandlersFactory
{
IRequestHandler GetHandler(Type requestType);
IRequestHandler GetHandler<T>()
where T : Request;
IRequestHandler<T, TK> GetHandler<T, TK>()
where T : Request
where TK : Response;
}
and two classes:
public sealed class PublicService: IPublicService
{
private readonly IRequestHandlersFactory _requestHandlersFactory;
public PublicService(IRequestHandlersFactory requestHandlersFactory)
{
_requestHandlersFactory = requestHandlersFactory;
}
public Response Handle(Request request)
{
var handler = _requestHandlersFactory.GetHandler(request.GetType());
return handler.Handle(request);
}
}
public sealed class RequestHandlersFactory : IRequestHandlersFactory
{
private readonly IServiceLocator _serviceLocator;
private RequestHandlersFactory(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
...
}
public RequestHandlersFactory(IServiceLocator serviceLocator, String assemblyName) : this(serviceLocator)
{
AddHandlersFromAssembly(Assembly.Load(assemblyName));
}
public RequestHandlersFactory(IServiceLocator serviceLocator, Assembly assembly) : this(serviceLocator)
{
AddHandlersFromAssembly(assembly);
}
...
}
Now I want to create unity config file:
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<alias alias="IPublicService" type="MyAssembly.IPublicService, MyAssembly"/>
<alias alias="PublicService" type="MyAssembly.PublicService, MyAssembly"/>
<alias alias="IRequestHandlersFactory" type="MyAssembly.IRequestHandlersFactory, MyAssembly"/>
<alias alias="RequestHandlersFactory" type="MyAssembly.RequestHandlersFactory, MyAssembly"/>
<container>
<register type="IPublicService" mapTo="PublicService">
<lifetime type="singleton"/>
</register>
<register type="IRequestHandlersFactory" mapTo="RequestHandlersFactory">
<lifetime type="singleton"/>
<constructor>
<param name="assemblyName">
<value value="MyAssemblyWithHandlers" />
</param>
<param name="serviceLocator" dependencyName="WcfServiceLocator" dependencyType="Microsoft.Practices.ServiceLocation.IServiceLocator, Microsoft.Practices.ServiceLocation"/>
</constructor>
</register>
</container>
My config code:
var container = new UnityContainer();
//configure container
var unitySection = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
var serviceLocator = new UnityServiceLocator(container );
container.RegisterInstance<IServiceLocator>("WcfServiceLocator", serviceLocator, new ContainerControlledLifetimeManager());
unitySection.Configure(container);
Try swapping the order of the constructor parameters in the config file so they line up with the actual parameter list in the class.