How to check attribute of action from HttpRequest - asp.net-mvc-3

I've followed this Prevent Forms authentication in order to try and handle redirecting from ajax gracefully. However I need to be able to determine if certain attributes are decorating the action that this call was made for as I only want to do this for some occasions. Can I get this information from the HttpRequest object that is accessible within this method?.
Essentially taking the part from the code above that I would like to manipulate:
public class SuppressFormsAuthenticationRedirectModule : IHttpModule {
private void OnPostReleaseRequestState(object source, EventArgs args) {
var context = (HttpApplication)source;
var response = context.Response;
var request = context.Request; // request is HttpRequest
if (response.StatusCode == 401 && request.Headers["X-Requested-With"] ==
"XMLHttpRequest") {
// TODO HERE: Check that the controller action contains a particular attribute
// and if so do not suppress redirect
SuppressAuthenticationRedirect(context.Context);
}
}
}
UPDATE:
It's probably worth noting that this code is held within a compiled DLL project that is then encorporated into a host MVC application (which we don't have access to). In that case I don't really have access to changing default implementations unless I can ensure it doesn't effect the rest of the controllers in the application.

I tried to use as much of the framework as possible, which is why I chose to expose the GetControllerType method from the DefaultControllerFactory. You'll notice that routeData contains the area, controller and action, so with a bit of reflection, you can bypass having to create a derived controller factory.
This is definitely not production ready. It is just a way to get the custom attributes from the requested action.
Edit: instead of setting the current controller factory, create a new DerivedControllerFactory
var httpApplication = (HttpApplication)sender;
var httpContext = new HttpContext(httpApplication.Request, new HttpResponse(new StringWriter()));
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
//var factory = ControllerBuilder.Current.GetControllerFactory() as DerivedControllerFactory;
var factory = new DerivedControllerFactory();
var controllerType = factory.GetControllerType(new RequestContext(new HttpContextWrapper(httpContext), routeData), routeData.Values["controller"].ToString());
var methodInfo = controllerType.GetMethod(routeData.Values["action"].ToString());
var attributes = methodInfo.GetCustomAttributes(true);
public class DerivedControllerFactory : DefaultControllerFactory
{
public new Type GetControllerType(RequestContext requestContext, string controllerName)
{
return base.GetControllerType(requestContext, controllerName);
}
}

Related

How to create HttpResponseMessage in a non-APIController

I have a service layer called GatewayService which calls another WebApi to get the product information and manipulate the Inventory in the response and return the same response to the caller.
Here is the code I have. The problem that I have is, I can't use Request.CreateResponse(...) which will give me a compilation error because GatewayService does not inherit ApiController.
Is there a way to update the response and return as HttpResponseMessage?
public class GatewayService
{
// Code Removed for bravity
public HttpResponseMessage Get(SingleProductSearcRequest request)
{
var response = productServiceWebApi.Get(request); // Returns HttpResponseMessage
var p = response.Content.ReadAsAsync<JObject>().Result;
p["Inventory"] = "Not Available";
return Request.CreateResponse(p); // COMPILER ERROR!!!
}
}
Request.CreateResponse() is just an extension method for HttpRequest. You can manually construct an HttpResponseMessage as well as give it content by doing something like:
var p = response.Content.ReadAsAsync<JObject>().Result;
HttpResponseMessage message = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
message.Content = new ObjectContent(p);
You can transfer headers and other information over as well, if necessary. Depending on the need, there's also StringContent, etc.

Referencing Action Parameters from ExceptionLogger

I'm wanting to make use of the new method for globally logging errors. I've written a class that inherits ExceptionLogger and overrides the Log() method. Then registered it as a replacement.
public class TraceExceptionLogger : ExceptionLogger
{
public async override void Log(ExceptionLoggerContext context)
{
// This is always empty string
var content = await context.Request.Content.ReadAsStringAsync();
// This is almost always null
var actionContext = context.ExceptionContext.ActionContext;
}
}
I can dig through the ExceptionLoggerContext object's properties to get pretty much everything I need, EXCEPT for action parameters. There is indeed an ActionContext property but I've only seen it null and this wiki page states that ActionContext and ControllerContext will almost always be null.
Also, I can't get the content stream because its stream is already read before it gets to my logger. So there's no way for me to get any posted json from the request's content.
Is there maybe a way to get the posted data from HttpContext.Current or in some other way?
Ok it looks like I can get the body text from HttpContext by reading InputStream on the Request object like this:
string bodyText = string.Empty;
using (var sr = new StreamReader(HttpContext.Current.Request.InputStream))
{
sr.BaseStream.Seek(0, SeekOrigin.Begin);
bodyText = sr.ReadToEnd();
}
This code has been successful me so far for getting my posted json data.
Here's action parameters for future reference
public class HomeController : ApiController {
public string Get(string id, [FromHeader] Whoever whoever) {
public string Post(Whatever whatever) {
var args = ((ApiController) context.ExceptionContext
.ControllerContext.Controller)).ActionContext.ActionArguments
if (args.ContainsKey("whatever")) {
var whatever = (Whatever)args["whatever"];

Update: Passing HttpContext and IPrincipal to Controler in ReSharper unit test

I'm attempting to run a couple basic unit tests on an ASP MVC controller, however at one point the controller needs to examine the IPrincipal User object like so:
ViewBag.Level = Security.GetLevel(User);
Once the unit test enters the Create controller method, however, the above line throws a NullReferenceException as the User object is null.
Any ideas on how to set an IPrincipal for a unit test session?
Here's the test as I have it written right now. I attempted to access the User object and simply set it before the test goes into the Create method, however the intellisense isn't picking it up.
[Test]
public void a06_CloneFromDatabase()
{
using (AgentResources db = new AgentResources())
{
var master = (from a in db.AgentTransmission
where a.RecordStatus.Equals("C")
select a).FirstOrDefault();
var result = _controlAgtTran.Create(master.ID, null, string.Empty, string.Empty, string.Empty, string.Empty, false) as ViewResult;
var model = result.Model as AgentTransmission;
Assert.AreEqual(model.ReferenceType, master.ReferenceType);
}
}
EDIT
Following the post mentioned in the comments below I found a method to create a HttpContext session and apply and IPrincipal to that session. This works fine until the unit test moves into the controller where the HttpContext and IPrincipal User objects are all, once again, null.
Since it seems the instance of the controller I'm using has it's HttpContext property as read only (and the IPrincipal User property as well) does anyone know of a way to pass the HttpContext being used in the unit test inside the controller being tested? Also, if this is not possible, what is a usable method for testing RouteValues using ReSharper's unit tests?
[SetUp]
public void SetUp()
{
_controlAgtTran = new AgentTransmissionController();
/****Set up Current HTTP Context to pass Security.cs checks*****/
//Set up the HTTP Request
var httpRequest = new HttpRequest("", "http://localhost:2574/", "");
//Set up the HTTP Response
var httpResponse = new HttpResponse(new StringWriter());
//Set up the HTTP Context
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("NEAROD",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
100,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
httpContext.Items["AspSession"] =
typeof (HttpSessionState)
.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Standard,
new[] {typeof (HttpSessionStateContainer)},
null)
.Invoke(new object[] {sessionContainer});
//Assign the context
HttpContext.Current = httpContext;
}
[Test]
public void a01_IncompleteRecordGoesToEdit()
{
AgentTransmission agtTran = new AgentTransmission();
agtTran.ReferenceNumber = 95820518787;
agtTran.ReferenceType = "S";
agtTran.EffectiveDate = DateTime.Now;
agtTran.RelationshipEffDate = DateTime.Now;
agtTran.RecordStatus = "N";
agtTran.CreatedDate = DateTime.Now;
agtTran.CreatedOperator = "xTest1";
agtTran.FirstName = "Unit";
agtTran.LastName = "Test";
agtTran.ExtRepType = "EXTREPID";
agtTran.JIT = true;
agtTran.SendToDRM = true;
agtTran.SendToNMF = true;
agtTran.WelcomeLetter = true;
agtTran.OverrideRegionInd = false;
//set IPrincipal
string[] roles = {"LCO"};
IPrincipal principal = new GenericPrincipal(new GenericIdentity("SYMETRA\\NEAROD"), roles);
HttpContext.Current.User = principal;
IPrincipal user = HttpContext.Current.User;
Assert.AreEqual(user, principal); //This passes
Assert.AreEqual(principal, _controlAgtTran.User); //this fails
var result = (RedirectToRouteResult)_controlAgtTran.Create(agtTran); //this crashes
//Tests aren't run
Assert.IsNotNull(result);
Assert.AreEqual(3, result.RouteValues.Count);
Assert.AreEqual("AgentTransmission", result.RouteValues["controller"]);
Assert.AreEqual("Edit", result.RouteValues["action"]);
}
Following a similar solution mentioned in this post I added the following to the end of the SetUp() method.
var controllerCtx = new ControllerContext();
controllerCtx.HttpContext = new HttpContextWrapper(HttpContext.Current);
_controlAgtTran.ControllerContext = controllerCtx;
Wrapping the current HttpContext inside an HttpContextBase property (the inappropriately named controllerCtx.HttpContext) the test now has access to the User and HttpContext properties of the controller. These properties were previously read-only when using just the HttpContext.Current session and therefore always null.
FYI - this is my first time unit testing with these objects so that explanation may be less than 100% correct. Please feel free to comment below and I'll make any necessary changes.

Instantiate new System.Web.Http.OData.Query.ODataQueryOptions in nunit test of ASP.NET Web API controller

I have an ASP.NET MVC4 Web API project with an ApiController-inheriting controller that accepts an ODataQueryOptions parameter as one of its inputs.
I am using NUnit and Moq to test the project, which allow me to setup canned responses from the relevant repository methods used by the ApiController. This works, as in:
[TestFixture]
public class ProjectControllerTests
{
[Test]
public async Task GetById()
{
var repo = new Mock<IManagementQuery>();
repo.Setup(a => a.GetProjectById(2)).Returns(Task.FromResult<Project>(new Project()
{
ProjectID = 2, ProjectName = "Test project", ProjectClient = 3
}));
var controller = new ProjectController(repo.Object);
var response = await controller.Get(2);
Assert.AreEqual(response.id, 2);
Assert.AreEqual(response.name, "Test project");
Assert.AreEqual(response.clientId, 3);
}
}
The challenge I have is that, to use this pattern, I need to pass in the relevant querystring parameters to the controller as well as the repository (this was actually my intent). However, in the case of ODataQueryOptions-accepting ApiController methods, even in the cases where I would like to use just the default parameters for ODataQueryOptions, I need to know how to instantiate one. This gets tricky:
ODataQueryOptions does not implement an interface, so I can't mock it directly.
The constructor requires an implementation of System.Web.Http.OData.ODataQueryContext, which requires an implementation of something implementing Microsoft.Data.Edm.IEdmModel, for which the documentation is scarce and Visual Studio 2012 Find References and View Call Hierarchy do not provide insight (what implements that interface?).
What do I need to do/Is there a better way of doing this?
Thanks.
Looks like someone else already answered this in the comments here, but it's not a complete solution for my use-case (see comment below):
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
var opts = new ODataQueryOptions<Customer>(new ODataQueryContext(modelBuilder.GetEdmModel(),typeof(Customer)), request);
This is the solution I have been using in my NUnit tests to inject ODataQueryOptions
private static IEdmModel _model;
private static IEdmModel Model
{
get
{
if (_model == null)
{
var builder = new ODataConventionModelBuilder();
var baseType = typeof(MyDbContext);
var sets = baseType.GetProperties().Where(c => c.PropertyType.IsGenericType && c.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>));
var entitySetMethod = builder.GetType().GetMethod("EntitySet");
foreach (var set in sets)
{
var genericMethod = entitySetMethod.MakeGenericMethod(set.PropertyType.GetGenericArguments());
genericMethod.Invoke(builder, new object[] { set.Name });
}
_model = builder.GetEdmModel();
}
return _model;
}
}
public static ODataQueryOptions<T> QueryOptions<T>(string query = null)
{
query = query ?? "";
var url = "http://localhost/Test?" + query;
var request = new HttpRequestMessage(HttpMethod.Get, url);
return new ODataQueryOptions<T>(new ODataQueryContext(Model, typeof(T)), request);
}

How do I get the MethodInfo of an action, given action, controller and area names?

What I have is the following extension method:
public MyCustomAttribute[] GetActionAttributes(
this Controller #this,
string action,
string controller,
string area,
string method)
{
}
How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?
To this moment I have nothing... no clues on how to do this.
I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.
Why I need these attributes
My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.
Other uses
I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.
Not a duplicate
Yes, some will say this is possibly a duplicate of this question...
that does not have the answer I want:
How can i get the MethodInfo of the controller action that will get called given a request?
That's why I have specified the circumstances of my question.
I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
MVC 3 (I think)
MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
static object ControllerBuilder.Current (very bad for unit testing)
a lot of classes from MVC (high coupling is always bad)
not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))
The code
public static Attribute[] GetAttributes(
this Controller #this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? #this.RouteData.GetRequiredString("action");
var controllerName = controller
?? #this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = #this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.
public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
{
/// <summary>
/// Use for validate user permission and when it also validate user session is active.
/// </summary>
/// <param name="filterContext">Filter Context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
if (!IsUserHasPermission(controller, actionName))
{
// Do your required opeation
}
}
}
if you have a default route configured like
routes.MapRoute(
"Area",
"",
new { area = "MyArea", controller = "Home", action = "MyAction" }
);
you can get the route information inside the controller action like
ht tp://localhost/Admin
will give you
public ActionResult MyAction(string area, string controller, string action)
{
//area=Admin
//controller=Home
//action=MyAction
//also you can use RouteValues to get the route information
}
here is a great blog post and a utility by Phil Haack RouteDebugger 2.0
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];
Good Luck.

Resources