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);
Related
In .Net MVC, you define routes into a RouteCollection. The URL helper methods make it easy to turn a controller + action + optional params into a URL.
When .Net MVC processes a request from a client browser, it clearly maps this URL to the right controller + action, to execute the appropriate command.
However, I can't see a way to programatically access this routing on the fly, such that I can turn a fully qualified URL (or a list of 10k+ URLs) into it's route components.
Does anyone know how you'd turn, for example, the following string input:
"http://stackoverflow.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair"
into the following output:
{
controller: "questions",
action: "view",
id: 2342325,
seoText: "c-sharp-net-mvc-turning-url-into-controller-action-pair"
}
Given this mapping is clearly being done by .Net, is it exposed anywhere?
Why would anyone want to do this?
Imagine you have a list of URLs you know have been accessed, mostly dynamic in nature, for example stackoverflow.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair, and you want to work out which actual endpoints / actions / controllers are being hit programatically (without much care about the actual data being passed).
You could hand code mappings, such that you know /questions/{id}/{text} -> controller: questions, action: question, but that's not future-proof, nor is it fun, and relies on text manipulation / processing.
Given a Route Dictionary and a list of URLs, with a function as described above, you could look at which controllers are most hit, or which actions, etc.
You should take a look at creating your own MvcRouteHandler. This is the point in the MVC stack where the Route Engine has already parsed the URL to find which Controller and Action to call, and then it goes through this method to get the actual C# class and method to invoke. No authorization or even HTTP Verb has been applied yet, so you will see every call that is made to your application.
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext context)
{
var controller = context.RouteData.Values["controller"];
var action = context.RouteData.Values["action"];
// Do whatever logging you want with this data, maybe grab the other params too.
return base.GetHttpHandler(context);
}
}
This can easily be registered where you set up your Routing.
routes.MapRoute("Home", "{controller}/{action}", new
{
controller = "Home",
action = "Index"
})
.RouteHandler = new CustomRouteHandler();
Looks like the only way to do this is by creating a dummy HTTP Context, similar to how you would unit test routes. It's a shame MVC doesn't provide better access to this, given it's being run on every request, rather than wrapping it up inside the context object.
Anyway, here is a working solution which can be modified to suit your needs:
public class UrlToRouteMapper
{
public static RouteValueDictionary GetRouteDataFromURL(string absoluteURL)
{
var testUrl = "~" + new Uri(absoluteURL).AbsolutePath;
var context = new StubHttpContextForRouting(requestUrl: testUrl);
var routes = new System.Web.Routing.RouteCollection();
MvcApplication.RegisterRoutes(routes);
System.Web.Routing.RouteData routeData = routes.GetRouteData(context);
return routeData.Values;
}
public static string GetEndpointStringFromURL(string absoluteURL)
{
var routeData = GetRouteDataFromURL(absoluteURL);
return routeData["controller"] + "/" + routeData["action"];
}
}
public class StubHttpContextForRouting : HttpContextBase {
StubHttpRequestForRouting _request;
StubHttpResponseForRouting _response;
public StubHttpContextForRouting(string appPath = "/", string requestUrl = "~/") {
_request = new StubHttpRequestForRouting(appPath, requestUrl);
_response = new StubHttpResponseForRouting();
}
public override HttpRequestBase Request {
get { return _request; }
}
public override HttpResponseBase Response {
get { return _response; }
}
}
public class StubHttpRequestForRouting : HttpRequestBase {
string _appPath;
string _requestUrl;
public StubHttpRequestForRouting(string appPath, string requestUrl) {
_appPath = appPath;
_requestUrl = requestUrl;
}
public override string ApplicationPath {
get { return _appPath; }
}
public override string AppRelativeCurrentExecutionFilePath {
get { return _requestUrl; }
}
public override string PathInfo {
get { return ""; }
}
}
public class StubHttpResponseForRouting : HttpResponseBase {
public override string ApplyAppPathModifier(string virtualPath) {
return virtualPath;
}
}
I have a controller in my web api. Let's call it TimeController.
I have a GET action and a PUT action. They look like this:
public class TimeController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, DateTime.UtcNow);
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
I also have a route config as follows:
routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });
so I can access it in a restful manner.
Now I also want to version the GET action using a custom Route attribute. I'm using code very similar to what Richard Tasker talks about in this blog post.
(the difference being that I use a regular expression to get the version from the accept header. Everything else is pretty much the same)
So my controller now looks like this:
public class TimeController : ApiController
{
private IService _service;
public TimeController(IService service)
{
_service = service;
}
[HttpGet, RouteVersion("Time", 1)]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow);
}
[HttpGet, RouteVersion("Time", 2)]
public HttpResponseMessage GetV2()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow.AddDays(1));
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
However, now when I try to access the PUT endpoint I'm getting a 404 response from the server. If I step through the code in debug mode, I can see that the RouteVersion attribute is being fired, even though I haven't decorated the action with it.
If I add the attribute to the PUT action with a version of 1, or I add the built in Route attribute like this: Route("Time") then it works.
So my question is: why is the attribute firing even though I haven't decorated the action with it?
Edit: Here is the code for the attribute:
public class RouteVersion : RouteFactoryAttribute
{
private readonly int _allowedVersion;
public RouteVersion(string template, int allowedVersion) : base(template)
{
_allowedVersion = allowedVersion;
}
public override IDictionary<string, object> Constraints
{
get
{
return new HttpRouteValueDictionary
{
{"version", new VersionConstraint(_allowedVersion)}
};
}
}
}
public class VersionConstraint : IHttpRouteConstraint
{
private const int DefaultVersion = 1;
private readonly int _allowedVersion;
public VersionConstraint(int allowedVersion)
{
_allowedVersion = allowedVersion;
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection != HttpRouteDirection.UriResolution)
{
return true;
}
int version = GetVersionFromHeader(request) ?? DefaultVersion;
return (version == _allowedVersion);
}
private int? GetVersionFromHeader(HttpRequestMessage request)
{
System.Net.Http.Headers.HttpHeaderValueCollection<System.Net.Http.Headers.MediaTypeWithQualityHeaderValue> acceptHeader = request.Headers.Accept;
var regularExpression = new Regex(#"application\/vnd\.\.v([0-9]+)",
RegexOptions.IgnoreCase);
foreach (var mime in acceptHeader)
{
Match match = regularExpression.Match(mime.MediaType);
if (match.Success)
{
return Convert.ToInt32(match.Groups[1].Value);
}
}
return null;
}
}
Edit2: I think there is some confusion so I've updated the Put action to match the route config
So my question is: why is the attribute firing even though I haven't decorated the action with it?
It is clear from both the way your question is phrased "when I try to access the PUT endpoint" and the fact that it matches the GET action (and then subsequently runs its constraint) that you have not issued a PUT request to the server. Most browsers are not capable of issuing a PUT request, you need a piece of code or script to do that.
Example
using (var client = new System.Net.WebClient())
{
// The byte array is the data you are posting to the server
client.UploadData(#"http://example.com/time/123", "PUT", new byte[0]);
}
Reference: How to make a HTTP PUT request?
I think its because of your action signature in combination with the default route
In your default route you specify the Id attribute as optional, however in your action you use the parameter days, in this case the framework can't resolve it. you either have to add it as a query string parameter eg:
?days={days}
Or change the signature to accept id as input.
Since it can't resove the action with days in the url it will return a 404
Personally i don't use the default routes and always use Attribute routing to prevent this kinda behavior
So my question is: why is the attribute firing even though I haven't decorated the action with it?
Any controller methods that do not have a route attribute use convention-based routing. That way, you can combine both types of routing in the same project.
Please see this link :
attribute-routing-in-web-api-2
Also as method is not decorated with route attribute, When the Web API framework receives an HTTP request, it tries to match the URI against one of the route templates in the routing table. If no route matches, the client receives a 404 error. That is why you are getting 404
Please see this one as well : Routing in ASP.NET Web API
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());
}
I'm using a custom filter to validate the content type, like:
public override void OnActionExecuting(HttpActionContext httpActionContext)
{
List<String> errors = new List<String>();
// a
if (httpActionContext.Request.Content.Headers.ContentType.MediaType == "application/json")
{
}
else
{
errors.Add("Invalid content type.");
}
// more checks
}
The above code is working fine, but the validation should check the request http verb, because it should validate the content type only for put or post. I don't want to remove the custom filter from httpget actions because I have more checks inside it, and I don't want to split the filter in two parts, meaning I have to check the http verb inside the filter, but I can't find how.
Any tips?
You can get the method type (post or put) from this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
string methodType = actionContext.Request.Method.Method;
if (methodType.ToUpper().Equals("POST")
|| methodType.ToUpper().Equals("PUT"))
{
// Your errors
}
}
If you need to get the HTTP Method of the request being validated by the filter, you can inspect the Method property of the request:
var method = actionContext.Request.Method;
I would recommend however that you break the filter apart, as you are quickly headed towards a big ball of mud scenario.
You really should be using the standard HTTPVerb attributes above your controller methods:
[HttpGet]
[HttpPut]
[HttpPost]
[HttpDelete]
[HttpPatch]
MVC Controllers for multiple:
[AcceptVerbs(HttpVerbs.Get, HttpVerbs.Post)]
WebAPI Controlelrs for multiple
[AcceptVerbsAttribute("GET", "POST")]
In the constructor of the action filter, you can pass in options/named parameters that will set the settings for the OnActionExecuting logic. Based on those settings you can switch up your logic.
public class MyActionFilterAttribute : ActionFilterAttribute
{
private HttpVerbs mOnVerbs;
public MyActionFilterAttribute(HttpVerbs onVerbs)
{
mOnVerbs = onVerbs;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var currentVerb = filterContext.HttpContext.Request.HttpMethod;
if (mOnVerbs.HasFlag(HttpVerbs.Post)) { }
else if (mOnVerbs.HasFlag(HttpVerbs.Get)) { }
base.OnActionExecuting(filterContext);
}
}
[MyActionFilter(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Index()
{
}
I need to transform some url parameters while creating link on server side.
Example:
#html.ActionLink("text","index","Home",null,new { id=Model.Id });
Now i have to transform id parameter so i can simply convert it and pass it into object objectRoute parameter or i can simply override ActionLink.But problem is that i have to make refactor on whole project.
So i am looking a way to intercepting mechanism or handler mechanism.
Is there any solution for this ?
You could try using an ActionFilterAttribute:
public class ConversionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var idValue = filterContext.RouteData.Values["id"];
var convertedIdValue = ConvertId(idValue);
var newRouteValues = new RouteValueDictionary(filterContext.RouteData.Values);
newRouteValues["id"] = convertedIdValue;
filterContext.Result = new RedirectToRouteResult(newRouteValues);
}
}
Then you'll need to apply the attribute to the action where you want this to happen:
[Conversion]
public ActionResult Index(int id)
{
// Your logic
return View();
}