First, I'm newish to MVC so bear with me. I'm following the Twilio example located here to validate that status callbacks from Twilio are authentic: https://www.twilio.com/docs/usage/tutorials/how-to-secure-your-csharp-aspnet-app-by-validating-incoming-twilio-requests#create-a-custom-filter-attribute
The code provided as-is should be reviewed by Twilio as the constructor name has a typo in it as they have "RequestAttribute" in it twice.
My issue is that I cannot for the life of me get the Attribute to resolve when I place it on my controller. I noticed the example Action Filter has a namespace ending ".Filters" and I noticed that my MVC application does not have a "Filters" directory. Some googling indicated that may be a difference between MVC4 and 5 - from what I can tell I'm using MVC5 as that's the listed version number on the System.Web.Mvc reference (version 5.2.2.0 specifically if that matters).
Anyway, Twilio linked to Microsoft documentation on creating the Custom Action Filter here: https://docs.microsoft.com/en-us/previous-versions/aspnet/dd410056(v=vs.98)?redirectedfrom=MSDN
Microsoft suggests creating the Action Filter class directly in the "Controllers" directory - so that's where I placed mine. Here is how that class reads currently:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Twilio.Security;
using System.Net;
using System.Web.Mvc;
using System.Web.Mvc.Filters;
namespace SMSStatusWS.Controllers
{
public class ValidateTwilioRequestAttribute : System.Web.Mvc.ActionFilterAttribute
{
private readonly RequestValidator _requestValidator;
public ValidateTwilioRequestAttribute()
{
var authToken = "<--------REMOVED------->";
_requestValidator = new Twilio.Security.RequestValidator(authToken);
}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var context = actionContext.HttpContext;
if (!IsValidRequest(context.Request))
{
actionContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
base.OnActionExecuting(actionContext);
}
private bool IsValidRequest(HttpRequestBase request)
{
var signature = request.Headers["X-Twilio-Signature"];
var requestUrl = request.Url.AbsoluteUri;
return _requestValidator.Validate(requestUrl, request.Form, signature);
}
}
}
Then my attempt to use it...
[HttpPost]
[ValidateTwilioRequest ]
public ActionResult UpdateSmsStatus(string dbContext, string smsQueueId)
{
// Controller code here...
}
The attribute [ValidateTwilioRequest ]
is highlighted in red in Visual Studio... meaning it can't resolve it (I guess) but I'm not sure why as the namespace is the same in both my controller class and the action filter class...
namespace SMSStatusWS.Controllers
So at this point, I'm not sure what's wrong here. As far as I can tell, the custom action filter was created correctly, and I'm using it as Twilio shows, but yet it doesn't work. Even though the filter does not seem to resolve, the project builds and runs... but of course it never hits the code in the action filter.
Is there some other way I need to reference this Action Filter that the docs are not telling me? Thoughts on what to try or look for that I may be overlooking?
UPDATE 10/13/21: I have made some progress... turns out the attribute not resolving was related to Visual Studio. Not able to figure out why it wasn't resolving, I closed the solution and restarted Visual Studio and when I reopened it, it started resolving the attribute immediately. However, I'm not positive it's working as I can't hit a breakpoint in the OnActionExecuting override and Visual Studio says no symbols have loaded.
So new question now, how do you debug custom Action Filters? Shouldn't I be able to step into the code with the debugger?
CodePudding user response:
You could you try using System.Diagnostics.Debug.WriteLine in the OnActionExecuting function and using that rather than the break point. That should write out to the output window and let you know if that code is running. After adding that code your OnActionExecuting function would look like this.
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
string controller = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
string action = actionContext.ActionDescriptor.ActionName;
System.Diagnostics.Debug.WriteLine("Controller-" controller ", Action-" action);
var context = actionContext.HttpContext;
if (!IsValidRequest(context.Request))
{
actionContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
base.OnActionExecuting(actionContext);
}
CodePudding user response:
Wow... learned a bit about Visual Studio and debugging today.The MVC project I have created for all of this had the project output set to a bin directory 2 levels up from my project. I'll call this the "global bin" - we use our global bin as a place for a lot of other projects to write their output to and that one location is referenced in our nightly build.
What I noticed is that my local project bin directory had an old DLL and PDB file in it, presumably from a build prior to changing the output directory to our global bin (global bin has the current dll and pdb). When debugging, I would notice that the debugger would load the dll and pdb from the "Temporary Asp.Net files" path (you can find this info in the "Module" window while debugging in Visual Studio) - this temp directory had those same old versions of the pdb in it and that is ultimately why I was unable to debug my Action Filter Attribute - it had an outdated pdb that did not have symbols for my latest code changes in it.
My band-aid for now is to set the output directory of my Project back to it's own local bin directory, and in web.config, disable shadowCopyBinAssemblies - which prevents the debugger from using the outdated pdb in the Temporary Asp.Net files directory and instead uses the pdb in the local bin.
That looks like this in web.config...
<system.web>
<hostingEnvironment shadowCopyBinAssemblies="false"/>
<system.web
I'm confused as to why Visual Studio doesn't shadowCopy the dll and pdb from our defined global bin (when it's defined as the project's output directory) to the Temporary Asp.Net files directory. That would solve all of this but I don't know if that can be configured anywhere.
As soon as I changed this to output to the project's bin directory and disabled the shadowCopy in web.config, the debugger loaded symbols from the correct dll and pdb and my breakpoints hit immediately.
I guess I'll add a post-build event or something to copy the dll & pdb files to our global bin so this doesn't break our nightly build, but this feels like a hack. If the default shadowCopy settings for the Temporary Asp.Net Files directory can't be changed I don't know what else to do about it. Is this a Visual Studio bug perhaps... shadowCopying from the project bin always, even if the output is configured to be written elsewhere?