Get Controller-instances from Routes.MapHttpRoute - c#

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..

Related

Getting full url not working when url contains #

Hello stack overflow peeps,
Having a bit of an issue implementing an oAuth system.
//https://github.com/justintv/Twitch-API/blob/master/authentication.md
public ActionResult AuthorizeTwitch(CancellationToken cancellationToken) {
var twitchBridge = new TwitchBridge();
var codes = Request.Params.GetValues("code");
var token = Request.Params.GetValues("access_token");
// stage 1 Initial Handshake
if (codes == null && token == null) {
return Redirect(twitchBridge.AuthorizeUrl());
}
// stage 2 We have a code so we are at stage two request the token
else if (codes != null) {
return Redirect(twitchBridge.RetrieveToken(codes[0]));
}
// SAVE OAUTH STUFF DOWN HERE.
// THEN REDIRECT
return RedirectToAction("Index", "Home");
}
The above action seems to work however when for stages 1 and 2 but when I get a response form stage 2 I get redirected to a URL that looks like.
http://localhost:58434/Home/AuthorizeTwitch#access_token=anaccesstoken&scope=user_read
This hits my action but I cant access the value for access_token. I can access Request.Url.OriginalString but the return string looks like.
http://localhost:58434/Home/AuthorizeTwitch
Debugging and looking into the request object and the URL object nothing seems to have stored anything from the # onwards. I suspect this has something to do with the routes setup for my site.
The relevant route that is being hit is
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Can any one see what i'm doing wrong?
In case it is something to do with my actually request to twitch this is how im building the url.
public string AuthorizeUrl() {
return string.Format(
"{0}{1}?response_type=code&client_id={2}&redirect_uri={3}&scope={4}",
TwitchUrlBase,
TwitchAuthorizeMethod,
ClientId,
HttpUtility.UrlEncode(TwitchAuthorizeRedirectURL),
TwitchAuthorizeScope
);
}
public string RetrieveToken(string code) {
return string.Format(
"{0}{1}?response_type=token&client_id={2}&client_secret={3}grant_type=authorization_code&redirect_uri={4}&scope={5}",
TwitchUrlBase,
TwitchAuthorizeMethod,
ClientId,
ClientSecret,
HttpUtility.UrlEncode(TwitchAuthorizeRedirectURL),
TwitchAuthorizeScope
);
}
The fragment is not supposed to be sent by the client to the server, so it makes sense that it's not there. Did you mean to send a query?

Unit Testing WebApi Controller Using In-Memory HttpServer - No HTTP resource was found

From this blog article by Yusef: http://blogs.msdn.com/b/youssefm/archive/2013/01/28/writing-tests-for-an-asp-net-webapi-service.aspx
I'm trying to set up some unit test for a WebApi project but continue to get:
"No HTTP resrouce was found that matches the request URI http://localhost/api/Filter"
Test case:
[TestMethod]
public void TestMethod1()
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
HttpServer server = new HttpServer(config);
using (HttpMessageInvoker client = new HttpMessageInvoker(server))
{
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/Filter"))
{
request.Content = new StringContent(ValidJSONRequest);
request.Content.Headers.Add("content", "application/json");
using (HttpResponseMessage response = client.SendAsync(request, CancellationToken.None).Result)
{
Assert.AreEqual(ValidJSONResponse, response.Content.ReadAsStringAsync().Result);
}
}
};
}
NB. ValidJSONRequest/ValidJSONResponse are string containing JSON objects.
Running in IIS express this routing works perfectly and behaves as expected and I can't for the life of me work out what's going on? What am I missing?
Right, I'm still not sure exactly what's going on here but I've found a workaround.
This blog article contains some details - effectively the controllers context needs to be loaded up into memory... http://www.tugberkugurlu.com/archive/challenge-of-solving-an-asp-net-web-api-self-hosting-problem-no-http-resource-was-found-that-matches-the-request-uri
So how to fix it? Add this test case to the test class and it works fine.
[TestMethod]
public void Filter_Test()
{
FilterController controller = new FilterController();
}
The problem is that you're not specifying an id on your tested URL (http://localhost/api/Filter), and the configureed route doesn't have the id configured as optional.
So, either test a ULR that specifies an id, like http://localhost/api/Filter/1, or chagne the route configuration so that the id is optional, like this: instead of
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional } // optional id
);
In this way, the tested url will match the DefaultApi route.
Of course, you need a Postxxx method in your controller, because you're trying a POST action, and not specifying an action name neither in the tested URL, nor in the route definition. But, if you say it's working on local IIS, then this method must exist.

WebApi Passing data from ActionFilter to Action so I can Unit Test

I have a WebApi service that needs to bill usage. It's an WebApi v1 / MVC 4 that I just upgraded to WebApi 2 / MVC 5. I have an ActionFilterAttribute that will determine the pricing rules for the request. The ActionFilter adds the billing information to the HttpActionContext.Request.Properties. The controller action then performs service requested, bills the usage and returns results.
My problem is I now have a dependency on Request in my controller, which is causing me a problem in unit testing (Structuremap). I was hoping to create a class that exposed properties that internally accessed the Request object, so I could inject fake classes for testing. My first attempt is giving my problems.
I'm hoping to find a better way to pass data from to the controller that I could easily unit test. If I'm doing it the recommended way, then I'll try to solve the structuremap problems. Also, this is my first WebApi project, so I could be doing things the hard way.
Here's some code in case I missed critical details:
ActionFilterAttribute:
public override void OnActionExecuting(HttpActionContext actionContext)
{
...
actionContext.Request.Properties.Add("PricingRule", pricingRule);
actionContext.Request.Properties.Add("ServiceUsage", serviceUsage);
actionContext.Request.Properties.Add("ServiceEndPoint", serviceEndPoint);
// Record how long it took to for pricing code to execute.
actionContext.Request.Headers.Add("PriceDuration", span.TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
}
Controller:
public HttpResponseMessage GetServiceRequest([FromUri]string customerId, [FromUri]string apiKey)
{
....
var priceDuration = Request.Headers.GetValues("PriceDuration").FirstOrDefault();
object myObject;
Request.Properties.TryGetValue("PricingRule", out myObject);
var pricingRule = (PricingRule)myObject;
...
}
Thanks!
Your controller having dependency on Request is not too bad. It is just a property and you can set it like this to any request object of your liking, as you you 'arrange' your test.
var controller = new MyApiControllerClassToUnitTest();
controller.Configuration = new HttpConfiguration();
var route = controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
var routeValues = new HttpRouteValueDictionary();
routeValues.Add("controller", controllerPrefix);
var routeData = new HttpRouteData(route, routeValues);
controller.Request = new HttpRequestMessage(HttpMethod.GET, "http://someuri");
controller.Request.Properties.Add(
HttpPropertyKeys.HttpConfigurationKey, controller.Configuration);
controller.Request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);

ASP.NET MVC: Redirect from query string params to a canonical url

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 controller type and action info from a url or from route data?

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

Categories

Resources