Work out if Controller is in an Area - c#

Is there a way to check whether the Controller which invoked a method comes from a Controller which is within an Area?
For example, I have a class which inherits from AuthorizeAttribute e.g.
public class CustomAuthorize: System.Web.Mvc.AuthorizeAttribute
{
public CustomAuthorize()
{
...
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// TODO - Check if the controller is from an Area
}
}
I then have some controller actions which are decorated with relevant Roles (as well as a few other custom attributes) e.g.
[CustomAuthorize(Roles ="Administrator")]
[HttpGet]
public virtual ActionResult Index()
{
...
}
In the TODO section above, I'd like to see if the Controller is one of the controllers from one of my Areas. I know that my controllers which are in an area will be in the ProjectName.Areas.xxx.Controllers namespace (where xxx is the Area name), whereas ones which aren't will be in the ProjectName.Controllers namespace.
Is there some way (using reflection perhaps?) that from within the AuthorizeCore function above that I can work out the specific area (or namespace that it came from) so that I can implement some custom functionality?

You can get it from the RouteData.DataTokens:
httpContext.Request.RequestContext.RouteData.DataTokens["area"]
That will return null if your controller is not in an area or the name of the area if your controller is in an area.

I'm not too familiar with MCV's Area concept, but I've found this link from a Google search. Perhaps it may help you out.
ASP.NET MVC - Get Current Area Name in View or Controller

Related

How can I make Url.Action() recognize areas defined in referenced Razor Class Libraries?

In my ASP.NET Core project's Razor, I'm using Url.Action() like this:
#Url.Action("Index", "Foo", new {Area = "MyArea"})
The controller action is defined in a referenced Razor Class Library like so:
[Route("MyArea/Foo")]
[Authorize]
public class FooController : Controller
{
[HttpPost]
public IActionResult Index()
{
return Ok();
}
}
Back in my ASP.NET Core project, in the Visual Studio code editor, MyArea appears in red, and the hover tooltip states "Cannot resolve area 'MyArea'". And of course, my call to Url.Action() returns string.Empty....
But the route is valid.
What change(s) could I apply to either my ASP.NET Core project or the referenced RCL to cause MyArea to be recognized as a valid area, and make the call Url.Action() return the expected URL?
[Area("MyArea")]
[Authorize]
public class FooController : Controller
{
[HttpPost]
public IActionResult Index()
{
return Ok();
}
}
Areas are different part of the route since they are named.. So you have to use the [Area()] otherwise it doesn't know what you are asking for the route. By convention the Foo route is already know since it's the name of the controller. This assumes you have the route mapped correctly in the Configure() method in your startup.cs. This also assumes that you are using the Area folder with a folder called MyArea in it.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/areas?view=aspnetcore-3.1

ASP.NET MVC - Optionally Redirect from a Base Class Method?

Consider the following block of code that reappears in many of my controller actions. (I'm chiefly concerned with the first 6 lines of the method body).
[HttpGet]
public ActionResult OptOut()
{
var user = this.SecurityPrincipal;
if (user.IsReadOnlyUser)
{
this.TempData["ViewModel"] = new AuthorizationModel { User = user };
return this.RedirectToAction("NotAuthorized", "Authorization");
}
var model = /* Elided for brevity */
return this.View(model);
}
My controllers derive from a base class, SecuredController which, in turn, derives from Controller. SecurityPrincipal is a property of SecuredController, and contains extensive Active Directory data about the current user.
In an effort to eliminate duplicate code, I'd ideally like to move the functionality contained in the if {...} block into a base class method, but I can't think of any way to do so, since the return type of the method would have to be ActionResult, resulting in something ungainly like this:
if ((var result = this.RequireReadWrite()) != null)
{
return result;
}
Can anyone suggest a way to do this, or am I simply out of luck here?
As mentioned in the comments, especially noting that security is a cross cutting concern we've suggested using MVC Action Filters to be applied in your use case and design.
Microsoft's documentation is pretty informative and there are more examples that can be found on the web on how to use MVC Filters. I'll try to provide an example, but this will be based on a lot of assumptions on your software architecture, since I simply don't have the knowledge of that.
You could create the following class:
public class SecuredFilterAttribute : AuthorizeAttribute
{
...
}
If using a Dependency Injection framework, you could inject the SecurityPrincipal service. But again I don't know the architecture of your application, so it's up to you how you create that dependency.
When overriding the AuthorizeCore, you could implement it like so:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !this.SecurityPrinciple.IsReadOnlyUser;
}
And when not authorized override the HandleUnauthorizedRequest method to redirect:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var redirectRoute = ...; //your route to redirect to an unauthorized page
filterContext.Result = new RedirectToRouteResult(redirectRoute);
//do some other things, for example, setting some tempdata information
}
Again it's up to you on how you would use this Filter. You could register it globally, or apply it on a per controller or action basis. To register it globally, in your startup:
GlobalFilters.Filters.Add(new SecuredFilterAttribute());

How to override an Action method in Mvc

I'm using a Cms for Mvc. This Cms has the following Controller:
public class OrderController : Controller
{
public ActionResult Index()
{
return View();
}
}
For customization needs, I'd like to override the behaviour of this controller and return something different when the same URL is visited by the user. What's the best approach in order to achieve this result?
I tried to inherit the Cms Controller and make the ActionResult an override, following this answer: How to override controller actionresult method in mvc3?
public class OrderController : Cms.Areas.Admin.Controllers.OrderController
{
public override ActionResult Index(Guid orderItemId)
{
// Do extra stuff
return View();
}
}
But this doesn't work. When I try to navigate "admin/order" I still enter in the Cms Controller/Action.
Any suggestion?
NOTE: The Controller I'm trying to override is in another assembly and the action is set to virtual. It's in an Area, therefore the Route is configured inside AreaRegistration.
Your request need to use OrderController instead EcommerceOrderController, take a look on your MVC routes
This seems to me to be a routing question. It doesn't matter if you override the controller if your route still points to the original. If you want a URL to invoke your action, you need to add a route with a higher priority than the one that is currently resolving to the original.

Remove controller name from URL with Attribute routing

I want to remove controller name from URL for specific Controller.
My Controller name is Product
I found some link to do this
Routing with and without controller name
MVC Routing without controller
But all the above links done in route config file. and those are affecting other controller too. I want to do it using Attribute Routing.
Can it is possible? As I want to do this for only Product controller.
I have tried to do it on action like this
[Route("Sample/{Name}")]
but it is not working.
Gabriel's answer is right, however, it can be a bit misleading since you're asking for MVC and that answer is for Web API.
In any case, what you want is to put the annotation over the class definition instead of an action method. MVC example would be like:
[RoutePrefix("SomethingOtherThanProduct")]
public class ProductController : Controller
{
public ActionResult Index()
{
...
return View();
}
}
I'm also dropping this as an answer since you may find the following article helpful: [Attribute] Routing in ASP.NET MVC 5 / WebAPI 2
Make sure you set the RoutePrefix attribute on the whole controller class, as well as using the Route attribute on the action.
[RoutePrefix("notproducts")]
public class ProductsController : ApiController
{
[Route("")]
public IEnumerable<Product> Get() { ... }
}

POST data using RenderMvcController in Umbraco

I have set up a document type in Umbraco, and have created a custom controller & model for this document type.
The custom controller inherits from : RenderMvcController class and the views inherit the model through #inherits Umbraco.Web.Mvc.UmbracoViewPage<com.something.model>
This all works fine for any HttpGet requests. However as soon as I want to do a form post back to the controller using #using (Html.BeginUmbracoForm("SomeAction", "SomeController", null, new { #class = "some-class" }))
I get the following error message: Could not find a Surface controller route in the RouteTable for controller name SomeController
From all the documentation that I was able to find it always refers to SurfaceControllers when it comes to form posts. Is there a way to change the routing so that it would post to my custom controller, rather then another controller that inherits from the SurfaceController class?
If you are going to post the form in this way, you need two controllers. One for the Document Type that inherits from MvcRenderController (as you already have) and a second which inherits from the SurfaceController.
The surface controller just needs a single POST action that does one of the following things:
// e.g. if modelstate is invalid
return CurrentUmbracoPage();
// redirecting back to original page after a successful post
return RedirectToCurrentUmbracoPage();
// Redirecting to another page after a successful post
return RedirectToUmbracoPage(12345)
This has been taken from the documentation here: http://our.umbraco.org/documentation/Reference/Templating/Mvc/forms
Strictly speaking the initial document type controller is not necessary in this scenario as it does not play a part in the post of the form.
If you want to post directly to the custom controller then you should not use Html.BeginUmbracoForm, you should just post directly to the current URL. The complication here is that it is then a little tricky to bind your model as a parameter of the post action. Your view model will have to inherit from RenderModel in the following way:
public class BaseModel : RenderModel
{
public BaseModel() :
base(UmbracoContext.Current.PublishedContentRequest.PublishedContent) { }
}
According to http://our.umbraco.org/forum/developers/api-questions/38662-(v6)-Could-not-find-a-Surface-controller-route-error?p=0,
SomeController Needs to inherit from SurfaceController, not RenderMvcController.
Alternatively, you could create a dedicate route and point it directly to your controller:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("someController", "some/someAction",
new { controller = "Some", action = "SomeAction" });
}
}

Categories