I'm writing an ActionFilter and would like to have a more type-safe way to use RedirectToRouteResult. While investigating this, I wondered if there was a way to get the name (as a string) of any of my controllers. So for example, I would like to get "Home" from my HomeController, or "Admin" from my Admin controller. Is this at all possible?
From a filter context you can get the controller name by using:
public class MyFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
//That will give you "HomeController"
var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
//You can remove the "Controller" part, by replacing it with an empty string, like:
var justTheController = controllerName.Replace("Controller", string.Empty);
}
}
Have you tried this. You know you can substitute typeof by GetType on instance variable. if you have HomeController instance homeCtrl....you can do homeCtrl.GetType
var fullName= typeof(HomeController).Name;
var partialName = fullName.Remove(fullName.IndexOf("Controller"));
Actually there are ways to get the controller name :
filterContext.Controller will give you an object from where you
can deduce the controller name as
filterContext.Controller.GetType().Name
You always have the controller in the route values and can deduce as Request.RequestContext.RouteData.Values("controller").ToString()
Write extension method
public static class ControllerStringExtension
{
private const string ControllerString = "Controller";
public static string Short(this string value)
{
if (value.EndsWith(ControllerString))
{
//Remove 'Controller' from end of value name.
return value.Remove(value.Length - ControllerString.Length);
}
throw new ApplicationException("Should be used only for Controller names.");
}
}
than use: Redirect to 'home' controler and 'index' action.
public RedirectToRouteResult Something()
{
return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Short());
}
Related
I would like to create URL address based on some specific conditions. For now I have simple code in some controller's action:
string url ="";
if(some conditions based on data fetched from DB)
{
url = Url.Action("action","controller");
}
else{
url = some other url;
}
The problem is that this kind of logic will be used in a few other places. Is it posible to move it to some other class and still use the MVC Url.Action helper? Or there is another simple way to solve this problem?
You can create a custom action filter for this.the same can be used for all actions in a controller or for only particular actions in a controller.
Inside the filter
public class GenerateUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
GenerateUrl(filterContext);
}
private void GenerateUrl(ActionExecutingContext filterContext)
{
//your logic
}
}
Add this attribute above the action methods or for an entire controller.
[GenerateUrl]
This attribute will be called before the action is executed.
You can write own extension to make it available globally.
public static string NewAction(this IUrlHelper helper, string action, string controller)
{
if (some conditions)
{
//Manipulate however you like
return helper.Action(action, controller, values: null, protocol: null, host: null, fragment: null);
}
else
{
//This is default action
return helper.Action(action, controller, values: null, protocol: null, host: null, fragment: null);
}
}
Usage
Url.NewAction("action","controller");
Don't forget to create extension in static class.
You can redirect in an action filter instead of in your action.https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
An action filter is an attribute that you can apply to a controller action -- or an entire controller -- that modifies the way in which the action is executed.
public class RedirectUrlAction
{
private int value;
public string GetActionUrl(string action, string controller, string DefaultRediretUrl, Func<int,Boolean> Condition, UrlHelper urlHrlper)
{
string url = "";
if (Condition(value) == true)
{
urlHrlper.Action(action, controller);
}
else
{
url = DefaultRediretUrl;
}
return url;
}
}
calling the function
RedirectUrlAction act = new RedirectUrlAction();
act.GetActionUrl("value", "get", "http//:www.google.com", (x) => x % 2 == 0, Url);
I don't want to repeat the following
nameof(HomeController).Replace(nameof(Controller), string.Empty)
everytime I need to supply controller name without suffix "Controller".
Is there any elegant way to simplify? I attempted to create an extension method as follows but I don't know how to get the instance of the controller in question from a cshtml view.
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1
{
public static class Utilities
{
public static string BareName(this Controller controller)
{
return nameof(controller.GetType).Replace(nameof(Controller), string.Empty);
}
}
}
I want to invoke the following, for example, in view page.
#Html.ActionLink("something", nameof(HomeController.someaction), nameof(HomeController).Replace(nameof(Controller), string.Empty))
You could create a separate extension method on ActionContext which resolves the controller name from the ActionDescriptor.
public static string GetControllerName(this ActionContext actionContext)
{
return (actionContext.ActionDescriptor as ControllerActionDescriptor)?.ControllerName;
}
You don't need to trim the "Controller" part from the string in this case. Call it in your view like this:
var controllerName = ViewContext.GetControllerName();
Assuming that you want the name of the controller which is not currently handling the request, you want something like this:
public static string BareName<T>() where T: Controller
{
return typeof(T).Name.Replace(nameof(Controller), string.Empty);
}
You can then use it like:
#Html.ActionLink("something", nameof(HomeController.someaction), Utilities.BareName<HomeController>())
I'm still not sure why Microsoft hasn't put this as part of the ASP.NET Core core package...
If you're in a view, just use ViewContext.RouteData.Values["controller"].
UPDATE (based on edited question)
So, yeah, it wasn't clear before what you were actually trying to achieve. Basically, you want to be able to specify the controller/action params of methods like Html.Action without using hard-coded strings. As far as extension methods go, your best bet is an IHtmlHelper extension like:
public static class IHtmlHelperExtensions
{
public static string GetControllerName<TController>(this IHtmlHelper helper)
where TController : Controller
{
return typeof(TController).GetName().Replace("Controller", "");
}
}
Which you can then use like:
Html.GetControllerName<HomeController>()
However, this still feels clunky to me. Honestly, if you're looking to avoid hard-coding things, I'd recommend naming your routes and using a static class to house the names:
public static class RouteNames
{
public const string Home = "Home";
}
Then:
public class HomeController : Controller
{
[HttpGet("", Name = RouteNames.Home)]
public IActionResult Index() => View();
}
And then finally:
#Html.RouteLink(RouteNames.Home, RouteNames.Home)
Or via tag helper:
<a asp-route="#RouteNames.Home">#RouteNames.Home</a>
If you want to get controller, action or area name inside a view or controller, here are some options:
you just need a reference to an ViewContext or HttpContext or IHtmlHelper object:
////////////////////////////////
// with an IHtmlHelper object //
////////////////////////////////
var request = htmlHelper.ViewContext.HttpContext.Request;
var controllerName = request.RouteValues["controller"].ToString();
var actionName = request.RouteValues["action"].ToString();
// if area exists
var areaName = request.RouteValues["area"].ToString();
//////////////////////////////////
// with an `ViewContext` object //
//////////////////////////////////
var controllerName = ViewContext.RouteData.Values["controller"].ToString();
var actionName = ViewContext.RouteData.Values["action"].ToString();
// if area exists
var areaName = ViewContext.RouteData.Values["area"].ToString();
So an extension method for IHtmlHelper could be as follows:
public static class IHtmlHelperExtensions
{
public static string GetControllerName(this IHtmlHelper htmlHelper) =>
htmlHelper.ViewContext.RouteData.Values["controller"].ToString();
}
[HttpGet]
public IActionResult Get([FromRoute]string controller)
{
//controller will be Controller name without suffix "Controller".
}
var controllerName = ControllerContext.ActionDescriptor.ControllerName.Replace(nameof(Controller), string.Empty);
According to the inlined documentation, ControllerBase.RedirectToAction takes both the action name and the controller name:
// Parameters:
// actionName:
// The name of the action.
//
// controllerName:
// The name of the controller.
public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName);
Now, let's assume I want to redirect to the following action:
[Route("Whatever")]
public class WhateverController : Controller
{
[HttpGet("Overview")]
public IActionResult Overview()
{
return View();
}
}
Naturally, I wanted to use the nameof operator:
[Route("Home")]
public class HomeController : Controller
{
[HttpGet("Something")]
public IActionResult Something()
{
return RedirectToAction(
nameof(WhateverController.Overview), // action name
nameof(WhateverController) // controller name
);
}
}
But that call fails with the error InvalidOperationException: No route matches the supplied values.
I know I could hardcode the controller name to "whatever" instead of using the nameof operator, but is there a way to get the proper name from the class name?
The problem is nameof(WhateverController) returns WhateverController, not (Whatever) that you and routing system expect.
You may use nameof(WhateverController).Replace("Controller", "") to get what you want.
Edit:
If all you want is not hard-coded controller/action names, Then it's better to use something like R4MVC.
nameof(WhateverController) will return "WhateverController". RedirectToAction is expecting to take your controller's name in the form of "Whatever".
Using nameof instead of hardcoding strings is definitely good (in a lot of circumstances) but it looks like that's what's throwing you off in this case.
I am not a fan of extension methods because it pollutes the API in this case, but if you want, it is not the worst idea.
public static class StringExtensions
{
/// <summary>
/// Removes the word "Controller" from the string.
/// </summary>
public static string RemoveController(this string value)
{
string result = value.Replace("Controller", "");
return result;
}
}
Usage
nameof(WhateverController).RemoveController();
A Better Approach
Instead of extension method, create a base controller and put the method in it.
public class ControllerBase : Controller
{
/// <summary>
/// Removes the word "Controller" from the string.
/// </summary>
protected string _(string value)
{
string result = value.Replace("Controller", "");
return result;
}
}
If I think a method name serves no purpose, I sometimes use _ but you can replace it with a name if you want such as RemoveController.
Usage
public class SomeController : ControllerBase
{
public ActionResult Index(string value)
{
return RedirectToAction(nameof(WhateverController.Overview), _(nameof(WhateverController)));
}
}
You can see from above usage how _ stays out of the way and improves readability. Again this is what I do so if you don't like it, you don't need to do this.
How can I use Url.Action() in a class file of MVC project?
Like:
namespace _3harf
{
public class myFunction
{
public static void CheckUserAdminPanelPermissionToAccess()
{
if (ReferenceEquals(HttpContext.Current.Session["Loged"], "true") &&
myFunction.GetPermission.AdminPermissionToLoginAdminPanel(
Convert.ToInt32(HttpContext.Current.Session["UID"])))
{
HttpContext.Current.Response.Redirect(Url.Action("MainPage", "Index"));
}
}
}
}
You will need to manually create the UrlHelper class and pass the appropriate RequestContext. It could be done with something like:
var requestContext = HttpContext.Current.Request.RequestContext;
new UrlHelper(requestContext).Action("Index", "MainPage");
However, you are trying to achieve redirection based on authentication. I suggest you look at implementing a custom AuthorizeAttribute filter to achieve this kind of behavior to be more in line with the framework
Pass the RequestContext to your custom class from the controller. I would add a Constructor to your custom class to handle this.
using System.Web.Mvc;
public class MyCustomClass
{
private UrlHelper _urlHelper;
public MyCustomClass(UrlHelper urlHelper)
{
_urlHelper = urlHelper;
}
public string GetThatURL()
{
string url=_urlHelper.Action("Index", "Invoices");
//do something with url or return it
return url;
}
}
You need to import System.Web.Mvc namespace to this class to use the UrlHelper class.
Now in your controller, create an object of MyCustomClass and pass the controller context in the constructor,
UrlHelper uHelp = new UrlHelper(this.ControllerContext.RequestContext);
var myCustom= new MyCustomClass(uHelp );
//Now call the method to get the Paging markup.
string thatUrl= myCustom.GetThatURL();
#Simon Belanger's answer is perfectly working, but UrlHelper.Action() generates relative URLs and in my case i need the fully qualified absolute URL. So what i need to do is - i have to use one of the overload provided by UrlHelper.Action() method.
var requestContext = HttpContext.Current.Request.RequestContext;
string link = new UrlHelper(requestContext).Action("Index", "Home", null, HttpContext.Current.Request.Url.Scheme);
So let say if your application hosted on "https://myexamplesite.com" then above code will give you full url like this - "https://myexamplesite.com/Home/Index". Hope this answer will help those readers who will come across this link.
For those arriving late to this post, using .Net Core and .Net 5.0, You should try this;
private readonly IUrlHelperFactory _urlHelperFactory;
private readonly IActionContextAccessor _actionContextAccessor;
public EmailSenderService(IUrlHelperFactory urlHelperFactory,
IActionContextAccessor actionContextAccessor)
{
_urlHelperFactory = urlHelperFactory;
_actionContextAccessor = actionContextAccessor;
}
private string GenerateUrl(string action, string controller, object routeValues = null)
{
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
return urlHelper.Action(action, controller, routeValues, _actionContextAccessor.ActionContext.HttpContext.Request.Scheme);
}
I tried to use #simion's answer and I was getting an invalid type in the constructor for UrlHelper. "cannot convert from System.Web.Routing.RequestContext to System.Net.Http.HttpRequestMessage"
So I used this
var urlHelper = new System.Web.Mvc.UrlHelper(HttpContext.Current.Request.RequestContext);
string url = urlHelper.Action("MainPage", "Index");
worked out for me.
You can also use this
return RedirectToAction("Index", "MainPage");
You simply need to pass Url property from your controller to your class file,
string CreateRoutingUrl(IUrlHelper url)
{
return url.Action("Action", "Controller");
}
and on your controller :
MyClass.CreateRoutingUrl(Url);
I'm trying to create a custom ActionFilter which operates on a set of parameters that would be passed to it from the controller.
So far, my customer ActionFilter looks like this:
public class CheckLoggedIn : ActionFilterAttribute
{
public IGenesisRepository gr { get; set; }
public Guid memberGuid { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier <= bottomMember.Role.Tier)
{
filterContext
.HttpContext
.Response
.RedirectToRoute(new { controller = "Member", action = "Login" });
}
base.OnActionExecuting(filterContext);
}
}
I know I still need to check for nulls, etc. but I can't figure out why gr and memberGuid aren't successfully being passed. I'm calling this Filter like this:
[CheckLoggedIn(gr = genesisRepository, memberGuid = md.memberGUID)]
public ActionResult Home(MemberData md)
{
return View(md);
}
genesisRepository and md are being set in the controller's constructor.
I'm not able to get this to compile. The error I get is:
Error 1 'gr' is not a valid named attribute argument because it is not a valid attribute parameter type
Error 2 'memberGuid' is not a valid named attribute argument because it is not a valid attribute parameter type
I double checked that gr and memberGuid were the same types as genesisRepority and md.memberGUID, What is causing these errors?
Solution
Thanks to jfar for offering a solution.
Here's the Filter I ended up using:
public class CheckLoggedIn : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var thisController = ((MemberController)filterContext.Controller);
IGenesisRepository gr = thisController.GenesisRepository;
Guid memberGuid = ((MemberData)filterContext.HttpContext.Session[thisController.MemberKey]).MemberGUID;
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier >= bottomMember.Role.Tier)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new {
controller = "Member",
action = "Login"
}));
}
base.OnActionExecuting(filterContext);
}
}
This is a way to make this work. You have access to the ControllerContext and therefore Controller from the ActionFilter object. All you need to do is cast your controller to the type and you can access any public members.
Given this controller:
public GenesisController : Controller
{
[CheckLoggedIn()]
public ActionResult Home(MemberData md)
{
return View(md);
}
}
ActionFilter looks something like
public class CheckLoggedIn : ActionFilterAttribute
{
public IGenesisRepository gr { get; set; }
public Guid memberGuid { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* how to get the controller*/
var controllerUsingThisAttribute = ((GenesisController)filterContext.Controller);
/* now you can use the public properties from the controller */
gr = controllerUsingThisAttribute .genesisRepository;
memberGuid = (controllerUsingThisAttribute .memberGuid;
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier <= bottomMember.Role.Tier)
{
filterContext
.HttpContext
.Response
.RedirectToRoute(new { controller = "Member", action = "Login" });
}
base.OnActionExecuting(filterContext);
}
}
Of course this is assuming the ActionFilter isn't used across multiple controllers and you're ok with the coupling. Another Option is to make a ICheckedLoggedInController interface with the shared properties and simply cast to that instead.
You can only use constant values for attribute properties; see a this page for a full explanation.
Attributes are essentially metadata added to a type. They can only use const values, instead of instance variables. In your case you are tying to pass in your instance variables of genisisRepository, etc. This will fail to compile as they are not compile time constants.
You should look into Dependency Injection for Action Filters to achieve this, typically using an IoC container.
Also, if your ActionFilter is performing a post ActionResult action, such as OnActionExecuted, you could probably get away with storing something in the route data:
public ActionResult Index()
{
ControllerContext.RouteData.DataTokens.Add("name", "value");
return View();
}