I'm trying to programmatically map a query string to an SEO friendly route URL I've created within my RouteConfig.cs file but appear to be having difficultly.
My route configuration looks like this:
routes.MapRoute(
name: "ReviewPost",
url: "{year}/{month}/{filename}/",
defaults: new {controller = "ReviewPost", action = "Detail"},
namespaces: new[] {"ApplicationName.Website.Controllers"},
constraints: new {year = #"\d{4}", month = #"\d{2}"}
);
And I'm able to retrieve the correct RouteData object like so:
string url = "~/ReviewPost/Detail/?year=2015&month=05&filename=review-title";
RouteData routeData = RouteDataUtils.RouteByUrl(myUrl);
However I'm having difficulty figuring out how to generate the friendly URL that should look like the one specified in my configuration (/2015/05/review-title/).
Is the RouteData object able to create an instance of the formatted URL above, or do I have to format it myself? Or does the MVC framework have a class for correctly formatting the URL string using the RouteData object?
Any help would be appreciated.
Update
Try it this way:
var urlHelper = new UrlHelper();
var theUrl = urlHelper.RouteUrl(routeData.Values);
original
Try something like this:
var urlHelper = new UrlHelper();
var theUrl = urlHelper.RouteUrl("ReviewPost",routeData.Values);
Note that the first parameter to RouteUrl is the name of your route--i.e. how you named it in MapRoute.
Related
I'm creating a WebApi in ASP.NET. I want one of my get methods to allow a string to be passed into it. This string will function as a path to a branch in TFS where I'll do a QueryHistory to return its history.
In my WebApiConfig.cs file:
config.Routes.MapHttpRoute(
name: "Branches",
routeTemplate: "api/branches/{fullPath}",
defaults: new { controller = "branches", fullPath = RouteParameter.Optional }
);
In my controller.cs file:
// GET api/branches/$/Project/Branches/Path/To-This-Branch
public string Get(Uri fullPath)
{
string output = "";
NetworkCredential cre = new NetworkCredential("COSMO\\pd-srv", pWord);
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri("http://fortknox:8080/tfs/PD"), cre);
var service = tfs.GetService<VersionControlServer>();
string s = System.Uri.UnescapeDataString(fullPath);
var latestChange = service.QueryHistory(s, RecursionType.None, 1);
//output = JsonConvert.SerializeObject(latestChange);
//return a json formatted string containing the full history of the branch path passed in
return "value";
}
I've tried multiple ways, just seems like when I pass in a uriEncoded parameter the UnescapeDataString only accepts strings so it doesn't work. If I pass in the string I get an error due to the '/'.
How can I pass this path from my javascript to my Get() call in my api?
I have run into this as well in the past and solved it by converting "/" to "!" and then converting back in the API. You could substitute any character(s) there... you just need to get rid of the "/"
You can use {*fullPath} in the end of your route template to match the fullPath using a wildcard expression.
config.Routes.MapHttpRoute(
name: "Branches",
routeTemplate: "api/branches/{*fullPath}",
defaults: new { controller = "branches", fullPath = RouteParameter.Optional }
);
See MSDN: Handling a Variable Number of Segments in a URL Pattern
Try call Get method:
api/branches?fullPath="directory1/directory2"
In an existing C# Web project here at my Job I've added a Web API part.
In four of my own classes that I use for the Web API I need to access some of the existing Controller-classes. Right now I just create a new Instance of them and everything works as intented: ProductController controller = new ProductController();
Still, creating a new ProductController while one should already exist obviously isn't a good practice. I know the Controllers are created in the Config-file in the Routes.MapHttpRoute, since it's using the C# Web MVC method. Below I've copied that piece of code:
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MyProject.Controllers" }
);
route.DataTokens["UseNamespaceFallback"] = false;
I've tried to access these Controllers in my one of my API-classes like so:
private void getControllerInstance()
{
var url = "~/Products";
// Original path is stored and will be rewritten in the end
var httpContext = new HttpContextWrapper(HttpContext.Current);
string originalPath = httpContext.Request.Path;
try
{
// Fake a request to the supplied URL into the routing system
httpContext.RewritePath(url);
RouteData urlRouteData = RouteTable.Routes.GetRouteData(httpContext);
// If the route data was not found (e.g url leads to another site) then authorization is denied.
// If you want to have a navigation to a different site, don't use AuthorizationMenu
if (urlRouteData != null)
{
string controllerName = urlRouteData.Values["controller"].ToString();
// Get an instance of the controller that would handle this route
var requestContext = new RequestContext(httpContext, urlRouteData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
// TODO: Fix error (The controller for path '/Products' was not found or does not implement IController.) on this line:
var controllerbase = (ControllerBase)controllerFactory.CreateController(requestContext, controllerName);
controller = (ProductController)controllerbase;
}
}
finally
{
// Reset our request path.
httpContext.RewritePath(originalPath);
}
}
As you might have noticed by the TODO-comment, at the line var controllerbase = (ControllerBase)controllerFactory.CreateController(requestContext, controllerName);, I get the following error:
HttpException was unhandler by user code: The controller for path '/Products' was not found or does not implement IController.
Does anyone know how to fix this error? Has this got something to do with one of the following two lines of the code in the Config-file?
namespaces: new[] { "MyProject.Controllers" }
route.DataTokens["UseNamespaceFallback"] = false;
Or did I do something else wrong?
A tip to everyone: Don't continue programming when you are very, very tired.. Anyway, everything was correct except for a small flaw:
My API Controller is called ProductsController and my normal (default) controller is called ProductController. In the method above I use:
var url = "~/Products";
To access the ProductController..
So, after removing the "s" (and for good measure make everything lower case) I have the following instead:
var url = "~/product";
And now it works..
if i have an route like /foo/bar/pewpew .. is it possible to get an instance of the controller which that route maps too?
To get the controller name, you can call create a fake HttpContextBase that returns your URL in its Request, then pass it to RouteTable.Routes.GetRouteData and check the area and controller values.
To get the controller instance, pass a RequestContext consisting of that HttpContextBase and RouteData to ControllerBuilder.Current.GetControllerFactory.CreateController.
Try,
var wrapper=new HttpContextWrapper(System.Web.HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(wrapper);
var controller = ControllerBuilder.Current.GetControllerFactory().CreateController(new RequestContext(wrapper, routeData), routeData.Values["controller"].ToString());
Update, you can use this instead.
var wrapper = new HttpContextWrapper(new System.Web.HttpContext(new HttpRequest(null, "http://localhost:4836/", null), new HttpResponse(new StringWriter())));
In my Asp.Net Mvc project I'd like to have a good looking urls, e.g. mysite.com/Page2, and I want to redirect from my old style urls (such as mysite.com?page=2) with 301 state so that there won't be two urls with identical content. Is there a way to do it?
As far as I know Asp.Net binding framework doesn't make difference between query string and curly brace params
I am not sure, I got your question right. It seems, your current setup relies on those GET parameters (like mysite.com?page=2). If you dont want to change this, you will have to use those parameters further. There would be no problem in doing so, though. Your users do not have to use or see them. In order to publish 'new style URLs' only, you may setup a URL redirect in your web server. That would change new style URLs to old style URLs.
The problem is the 301. If the user requests an old style URL, it would be accepted by the webserver as well. Refusing the request with a 301 error seems hard to achieve for me.
In order to get around this, I guess you will have to change your parameter scheme. You site may still rely on GET parameters - but they get a new name. Lets say, your comments are delivered propery for the following (internal) URL in the old scheme:
/Article/1022/Ms-Sharepoint-Setup-Manual?newpage=2
Note the new parameter name. In your root page (or master page, if you are using those), you may handle the redirect permanent (301) manually. Therefore, incoming 'old style requests' are distinguishable by using old parameter names. This could be used to manually assemble the 301 in the response in ASP code.
Personally, I would sugesst, to give up the 301 idea and just use URL redirection.
Well, as far as I can see performing such redirection in ASP.NET MVC might be tricky. This is how I did it:
global.asax:
routes.Add(new QueryStringRoute());
routes.MapRoute(null, "Article/{id}/{name}",
new { controller = "Article", action = "View", page = 1 },
new { page = #"\d+" }
);
routes.MapRoute(null, "Article/{id}/{name}/Page{page}",
new { controller = "Article", action = "View" },
new { page = #"\d+" }
);
QueryStringRoute.cs:
public class QueryStringRoute : RouteBase
{
private static string[] queryStringUrls = new string[]
{
#"~/Article/\d{1,6}/.*?page=\d{1,3}"
};
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
foreach (string queryStringUrl in queryStringUrls)
{
Regex regex = new Regex(queryStringUrl);
if (regex.IsMatch(url))
{
long id = 0; /* Parse the value from regex match */
int page = 0; /* Parse the value from regex match */
string name = ""; /* Parse the value from regex match */
RouteData rd = new RouteData(this, new MvcRouteHandler());
rd.Values.Add("controller", "QueryStringUrl");
rd.Values.Add("action", "Redirect");
rd.Values.Add("id", id);
rd.Values.Add("page", page);
rd.Values.Add("name", name);
rd.Values.Add("controllerToRedirect", "Article");
rd.Values.Add("actionToRedirect", "View");
return rd;
}
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
QueryStringUrlController.cs:
public class QueryStringUrlController : Controller
{
public RedirectToRouteResult Redirect(long id, int page, string name,
string controllerToRedirect, string actionToRedirect)
{
return RedirectToActionPermanent(actionToRedirect, controllerToRedirect, new { id = id, page = page, name = name });
}
}
Assuming you have such routing as in my global.asax file (listed above) you can create a custom Route class that will handle incoming requests and map them on a special redirection controller which will then redirect them to appropriate urls with 301 state. Then you must add this route to global.asax before your "Article" routes
If you're using IIS 7, the URL Rewrite Module should work for your scenario.
How can I get the controller action (method) and controller type that will be called, given the System.Web.Routing.RouteData?
My scenario is this - I want to be able to do perform certain actions (or not) in the OnActionExecuting method for an action.
However, I will often want to know not the current action, but the "root" action being called; by this I mean I may have a view called "Login", which is my login page. This view may include
another partial view "LeftNav". When OnActionExecuting is called for LeftNav, I want to be able to determine that it is really being called for the "root" aciton of Login.
I realise that by calling RouteTable.Routes.GetRouteData(actionExecutingContext.HttpContext), I can get the route for the "root" request, but how to turn this into
method and type info?
The only solution I have so far, is something like:
var routeData = RouteTable.Routes.GetRouteData(actionExecutingContext.HttpContext)
var routeController = (string)routeData.Values["controller"];
var routeAction = (string)routeData.Values["action"];
The problem with this is that "routeController" is the controller name with the "Controller" suffix removed, and is not fully qualified; ie it is "Login", rather than "MyCode.Website.LoginController".
I would far rather get an actual Type and MethodInfo if possible, or at least a fully qualified type name.
Any thoughts, or alternative approaches?
[EDIT - this is ASP.Net MVC 1.0]
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var type1 = filterContext.Controller.GetType();
var type2 = filterContext.ActionDescriptor
.ControllerDescriptor.ControllerType;
}
OK, sorry, I missed the "root" part.
Then, another way, you can save controller type to thread storage. Pseudocode:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!Thread.LocalStorage.Contains("root_controller"))
Thread.LocalStorage["root_controller"] =
filterContext.ActionDescriptor
.ControllerDescriptor.ControllerType;
}
Just an idea. I'm sure thread local storage is available in C#. The key idea here is that you save it only for first request, thus it's always root controller.
Here is the solution I compiled from various sources. The url variable should contain the URL of the action:
url = "YOUR URL";
// Original path is stored and will be rewritten in the end
var httpContext = new HttpContextWrapper(HttpContext.Current);
string originalPath = httpContext.Request.Path;
try
{
// Fake a request to the supplied URL into the routing system
httpContext.RewritePath(url);
RouteData urlRouteData = RouteTable.Routes.GetRouteData(httpContext);
// If the route data was not found (e.g url leads to another site) then authorization is denied.
// If you want to have a navigation to a different site, don't use AuthorizationMenu
if(urlRouteData != null)
{
string controllerName = urlRouteData.Values["controller"].ToString();
string actionName = urlRouteData.Values["action"].ToString();
// Get an instance of the controller that would handle this route
var requestContext = new RequestContext(httpContext, urlRouteData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = (ControllerBase) controllerFactory.CreateController(requestContext, controllerName);
// Find the action descriptor
var controllerContext = new ControllerContext(httpContext, new RouteData(), controller);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
}
}
finally
{
// Reset our request path.
httpContext.RewritePath(originalPath);
}
public Type ControllerType(string controllerName)
{
var fullName = controllerName + "Controller";
var assemblyName = Assembly.GetExecutingAssembly().FullName;
return Activator.CreateInstance(assemblyName, fullTypeName).GetType();
}
public MethodInfo ActionMethodInfo(string actionName, Type controllerType)
{
return controllerType.GetMethod(actionName);
}
Are you thinking of an implementation similar to this? Some Try/Catches required!
MvcSiteMapProvider does this. Here is the code for this particular thing.
Here is the code